diff --git a/Display.cpp b/Display.cpp index 6deffd1..a2caec9 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 fe6360b..4dc1b83 100644 --- a/Display.h +++ b/Display.h @@ -23,6 +23,8 @@ #include +#include + class CDisplay { public: @@ -61,8 +63,10 @@ public: 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 @@ protected: 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 47b6329..cdd25a8 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 41b0e83..c485a15 100644 --- a/HD44780.h +++ b/HD44780.h @@ -121,6 +121,9 @@ protected: 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 0000000..a502f9a Binary files /dev/null and b/Images/POCSAG.bmp differ diff --git a/LCDproc.cpp b/LCDproc.cpp index d78a43a..8d4e24e 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 fcb1e59..5c43075 100644 --- a/LCDproc.h +++ b/LCDproc.h @@ -60,6 +60,9 @@ protected: 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 11ea6c5..738f4b7 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 b08ebf8..9ccd0aa 100644 --- a/Nextion.h +++ b/Nextion.h @@ -68,6 +68,9 @@ protected: 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 b73602c..77a144b 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 f644ee7..633222b 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 d8e3c33..c30f341 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 641cee6..201c11e 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 939a774..720ea73 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 6fda9bd..d73e4da 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 7b81e82..02973e8 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 72c3842..773abfd 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 42c839d..80f852b 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 9844d1d..47529d4 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 8f6e955..9defd2d 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 c4a8af7..e65594b 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 74ad140..372978d 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 f5ddadc..9e45877 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 8026319..9233a70 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 8927f8a..a096f47 100644 Binary files a/Nextion_G4KLX/NX4832T035.tft and b/Nextion_G4KLX/NX4832T035.tft differ diff --git a/NullDisplay.cpp b/NullDisplay.cpp index a70d4f8..afec322 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 cacb3b2..7af70d7 100644 --- a/NullDisplay.h +++ b/NullDisplay.h @@ -53,6 +53,9 @@ protected: 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 2161623..5d118c7 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); - if (length == 0U) - return; + assert(m_network != NULL); + unsigned char data[40U]; + unsigned int length = m_network->read(data); + if (length == 0U) + 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 aa580ef..a0390e0 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 @@ private: CDisplay* m_display; CRingBuffer m_queue; unsigned int m_frames; + unsigned int m_count; + + 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; - void writeNetwork(); - + 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 1919226..4b822b3 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 5716a24..d617458 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 08762b1..40b8bf4 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 7ad1e88..49a5052 100644 --- a/TFTSerial.h +++ b/TFTSerial.h @@ -55,6 +55,9 @@ protected: 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();