diff --git a/Display.cpp b/Display.cpp index 6deffd197..a2caec91a 100644 --- a/Display.cpp +++ b/Display.cpp @@ -298,6 +298,25 @@ void CDisplay::clearNXDN() } } +void CDisplay::writePOCSAG(uint32_t ric, const std::string& message) +{ + m_timer1.start(); + m_mode1 = MODE_POCSAG; + + writePOCSAGInt(ric, message); +} + +void CDisplay::clearPOCSAG() +{ + if (m_timer1.hasExpired()) { + clearPOCSAGInt(); + m_timer1.stop(); + m_mode1 = MODE_IDLE; + } else { + m_mode1 = MODE_POCSAG; + } +} + void CDisplay::writeCW() { m_timer1.start(); @@ -336,6 +355,11 @@ void CDisplay::clock(unsigned int ms) m_mode1 = MODE_IDLE; m_timer1.stop(); break; + case MODE_POCSAG: + clearPOCSAGInt(); + m_mode1 = MODE_IDLE; + m_timer1.stop(); + break; case MODE_CW: clearCWInt(); m_mode1 = MODE_IDLE; diff --git a/Display.h b/Display.h index fe6360b09..4dc1b8371 100644 --- a/Display.h +++ b/Display.h @@ -23,6 +23,8 @@ #include +#include + class CDisplay { public: @@ -61,8 +63,10 @@ class CDisplay void writeNXDNBER(float ber); void clearNXDN(); + void writePOCSAG(uint32_t ric, const std::string& message); + void clearPOCSAG(); + void writeCW(); - void clearCW(); virtual void close() = 0; @@ -99,6 +103,9 @@ class CDisplay virtual void writeNXDNBERInt(float ber); virtual void clearNXDNInt() = 0; + virtual void writePOCSAGInt(uint32_t ric, const std::string& message) = 0; + virtual void clearPOCSAGInt() = 0; + virtual void writeCWInt() = 0; virtual void clearCWInt() = 0; diff --git a/HD44780.cpp b/HD44780.cpp index 47b632951..cdd25a833 100644 --- a/HD44780.cpp +++ b/HD44780.cpp @@ -995,6 +995,18 @@ void CHD44780::clearNXDNInt() } } +void CHD44780::writePOCSAGInt(uint32_t ric, const std::string& message) +{ + ::lcdPosition(m_fd, m_cols - 5, m_rows - 1); + ::lcdPuts(m_fd, "POCSAG TX"); +} + +void CHD44780::clearPOCSAGInt() +{ + ::lcdPosition(m_fd, m_cols - 5, m_rows - 1); + ::lcdPuts(m_fd, " Idle"); +} + void CHD44780::writeCWInt() { ::lcdPosition(m_fd, m_cols - 5, m_rows - 1); diff --git a/HD44780.h b/HD44780.h index 41b0e83b5..c485a1577 100644 --- a/HD44780.h +++ b/HD44780.h @@ -121,6 +121,9 @@ class CHD44780 : public CDisplay virtual void writeNXDNRSSIInt(unsigned char rssi); virtual void clearNXDNInt(); + virtual void writePOCSAG(uint32_t ric, const std::string& message); + virtual void clearPOCSAGInt(); + virtual void writeCWInt(); virtual void clearCWInt(); diff --git a/Images/POCSAG.bmp b/Images/POCSAG.bmp new file mode 100644 index 000000000..a502f9a1d Binary files /dev/null and b/Images/POCSAG.bmp differ diff --git a/LCDproc.cpp b/LCDproc.cpp index d78a43a33..8d4e24e86 100644 --- a/LCDproc.cpp +++ b/LCDproc.cpp @@ -514,6 +514,14 @@ void CLCDproc::clearNXDNInt() socketPrintf(m_socketfd, "output 16"); // Set LED5 color green } +void CLCDproc::writePOCSAGInt(uint32_t ric, const std::string& message) +{ +} + +void CLCDproc::clearPOCSAGInt() +{ +} + void CLCDproc::writeCWInt() { } diff --git a/LCDproc.h b/LCDproc.h index fcb1e59e3..5c43075fd 100644 --- a/LCDproc.h +++ b/LCDproc.h @@ -60,6 +60,9 @@ class CLCDproc : public CDisplay virtual void writeNXDNRSSIInt(unsigned char rssi); virtual void clearNXDNInt(); + virtual void writePOCSAGInt(uint32_t ric, const std::string& message); + virtual void clearPOCSAGInt(); + virtual void writeCWInt(); virtual void clearCWInt(); diff --git a/Nextion.cpp b/Nextion.cpp index 11ea6c5ea..738f4b7b2 100644 --- a/Nextion.cpp +++ b/Nextion.cpp @@ -616,7 +616,7 @@ void CNextion::writeNXDNInt(const char* source, bool group, unsigned int dest, c if (m_mode != MODE_NXDN) { sendCommand("page NXDN"); - sendCommandAction(6U); + sendCommandAction(7U); } char text[30U]; @@ -679,6 +679,36 @@ void CNextion::clearNXDNInt() sendCommand("t3.txt=\"\""); } +void CNextion::writePOCSAGInt(uint32_t ric, const std::string& message) +{ + if (m_mode != MODE_POCSAG) { + sendCommand("page POCSAG"); + sendCommandAction(6U); + } + + char text[30U]; + ::sprintf(text, "dim=%u", m_brightness); + sendCommand(text); + + ::sprintf(text, "t0.txt=\"RIC: %u\"", ric); + sendCommand(text); + sendCommandAction(132U); + + ::sprintf(text, "t1.txt=\"MSG: %s\"", message.c_str()); + sendCommand(text); + sendCommandAction(133U); + + m_clockDisplayTimer.stop(); + + m_mode = MODE_POCSAG; +} + +void CNextion::clearPOCSAGInt() +{ + sendCommand("t1.txt=\"MMDVM IDLE\""); + sendCommandAction(134U); +} + void CNextion::writeCWInt() { sendCommand("t1.txt=\"Sending CW Ident\""); diff --git a/Nextion.h b/Nextion.h index b08ebf8ee..9ccd0aaab 100644 --- a/Nextion.h +++ b/Nextion.h @@ -68,6 +68,9 @@ class CNextion : public CDisplay virtual void writeNXDNBERInt(float ber); virtual void clearNXDNInt(); + virtual void writePOCSAGInt(uint32_t ric, const std::string& message); + virtual void clearPOCSAGInt(); + virtual void writeCWInt(); virtual void clearCWInt(); diff --git a/Nextion_G4KLX/NX3224K024.HMI b/Nextion_G4KLX/NX3224K024.HMI index b73602c3e..77a144b62 100644 Binary files a/Nextion_G4KLX/NX3224K024.HMI and b/Nextion_G4KLX/NX3224K024.HMI differ diff --git a/Nextion_G4KLX/NX3224K024.tft b/Nextion_G4KLX/NX3224K024.tft index f644ee7a0..633222bf8 100644 Binary files a/Nextion_G4KLX/NX3224K024.tft and b/Nextion_G4KLX/NX3224K024.tft differ diff --git a/Nextion_G4KLX/NX3224K028.HMI b/Nextion_G4KLX/NX3224K028.HMI index d8e3c333c..c30f34164 100644 Binary files a/Nextion_G4KLX/NX3224K028.HMI and b/Nextion_G4KLX/NX3224K028.HMI differ diff --git a/Nextion_G4KLX/NX3224K028.tft b/Nextion_G4KLX/NX3224K028.tft index 641cee6fb..201c11ead 100644 Binary files a/Nextion_G4KLX/NX3224K028.tft and b/Nextion_G4KLX/NX3224K028.tft differ diff --git a/Nextion_G4KLX/NX3224T024.HMI b/Nextion_G4KLX/NX3224T024.HMI index 939a774ce..720ea739f 100644 Binary files a/Nextion_G4KLX/NX3224T024.HMI and b/Nextion_G4KLX/NX3224T024.HMI differ diff --git a/Nextion_G4KLX/NX3224T024.tft b/Nextion_G4KLX/NX3224T024.tft index 6fda9bdca..d73e4da30 100644 Binary files a/Nextion_G4KLX/NX3224T024.tft and b/Nextion_G4KLX/NX3224T024.tft differ diff --git a/Nextion_G4KLX/NX3224T028.HMI b/Nextion_G4KLX/NX3224T028.HMI index 7b81e82b6..02973e8d0 100644 Binary files a/Nextion_G4KLX/NX3224T028.HMI and b/Nextion_G4KLX/NX3224T028.HMI differ diff --git a/Nextion_G4KLX/NX3224T028.tft b/Nextion_G4KLX/NX3224T028.tft index 72c38425f..773abfd84 100644 Binary files a/Nextion_G4KLX/NX3224T028.tft and b/Nextion_G4KLX/NX3224T028.tft differ diff --git a/Nextion_G4KLX/NX4024K032.HMI b/Nextion_G4KLX/NX4024K032.HMI index 42c839d12..80f852b3a 100644 Binary files a/Nextion_G4KLX/NX4024K032.HMI and b/Nextion_G4KLX/NX4024K032.HMI differ diff --git a/Nextion_G4KLX/NX4024K032.tft b/Nextion_G4KLX/NX4024K032.tft index 9844d1d97..47529d411 100644 Binary files a/Nextion_G4KLX/NX4024K032.tft and b/Nextion_G4KLX/NX4024K032.tft differ diff --git a/Nextion_G4KLX/NX4024T032.HMI b/Nextion_G4KLX/NX4024T032.HMI index 8f6e955c4..9defd2d83 100644 Binary files a/Nextion_G4KLX/NX4024T032.HMI and b/Nextion_G4KLX/NX4024T032.HMI differ diff --git a/Nextion_G4KLX/NX4024T032.tft b/Nextion_G4KLX/NX4024T032.tft index c4a8af73b..e65594b0f 100644 Binary files a/Nextion_G4KLX/NX4024T032.tft and b/Nextion_G4KLX/NX4024T032.tft differ diff --git a/Nextion_G4KLX/NX4832K035.HMI b/Nextion_G4KLX/NX4832K035.HMI index 74ad1403c..372978dda 100644 Binary files a/Nextion_G4KLX/NX4832K035.HMI and b/Nextion_G4KLX/NX4832K035.HMI differ diff --git a/Nextion_G4KLX/NX4832K035.tft b/Nextion_G4KLX/NX4832K035.tft index f5ddadc5f..9e4587708 100644 Binary files a/Nextion_G4KLX/NX4832K035.tft and b/Nextion_G4KLX/NX4832K035.tft differ diff --git a/Nextion_G4KLX/NX4832T035.HMI b/Nextion_G4KLX/NX4832T035.HMI index 802631959..9233a70e2 100644 Binary files a/Nextion_G4KLX/NX4832T035.HMI and b/Nextion_G4KLX/NX4832T035.HMI differ diff --git a/Nextion_G4KLX/NX4832T035.tft b/Nextion_G4KLX/NX4832T035.tft index 8927f8a27..a096f47ed 100644 Binary files a/Nextion_G4KLX/NX4832T035.tft and b/Nextion_G4KLX/NX4832T035.tft differ diff --git a/NullDisplay.cpp b/NullDisplay.cpp index a70d4f8a1..afec32218 100644 --- a/NullDisplay.cpp +++ b/NullDisplay.cpp @@ -126,6 +126,20 @@ void CNullDisplay::clearNXDNInt() #endif } +void CNullDisplay::writePOCSAGInt(uint32_t ric, const std::string& message) +{ +#if defined(RASPBERRY_PI) + ::digitalWrite(LED_STATUS, 1); +#endif +} + +void CNullDisplay::clearPOCSAGInt() +{ +#if defined(RASPBERRY_PI) + ::digitalWrite(LED_STATUS, 0); +#endif +} + void CNullDisplay::writeCWInt() { } diff --git a/NullDisplay.h b/NullDisplay.h index cacb3b20d..7af70d7a3 100644 --- a/NullDisplay.h +++ b/NullDisplay.h @@ -53,6 +53,9 @@ class CNullDisplay : public CDisplay virtual void writeNXDNInt(const char* source, bool group, unsigned int dest, const char* type); virtual void clearNXDNInt(); + virtual void writePOCSAGInt(uint32_t ric, const std::string& message); + virtual void clearPOCSAGInt(); + virtual void writeCWInt(); virtual void clearCWInt(); diff --git a/POCSAGControl.cpp b/POCSAGControl.cpp index 216162339..5d118c72d 100644 --- a/POCSAGControl.cpp +++ b/POCSAGControl.cpp @@ -22,15 +22,24 @@ // #define DUMP_POCSAG -const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; +const unsigned char ASCII_EOT = 0x04U; -#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) -#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) +const unsigned char BIT_MASK_TABLE8[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +#define WRITE_BIT8(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE8[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE8[(i)&7]) +#define READ_BIT8(p,i) (p[(i)>>3] & BIT_MASK_TABLE8[(i)&7]) CPOCSAGControl::CPOCSAGControl(CPOCSAGNetwork* network, CDisplay* display) : m_network(network), m_display(display), m_queue(5000U, "POCSAG Control"), +m_frames(0U), +m_count(0U), +m_output(), +m_buffer(), +m_ric(0U), +m_text(), +m_state(PS_NONE), m_fp(NULL) { assert(display != NULL); @@ -38,6 +47,8 @@ m_fp(NULL) CPOCSAGControl::~CPOCSAGControl() { + m_output.clear(); + m_buffer.clear(); } unsigned int CPOCSAGControl::readModem(unsigned char* data) @@ -55,20 +66,203 @@ unsigned int CPOCSAGControl::readModem(unsigned char* data) return len; } -void CPOCSAGControl::writeNetwork() +bool CPOCSAGControl::processData() { - unsigned char netData[40U]; - unsigned int length = m_network->read(netData); + assert(m_network != NULL); + + unsigned char data[40U]; + unsigned int length = m_network->read(data); if (length == 0U) - return; + return false; + + m_ric = 0U; + m_ric |= (data[0U] << 16) & 0x00FF0000U; + m_ric |= (data[1U] << 8) & 0x0000FF00U; + m_ric |= (data[2U] << 0) & 0x000000FFU; + + m_text = std::string((char*)(data + 3U), length - 3U); + + m_buffer.clear(); + addAddress(); + packASCII(); + // Ensure data is an even number of words + if ((m_buffer.size() % 2U) == 1U) + m_buffer.push_back(POCSAG_IDLE_WORD); + + return true; } void CPOCSAGControl::clock(unsigned int ms) { - if (m_network != NULL) - writeNetwork(); + if (m_state == PS_NONE) { + bool ret = processData(); + if (!ret) + return; + + m_display->writePOCSAG(m_ric, m_text); + m_state = PS_WAITING; + m_frames = 0U; + m_count = 1U; + } + + m_output.clear(); + m_output.push_back(POCSAG_SYNC_WORD); + + for (unsigned int i = 0U; i < POCSAG_FRAME_ADDRESSES; i++) { + if (m_state == PS_WAITING) { + if (i == (m_ric % POCSAG_FRAME_ADDRESSES)) { + uint32_t w1 = m_buffer.front(); + m_buffer.pop_front(); + uint32_t w2 = m_buffer.front(); + m_buffer.pop_front(); + + m_output.push_back(w1); + m_output.push_back(w2); + + m_state = PS_SENDING; + } else { + m_output.push_back(POCSAG_IDLE_WORD); + m_output.push_back(POCSAG_IDLE_WORD); + } + } else if (m_state == PS_SENDING) { + uint32_t w1 = m_buffer.front(); + m_buffer.pop_front(); + uint32_t w2 = m_buffer.front(); + m_buffer.pop_front(); + + m_output.push_back(w1); + m_output.push_back(w2); + + if (m_buffer.empty()) { + bool ret = processData(); + if (ret) { + m_display->writePOCSAG(m_ric, m_text); + m_state = PS_WAITING; + m_count++; + } else { + m_state = PS_ENDING; + } + } + } else { // PS_ENDING + m_output.push_back(POCSAG_IDLE_WORD); + m_output.push_back(POCSAG_IDLE_WORD); + } + } + + writeQueue(); + m_frames++; + + if (m_state == PS_ENDING) { + LogMessage("POCSAG, transmitted %u frames of data from %u messages", m_frames, m_count); + m_display->clearPOCSAG(); + m_state = PS_NONE; + } +} + +void CPOCSAGControl::addAddress() +{ + uint32_t word = 0x00001800U; + + word |= (m_ric / POCSAG_FRAME_ADDRESSES) << 13; + + addBCHAndParity(word); + + m_buffer.push_back(word); +} + +void CPOCSAGControl::packASCII() +{ + uint32_t word = 0x80000000U; + + unsigned int n = 30U; + for (std::string::const_iterator it = m_text.cbegin(); it != m_text.cend(); ++it) { + unsigned char MASK = 0x40U; + for (unsigned int j = 0U; j < 7U; j++, MASK >>= 1) { + bool b = (*it & MASK) == MASK; + if (b) + word |= (0x01U << n); + n--; + + if (n == 10U) { + addBCHAndParity(word); + m_buffer.push_back(word); + word = 0x80000000U; + n = 30U; + } + } + } + + // Add at least one EOT to the message + do { + unsigned char MASK = 0x70U; + for (unsigned int j = 0U; j < 7U; j++, MASK >>= 1) { + bool b = (ASCII_EOT & MASK) == MASK; + if (b) + word |= (0x01U << n); + n--; + + if (n == 10U) { + addBCHAndParity(word); + m_buffer.push_back(word); + word = 0x80000000U; + n = 30U; + } + } + } while (n != 30U); +} + +void CPOCSAGControl::addBCHAndParity(uint32_t& word) const +{ + uint32_t temp = word; + + for (unsigned int i = 0U; i < 21U; i++, temp <<= 1) { + if (temp & 0x80000000U) + temp ^= 0xED200000U; + } + + word |= (temp >> 21); + + temp = word; + + unsigned int parity = 0U; + for (unsigned int i = 0U; i < 32U; i++, temp <<= 1) { + if (temp & 0x80000000U) + parity++; + } + + if ((parity % 2U) == 1U) + word |= 0x00000001U; +} + +void CPOCSAGControl::writeQueue() +{ + // Convert 32-bit words to bytes + unsigned int n = 0U; + unsigned char data[POCSAG_FRAME_LENGTH_BYTES]; + for (unsigned int i = 0U; i < POCSAG_FRAME_LENGTH_WORDS; i++) { + uint32_t w = m_output.front(); + m_output.pop_front(); + + for (unsigned int j = 0U; j < 31U; j++, w <<= 1) { + bool b = (w & 0x80000000U) == 0x80000000U; + WRITE_BIT8(data, n, b); + n++; + } + } + + unsigned char len = POCSAG_FRAME_LENGTH_BYTES; + + CUtils::dump(1U, "Data to MMDVM", data, len); + + unsigned int space = m_queue.freeSpace(); + if (space < (len + 1U)) { + LogError("POCSAG, overflow in the POCSAG RF queue"); + return; + } + m_queue.addData(&len, 1U); + m_queue.addData(data, len); } bool CPOCSAGControl::openFile() @@ -88,7 +282,7 @@ bool CPOCSAGControl::openFile() if (m_fp == NULL) return false; - ::fwrite("POCSAG", 1U, 3U, m_fp); + ::fwrite("POCSAG", 1U, 6U, m_fp); return true; } diff --git a/POCSAGControl.h b/POCSAGControl.h index aa580ef6f..a0390e07a 100644 --- a/POCSAGControl.h +++ b/POCSAGControl.h @@ -25,7 +25,10 @@ #include "Display.h" #include "Defines.h" +#include + #include +#include class CPOCSAGControl { public: @@ -41,10 +44,27 @@ class CPOCSAGControl { CDisplay* m_display; CRingBuffer m_queue; unsigned int m_frames; - FILE* m_fp; + unsigned int m_count; - void writeNetwork(); + enum POCSAG_STATE { + PS_NONE, + PS_WAITING, + PS_SENDING, + PS_ENDING + }; + + std::deque m_output; + std::deque m_buffer; + uint32_t m_ric; + std::string m_text; + POCSAG_STATE m_state; + FILE* m_fp; + bool processData(); + void writeQueue(); + void addAddress(); + void packASCII(); + void addBCHAndParity(uint32_t& word) const; bool openFile(); bool writeFile(const unsigned char* data); void closeFile(); diff --git a/POCSAGDefines.h b/POCSAGDefines.h index 1919226da..4b822b314 100644 --- a/POCSAGDefines.h +++ b/POCSAGDefines.h @@ -26,6 +26,8 @@ const unsigned int POCSAG_RADIO_SYMBOL_LENGTH = 20U; // At 24 kHz sample ra const unsigned int POCSAG_FRAME_LENGTH_WORDS = 17U; const unsigned int POCSAG_FRAME_LENGTH_BYTES = POCSAG_FRAME_LENGTH_WORDS * sizeof(uint32_t); +const unsigned int POCSAG_FRAME_ADDRESSES = 8U; + const uint32_t POCSAG_SYNC_WORD = 0x7CD215D8U; const uint32_t POCSAG_IDLE_WORD = 0x7A89C197U; diff --git a/POCSAGNetwork.cpp b/POCSAGNetwork.cpp index 5716a248a..d61745864 100644 --- a/POCSAGNetwork.cpp +++ b/POCSAGNetwork.cpp @@ -114,7 +114,7 @@ void CPOCSAGNetwork::enable(bool enabled) if (enabled && !m_enabled) reset(); - unsigned char c = enabled ? 0xFFU : 0x00U; + unsigned char c = enabled ? 0x00U : 0xFFU; m_socket.write(&c, 1U, m_address, m_port); diff --git a/TFTSerial.cpp b/TFTSerial.cpp index 08762b122..40b8bf434 100644 --- a/TFTSerial.cpp +++ b/TFTSerial.cpp @@ -395,6 +395,20 @@ void CTFTSerial::clearNXDNInt() displayText(" "); } +void CTFTSerial::writePOCSAGInt(uint32_t ric, const std::string& message) +{ + gotoPosPixel(15U, 90U); + displayText("POCSAG TX"); + + m_mode = MODE_CW; +} + +void CTFTSerial::clearPOCSAGInt() +{ + gotoPosPixel(45U, 90U); + displayText("IDLE"); +} + void CTFTSerial::writeCWInt() { gotoPosPixel(45U, 90U); diff --git a/TFTSerial.h b/TFTSerial.h index 7ad1e8860..49a5052ba 100644 --- a/TFTSerial.h +++ b/TFTSerial.h @@ -55,6 +55,9 @@ class CTFTSerial : public CDisplay virtual void writeNXDNInt(const char* source, bool group, unsigned int dest, const char* type); virtual void clearNXDNInt(); + virtual void writePOCSAGInt(uint32_t ric, const std::string& message); + virtual void clearPOCSAGInt(); + virtual void writeCWInt(); virtual void clearCWInt();