From 1f75ee0cc176fd5558ac5f259ed236c30cdc67b1 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 1 Oct 2022 14:56:37 +0200 Subject: [PATCH] [Pager] Implemented POCSAG (#7) --- .../Pager/Pager_Receive/Pager_Receive.ino | 121 +++ .../Pager/Pager_Transmit/Pager_Transmit.ino | 102 +++ keywords.txt | 7 + src/RadioLib.h | 1 + src/TypeDef.h | 12 + src/protocols/Pager/Pager.cpp | 708 ++++++++++++++++++ src/protocols/Pager/Pager.h | 216 ++++++ src/protocols/PhysicalLayer/PhysicalLayer.h | 19 + 8 files changed, 1186 insertions(+) create mode 100644 examples/Pager/Pager_Receive/Pager_Receive.ino create mode 100644 examples/Pager/Pager_Transmit/Pager_Transmit.ino create mode 100644 src/protocols/Pager/Pager.cpp create mode 100644 src/protocols/Pager/Pager.h diff --git a/examples/Pager/Pager_Receive/Pager_Receive.ino b/examples/Pager/Pager_Receive/Pager_Receive.ino new file mode 100644 index 00000000..8497a9ae --- /dev/null +++ b/examples/Pager/Pager_Receive/Pager_Receive.ino @@ -0,0 +1,121 @@ +/* + RadioLib Pager (POCSAG) Receive Example + + This example shows how to receive FSK packets without using + SX127x packet engine. + + This example receives POCSAG messages using SX1278's + FSK modem in direct mode. + + Other modules that can be used to receive POCSAG: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - Si443x/RFM2x + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx127xrfm9x---lora-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 radio = new Module(5, 2, 9, 3); + +// DIO2 pin: 5 +const int pin = 4; + +// create Pager client instance using the FSK module +PagerClient pager(&radio); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 radio = RadioShield.ModuleA; + +void setup() { + Serial.begin(9600); + + // initialize SX1278 with default settings + Serial.print(F("[SX1278] Initializing ... ")); + int state = radio.beginFSK(); + + // when using one of the non-LoRa modules + // (RF69, CC1101, Si4432 etc.), use the basic begin() method + // int state = radio.begin(); + + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + + // initalize Pager client + Serial.print(F("[Pager] Initializing ... ")); + // base (center) frequency: 434.0 MHz + // speed: 1200 bps + state = pager.begin(434.0, 1200); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + + // start receiving POCSAG messages + Serial.print(F("[Pager] Starting to listen ... ")); + // address of this "pager": 1234567 + state = pager.startReceive(pin, 1234567); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + +} + +void loop() { + // the number of batches to wait for + // 2 batches will usually be enough to fit short and medium messages + if (pager.available() >= 2) { + Serial.print(F("[Pager] Received pager data, decoding ... ")); + + // you can read the data as an Arduino String + String str; + int state = pager.readData(str); + + // you can also receive data as byte array + /* + byte byteArr[8]; + size_t numBytes = 0; + int state = radio.receive(byteArr, &numBytes); + */ + + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + + // print the received data + Serial.print(F("[Pager] Data:\t")); + Serial.println(str); + + } else { + // some error occurred + Serial.print(F("failed, code ")); + Serial.println(state); + + } + } +} diff --git a/examples/Pager/Pager_Transmit/Pager_Transmit.ino b/examples/Pager/Pager_Transmit/Pager_Transmit.ino new file mode 100644 index 00000000..0b3dae58 --- /dev/null +++ b/examples/Pager/Pager_Transmit/Pager_Transmit.ino @@ -0,0 +1,102 @@ +/* + RadioLib Pager (POCSAG) Transmit Example + + This example sends POCSAG messages using SX1278's + FSK modem. + + Other modules that can be used to send POCSAG: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x + - nRF24 + - Si443x/RFM2x + - SX128x + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 radio = new Module(5, 2, 9, 3); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 radio = RadioShield.ModuleA; + +// create Pager client instance using the FSK module +PagerClient pager(&radio); + +void setup() { + Serial.begin(9600); + + // initialize SX1278 with default settings + Serial.print(F("[SX1278] Initializing ... ")); + int state = radio.beginFSK(); + + // when using one of the non-LoRa modules + // (RF69, CC1101, Si4432 etc.), use the basic begin() method + // int state = radio.begin(); + + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // initalize Pager client + Serial.print(F("[Pager] Initializing ... ")); + // base (center) frequency: 434.0 MHz + // speed: 1200 bps + state = pager.begin(434.0, 1200); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } +} + +void loop() { + Serial.print(F("[Pager] Transmitting messages ... ")); + + // the simples form of "message" is just a tone on the destination pager + int state = pager.sendTone(1234567); + delay(500); + + // next, transmit numeric (BCD) message to the destination pager + // NOTE: Only characters 0123456789*U-() and space + // can be sent in a BCD message! + state |= pager.transmit("0123456789*U -()", 1234567); + delay(500); + + // finally, let's transmit an ASCII message now + state |= pager.transmit("Hello World!", 1234567, RADIOLIB_PAGER_ASCII); + delay(500); + + // we can also send only a tone + + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + + // wait for a second before transmitting again + delay(3000); +} diff --git a/keywords.txt b/keywords.txt index 733a8b9b..cd5c8d06 100644 --- a/keywords.txt +++ b/keywords.txt @@ -49,6 +49,7 @@ HellClient KEYWORD1 AFSKClient KEYWORD1 FSK4Client KEYWORD1 APRSClient KEYWORD1 +PagerClient KEYWORD1 # SSTV modes Scottie1 KEYWORD1 @@ -239,6 +240,9 @@ noTone KEYWORD2 sendPosition KEYWORD2 sendMicE KEYWORD2 +# Pager +sendTone KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### @@ -330,3 +334,6 @@ RADIOLIB_ERR_INVALID_NUM_REPEATERS LITERAL1 RADIOLIB_ERR_INVALID_REPEATER_CALLSIGN LITERAL1 RADIOLIB_ERR_RANGING_TIMEOUT LITERAL1 + +RADIOLIB_ERR_INVALID_PAYLOAD LITERAL1 +RADIOLIB_ERR_ADDRESS_NOT_FOUND LITERAL1 diff --git a/src/RadioLib.h b/src/RadioLib.h index 0d202699..50e2fc7f 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -87,6 +87,7 @@ #include "protocols/AX25/AX25.h" #include "protocols/Hellschreiber/Hellschreiber.h" #include "protocols/Morse/Morse.h" +#include "protocols/Pager/Pager.h" #include "protocols/RTTY/RTTY.h" #include "protocols/SSTV/SSTV.h" #include "protocols/FSK4/FSK4.h" diff --git a/src/TypeDef.h b/src/TypeDef.h index 49a29f78..0d250d50 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -405,6 +405,18 @@ */ #define RADIOLIB_ERR_RANGING_TIMEOUT (-901) +// Pager-specific status codes + +/*! + \brief The provided payload data configuration is invalid. +*/ +#define RADIOLIB_ERR_INVALID_PAYLOAD (-1001) + +/*! + \brief The requested address was not found in the received data. +*/ +#define RADIOLIB_ERR_ADDRESS_NOT_FOUND (-1002) + /*! \} */ diff --git a/src/protocols/Pager/Pager.cpp b/src/protocols/Pager/Pager.cpp new file mode 100644 index 00000000..f75df513 --- /dev/null +++ b/src/protocols/Pager/Pager.cpp @@ -0,0 +1,708 @@ +#include "Pager.h" + +// this is a massive hack, but we need a global-scope ISR to manage the bit reading +// let's hope nobody ever tries running two POCSAG receivers at the same time +static PhysicalLayer* _readBitInstance = NULL; +static RADIOLIB_PIN_TYPE _readBitPin = RADIOLIB_NC; +static void PagerClientReadBit(void) { + if(_readBitInstance) { + _readBitInstance->readBit(_readBitPin); + } +} + +PagerClient::PagerClient(PhysicalLayer* phy) { + _phy = phy; + _readBitInstance = _phy; +} + +int16_t PagerClient::begin(float base, uint16_t speed) { + // calculate duration of 1 bit in us + _speed = (float)speed/1000.0f; + _bitDuration = (uint32_t)1000000/speed; + + // calculate 24-bit frequency + _base = base; + _baseRaw = (_base * 1000000.0) / _phy->getFreqStep(); + + // calculate module carrier frequency resolution + uint16_t step = round(_phy->getFreqStep()); + + // calculate raw frequency shift + _shift = RADIOLIB_PAGER_FREQ_SHIFT_HZ/step; + + // initialize BCH encoder + encoderInit(); + + // configure for direct mode + return(_phy->startDirect()); +} + +int16_t PagerClient::sendTone(uint32_t addr) { + return(PagerClient::transmit(NULL, 0, addr)); +} + +int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding) { + return(PagerClient::transmit(str.c_str(), addr, encoding)); +} + +int16_t PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding) { + return(PagerClient::transmit((uint8_t*)str, strlen(str), addr, encoding)); +} + +int16_t PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) { + if(addr > RADIOLIB_PAGER_ADDRESS_MAX) { + return(RADIOLIB_ERR_INVALID_ADDRESS_WIDTH); + } + + if(((data == NULL) && (len > 0)) || ((data != NULL) && (len == 0))) { + return(RADIOLIB_ERR_INVALID_PAYLOAD); + } + + // get symbol bit length based on encoding + uint8_t symbolLength = 0; + uint32_t function = 0; + if(encoding == RADIOLIB_PAGER_BCD) { + symbolLength = 4; + function = RADIOLIB_PAGER_FUNC_BITS_NUMERIC; + + } else if(encoding == RADIOLIB_PAGER_ASCII) { + symbolLength = 7; + function = RADIOLIB_PAGER_FUNC_BITS_ALPHA; + + } else { + return(RADIOLIB_ERR_INVALID_ENCODING); + + } + + if(len == 0) { + function = RADIOLIB_PAGER_FUNC_BITS_TONE; + } + + // get target position in batch (3 LSB from address determine frame position in batch) + uint8_t framePos = 2*(addr & 0x07); + + // get address that will be written into address frame + uint32_t frameAddr = ((addr >> 3) << RADIOLIB_PAGER_ADDRESS_POS) | function; + + // calculate the number of 20-bit data blocks + size_t numDataBlocks = (len * symbolLength) / RADIOLIB_PAGER_MESSAGE_BITS_LENGTH; + if((len * symbolLength) % RADIOLIB_PAGER_MESSAGE_BITS_LENGTH > 0) { + numDataBlocks += 1; + } + + // calculate number of batches + size_t numBatches = (1 + framePos + numDataBlocks) / RADIOLIB_PAGER_BATCH_LEN + 1; + if((1 + numDataBlocks) % RADIOLIB_PAGER_BATCH_LEN == 0) { + numBatches -= 1; + } + + // calculate message length in 32-bit code words + size_t msgLen = RADIOLIB_PAGER_PREAMBLE_LENGTH + (1 + RADIOLIB_PAGER_BATCH_LEN) * numBatches; + + #if defined(RADIOLIB_STATIC_ONLY) + uint32_t msg[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint32_t* msg = new uint32_t[msgLen]; + #endif + + // build the message + memset(msg, 0x00, msgLen*sizeof(uint32_t)); + + // set preamble + for(size_t i = 0; i < RADIOLIB_PAGER_PREAMBLE_LENGTH; i++) { + msg[i] = RADIOLIB_PAGER_PREAMBLE_CODE_WORD; + } + + // start by setting everything after preamble to idle + for(size_t i = RADIOLIB_PAGER_PREAMBLE_LENGTH; i < msgLen; i++) { + msg[i] = RADIOLIB_PAGER_IDLE_CODE_WORD; + } + + // set frame synchronization code words + for(size_t i = 0; i < numBatches; i++) { + msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + i*(1 + RADIOLIB_PAGER_BATCH_LEN)] = RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD; + } + + // write address code word + msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos] = encodeBCH(frameAddr); + + // write the data as 20-bit code blocks + if(len > 0) { + int8_t remBits = 0; + uint8_t dataPos = 0; + for(uint8_t i = 0; i < numDataBlocks + numBatches - 1; i++) { + uint8_t blockPos = RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos + 1 + i; + + // check if we need to skip a frame sync marker + if(((blockPos - (RADIOLIB_PAGER_PREAMBLE_LENGTH + 1)) % RADIOLIB_PAGER_BATCH_LEN) == 0) { + blockPos++; + i++; + } + + // mark this as a message code word + msg[blockPos] = RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1); + + // first insert the remainder from previous code word (if any) + if(remBits > 0) { + // this doesn't apply to BCD messages, so no need to check that here + uint8_t prev = Module::flipBits(data[dataPos - 1]); + prev >>= 1; + msg[blockPos] |= (uint32_t)prev << (RADIOLIB_PAGER_CODE_WORD_LEN - 1 - remBits); + } + + // set all message symbols until we overflow to the next code word or run out of message symbols + int8_t symbolPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength - remBits; + while(symbolPos > (RADIOLIB_PAGER_FUNC_BITS_POS - symbolLength)) { + + // for BCD, encode the symbol + uint8_t symbol = data[dataPos++]; + if(encoding == RADIOLIB_PAGER_BCD) { + symbol = encodeBCD(symbol); + } + symbol = Module::flipBits(symbol); + symbol >>= (8 - symbolLength); + + // insert the next message symbol + msg[blockPos] |= (uint32_t)symbol << symbolPos; + symbolPos -= symbolLength; + + // check if we ran out of message symbols + if(dataPos >= len) { + // in BCD mode, pad the rest of the code word with spaces (0xC) + if(encoding == RADIOLIB_PAGER_BCD) { + uint8_t numSteps = (symbolPos - RADIOLIB_PAGER_FUNC_BITS_POS + symbolLength)/symbolLength; + for(uint8_t i = 0; i < numSteps; i++) { + symbol = encodeBCD(' '); + symbol = Module::flipBits(symbol); + symbol >>= (8 - symbolLength); + msg[blockPos] |= (uint32_t)symbol << symbolPos; + symbolPos -= symbolLength; + } + } + break; + } + } + + // ensure the parity bits are not set due to overflow + msg[blockPos] &= ~(RADIOLIB_PAGER_BCH_BITS_MASK); + + // save the number of overflown bits + remBits = RADIOLIB_PAGER_FUNC_BITS_POS - symbolPos - symbolLength; + + // do the FEC + msg[blockPos] = encodeBCH(msg[blockPos]); + } + } + + // transmit the message + PagerClient::write(msg, msgLen); + + #if !defined(RADIOLIB_STATIC_ONLY) + delete[] msg; + #endif + + // turn transmitter off + _phy->standby(); + + return(RADIOLIB_ERR_NONE); +} + +int16_t PagerClient::startReceive(RADIOLIB_PIN_TYPE pin, uint32_t addr, uint32_t mask) { + // save the variables + _readBitPin = pin; + _filterAddr = addr; + _filterMask = mask; + + // set the carrier frequency + int16_t state = _phy->setFrequency(_base); + RADIOLIB_ASSERT(state); + + // set bitrate + state = _phy->setBitRate(_speed); + RADIOLIB_ASSERT(state); + + // set frequency deviation to 4.5 khz + state = _phy->setFrequencyDeviation((float)RADIOLIB_PAGER_FREQ_SHIFT_HZ / 1000.0f); + RADIOLIB_ASSERT(state); + + // now set up the direct mode reception + _phy->setDirectSyncWord(RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD, 32); + _phy->setDirectAction(PagerClientReadBit); + _phy->receiveDirect(); + + return(state); +} + +size_t PagerClient::available() { + return(_phy->available() + sizeof(uint32_t))/(sizeof(uint32_t) * (RADIOLIB_PAGER_BATCH_LEN + 1)); +} + +int16_t PagerClient::readData(String& str, size_t len) { + int16_t state = RADIOLIB_ERR_NONE; + + // determine the message length, based on user input or the amount of received data + size_t length = len; + if(length == 0) { + // one batch can contain at most 80 message symbols + length = available()*80; + } + + // build a temporary buffer + #if defined(RADIOLIB_STATIC_ONLY) + uint8_t data[RADIOLIB_STATIC_ARRAY_SIZE + 1]; + #else + uint8_t* data = new uint8_t[length + 1]; + if(!data) { + return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); + } + #endif + + // read the received data + state = readData(data, &length); + + if(state == RADIOLIB_ERR_NONE) { + // check tone-only tramsissions + if(length == 0) { + length = 6; + strncpy((char*)data, "", length); + } + + // add null terminator + data[length] = 0; + + // initialize Arduino String class + str = String((char*)data); + } + + // deallocate temporary buffer + #if !defined(RADIOLIB_STATIC_ONLY) + delete[] data; + #endif + + return(state); +} + +int16_t PagerClient::readData(uint8_t* data, size_t* len) { + // find the correct address + bool match = false; + uint8_t framePos = 0; + uint8_t symbolLength = 0; + while(!match && _phy->available()) { + uint32_t cw = read(); + framePos++; + + // check if it's the idle code word + if(cw == RADIOLIB_PAGER_IDLE_CODE_WORD) { + continue; + } + + // check if it's the sync word + if(cw == RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD) { + framePos = 0; + continue; + } + + // not an idle code word, check if it's an address word + if(cw & (RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1))) { + // this is pretty weird, it seems to be a message code word without address + continue; + } + + // should be an address code word, extract the address + uint32_t addr = ((cw & RADIOLIB_PAGER_ADDRESS_BITS_MASK) >> (RADIOLIB_PAGER_ADDRESS_POS - 3)) | (framePos/2); + if((addr & _filterMask) == (_filterAddr & _filterMask)) { + // we have a match! + match = true; + + // determine the encoding from the function bits + if((cw & RADIOLIB_PAGER_FUNCTION_BITS_MASK) == RADIOLIB_PAGER_FUNC_BITS_NUMERIC) { + symbolLength = 4; + } else { + symbolLength = 7; + } + } + } + + if(!match) { + // address not found + return(RADIOLIB_ERR_ADDRESS_NOT_FOUND); + } + + // we have the address, start pulling out the message + bool complete = false; + size_t decodedBytes = 0; + uint32_t prevCw = 0; + bool overflow = false; + int8_t ovfBits = 0; + while(!complete && _phy->available()) { + uint32_t cw = read(); + + // check if it's the idle code word + if(cw == RADIOLIB_PAGER_IDLE_CODE_WORD) { + complete = true; + break; + } + + // skip the sync words + if(cw == RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD) { + continue; + } + + // check overflow from previous code word + uint8_t bitPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength; + if(overflow) { + overflow = false; + + // this is a bit convoluted - first, build masks for both previous and current code word + uint8_t currPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength + ovfBits; + uint8_t prevPos = RADIOLIB_PAGER_MESSAGE_END_POS; + uint32_t prevMask = (0x7FUL << prevPos) & ~((uint32_t)0x7FUL << (RADIOLIB_PAGER_MESSAGE_END_POS + ovfBits)); + uint32_t currMask = (0x7FUL << currPos) & ~((uint32_t)1 << (RADIOLIB_PAGER_CODE_WORD_LEN - 1)); + + // next, get the two parts of the message symbol and stick them together + uint8_t prevSymbol = (prevCw & prevMask) >> prevPos; + uint8_t currSymbol = (cw & currMask) >> currPos; + uint32_t symbol = prevSymbol << (symbolLength - ovfBits) | currSymbol; + + // finally, we can flip the bits + symbol = Module::flipBits((uint8_t)symbol); + symbol >>= (8 - symbolLength); + + // decode BCD and we're done + if(symbolLength == 4) { + symbol = decodeBCD(symbol); + } + data[decodedBytes++] = symbol; + + // adjust the bit position of the next message symbol + bitPos += ovfBits; + bitPos -= symbolLength; + } + + // get the message symbols based on the encoding type + while(bitPos >= RADIOLIB_PAGER_MESSAGE_END_POS) { + // get the message symbol from the code word and reverse bits + uint32_t symbol = (cw & (0x7FUL << bitPos)) >> bitPos; + symbol = Module::flipBits((uint8_t)symbol); + symbol >>= (8 - symbolLength); + + // decode BCD if needed + if(symbolLength == 4) { + symbol = decodeBCD(symbol); + } + data[decodedBytes++] = symbol; + + // now calculate if the next symbol is overflowing to the following code word + int8_t remBits = bitPos - RADIOLIB_PAGER_MESSAGE_END_POS; + if(remBits < symbolLength) { + // overflow! + prevCw = cw; + overflow = true; + ovfBits = remBits; + } + bitPos -= symbolLength; + } + + } + + // save the number of decoded bytes + *len = decodedBytes; + return(RADIOLIB_ERR_NONE); +} + +void PagerClient::write(uint32_t* data, size_t len) { + // write code words from buffer + for(size_t i = 0; i < len; i++) { + PagerClient::write(data[i]); + } +} + +void PagerClient::write(uint32_t codeWord) { + // write single code word + Module* mod = _phy->getMod(); + for(int8_t i = 31; i >= 0; i--) { + uint32_t mask = (uint32_t)0x01 << i; + if(codeWord & mask) { + // send 1 + uint32_t start = mod->micros(); + _phy->transmitDirect(_baseRaw + _shift); + while(mod->micros() - start < _bitDuration); + } else { + // send 0 + uint32_t start = mod->micros(); + _phy->transmitDirect(_baseRaw - _shift); + while(mod->micros() - start < _bitDuration); + } + } +} + +uint32_t PagerClient::read() { + uint32_t codeWord = 0; + codeWord |= (uint32_t)_phy->read() << 24; + codeWord |= (uint32_t)_phy->read() << 16; + codeWord |= (uint32_t)_phy->read() << 8; + codeWord |= (uint32_t)_phy->read(); + + // TODO BCH error correction here + return(codeWord); +} + +uint8_t PagerClient::encodeBCD(char c) { + switch(c) { + case '*': + return(0x0A); + case 'U': + return(0x0B); + case ' ': + return(0x0C); + case '-': + return(0x0D); + case ')': + return(0x0E); + case '(': + return(0x0F); + } + return(c - '0'); +} + +char PagerClient::decodeBCD(uint8_t b) { + switch(b) { + case 0x0A: + return('*'); + case 0x0B: + return('U'); + case 0x0C: + return(' '); + case 0x0D: + return('-'); + case 0x0E: + return(')'); + case 0x0F: + return('('); + } + return(b + '0'); +} + +/* + BCH Encoder based on https://www.codeproject.com/articles/13189/pocsag-encoder + + Significantly cleaned up and slightly fixed. +*/ +void PagerClient::encoderInit() { + /* + * generate GF(2**m) from the irreducible polynomial p(X) in p[0]..p[m] + * lookup tables: index->polynomial form _bchAlphaTo[] contains j=alpha**i; + * polynomial form -> index form _bchIndexOf[j=alpha**i] = i alpha=2 is the + * primitive element of GF(2**m) + */ + + int32_t mask = 1; + _bchAlphaTo[RADIOLIB_PAGER_BCH_M] = 0; + + for(uint8_t i = 0; i < RADIOLIB_PAGER_BCH_M; i++) { + _bchAlphaTo[i] = mask; + + _bchIndexOf[_bchAlphaTo[i]] = i; + + if(RADIOLIB_PAGER_BCH_PRIMITIVE_POLY & ((uint32_t)0x01 << i)) { + _bchAlphaTo[RADIOLIB_PAGER_BCH_M] ^= mask; + } + + mask <<= 1; + } + + _bchIndexOf[_bchAlphaTo[RADIOLIB_PAGER_BCH_M]] = RADIOLIB_PAGER_BCH_M; + mask >>= 1; + + for(uint8_t i = RADIOLIB_PAGER_BCH_M + 1; i < RADIOLIB_PAGER_BCH_N; i++) { + if(_bchAlphaTo[i - 1] >= mask) { + _bchAlphaTo[i] = _bchAlphaTo[RADIOLIB_PAGER_BCH_M] ^ ((_bchAlphaTo[i - 1] ^ mask) << 1); + } else { + _bchAlphaTo[i] = _bchAlphaTo[i - 1] << 1; + } + + _bchIndexOf[_bchAlphaTo[i]] = i; + } + + _bchIndexOf[0] = -1; + + /* + * Compute generator polynomial of BCH code of length = 31, redundancy = 10 + * (OK, this is not very efficient, but we only do it once, right? :) + */ + + int32_t ii = 0; + int32_t jj = 1; + int32_t ll = 0; + int32_t kaux = 0; + bool test = false; + int32_t aux = 0; + int32_t cycle[15][6] = { { 0 } }; + int32_t size[15] = { 0 }; + + // Generate cycle sets modulo 31 + cycle[0][0] = 0; size[0] = 1; + cycle[1][0] = 1; size[1] = 1; + + do { + // Generate the jj-th cycle set + ii = 0; + do { + ii++; + cycle[jj][ii] = (cycle[jj][ii - 1] * 2) % RADIOLIB_PAGER_BCH_N; + size[jj]++; + aux = (cycle[jj][ii] * 2) % RADIOLIB_PAGER_BCH_N; + } while(aux != cycle[jj][0]); + + // Next cycle set representative + ll = 0; + do { + ll++; + test = false; + for(ii = 1; ((ii <= jj) && !test); ii++) { + // Examine previous cycle sets + for(kaux = 0; ((kaux < size[ii]) && !test); kaux++) { + test = (ll == cycle[ii][kaux]); + } + } + } while(test && (ll < (RADIOLIB_PAGER_BCH_N - 1))); + + if(!test) { + jj++; // next cycle set index + cycle[jj][0] = ll; + size[jj] = 1; + } + + } while(ll < (RADIOLIB_PAGER_BCH_N - 1)); + + // Search for roots 1, 2, ..., d-1 in cycle sets + int32_t rdncy = 0; + int32_t min[11]; + kaux = 0; + + for(ii = 1; ii <= jj; ii++) { + min[kaux] = 0; + for(jj = 0; jj < size[ii]; jj++) { + for(uint8_t root = 1; root < RADIOLIB_PAGER_BCH_D; root++) { + if(root == cycle[ii][jj]) { + min[kaux] = ii; + } + } + } + + if(min[kaux]) { + rdncy += size[min[kaux]]; + kaux++; + } + } + + int32_t noterms = kaux; + int32_t zeros[11]; + kaux = 1; + + for(ii = 0; ii < noterms; ii++) { + for(jj = 0; jj < size[min[ii]]; jj++) { + zeros[kaux] = cycle[min[ii]][jj]; + kaux++; + } + } + + // Compute generator polynomial + _bchG[0] = _bchAlphaTo[zeros[1]]; + _bchG[1] = 1; // g(x) = (X + zeros[1]) initially + + for(ii = 2; ii <= rdncy; ii++) { + _bchG[ii] = 1; + for(jj = ii - 1; jj > 0; jj--) { + if(_bchG[jj] != 0) { + _bchG[jj] = _bchG[jj - 1] ^ _bchAlphaTo[(_bchIndexOf[_bchG[jj]] + zeros[ii]) % RADIOLIB_PAGER_BCH_N]; + } else { + _bchG[jj] = _bchG[jj - 1]; + } + } + _bchG[0] = _bchAlphaTo[(_bchIndexOf[_bchG[0]] + zeros[ii]) % RADIOLIB_PAGER_BCH_N]; + } +} + +/* + BCH Encoder based on https://www.codeproject.com/articles/13189/pocsag-encoder + + Significantly cleaned up and slightly fixed. +*/ +uint32_t PagerClient::encodeBCH(uint32_t dat) { + // we only use the 21 most significant bits + int32_t data[21]; + int32_t j1 = 0; + for(int32_t i = 31; i > 10; i--) { + if(dat & ((uint32_t)1< start-2; --i) { + if(Mr[start]) { + Mr[i] ^= _bchG[j]; + ++j; + } else { + ++start; + j = 0; + end = start + RADIOLIB_PAGER_BCH_N - RADIOLIB_PAGER_BCH_K; + break; + } + } + } + + int32_t bb[11]; + j = 0; + for(int32_t i = start; i < end; ++i) { + bb[j] = Mr[i]; + ++j; + } + + int32_t iEvenParity = 0; + int32_t recd[32]; + for(uint8_t i = 0; i < 21; i++) { + recd[31 - i] = data[i]; + if(data[i] == 1) { + iEvenParity++; + } + } + + for(uint8_t i = 0; i < 11; i++) { + recd[10 - i] = bb[i]; + if(bb[i] == 1) { + iEvenParity++; + } + } + + if((iEvenParity % 2) == 0) { + recd[0] = 0; + } else { + recd[0] = 1; + } + + int32_t Codeword[32]; + memcpy(Codeword, recd, sizeof(int32_t)*32); + + int32_t iResult = 0; + for(int32_t i = 0; i < 32; i++) { + if(Codeword[i]) { + iResult |= ((uint32_t)1<