diff --git a/src/protocols/Pager/Pager.cpp b/src/protocols/Pager/Pager.cpp index 1416247a4..ac1e79f38 100644 --- a/src/protocols/Pager/Pager.cpp +++ b/src/protocols/Pager/Pager.cpp @@ -19,6 +19,102 @@ static void PagerClientReadBit(void) { } #endif +PagerMessage::PagerMessage(uint32_t address, uint8_t function, uint8_t* data, size_t data_len, uint8_t encoding) + : address(address) + , function(function) + , data(data) + , data_len(data_len) + , encoding(encoding) + {} + +PagerMessage::PagerMessage(uint32_t address, uint8_t* data, size_t data_len, uint8_t encoding) + : address(address) + , data(data) + , data_len(data_len) + , encoding(encoding) + { + // Automatically set function bits based on given encoding + if(encoding == RADIOLIB_PAGER_BCD) { + function = RADIOLIB_PAGER_FUNC_BITS_NUMERIC; + } else if(encoding == RADIOLIB_PAGER_ASCII) { + function = RADIOLIB_PAGER_FUNC_BITS_ALPHA; + } + if(data_len == 0) { + function = RADIOLIB_PAGER_FUNC_BITS_TONE; + } + } + +PagerMessage::PagerMessage(uint32_t address, const char* string, uint8_t encoding) + : PagerMessage(address, (uint8_t*)string, strlen(string), encoding) + {} + +uint32_t PagerMessage::getAddr_h() { + return address >> 3; +} + +uint32_t PagerMessage::getAddr_l() { + return address & 0x07; +} + +// get target position in batch (3 LSB from address determine frame position in batch) +uint8_t PagerMessage::getAddrFrameNr() { + return 2*getAddr_l(); +} + +uint8_t PagerMessage::getFirstDataFrameNr() { + return getAddrFrameNr()+1; +} + +// get symbol bit length based on encoding +uint8_t PagerMessage::getSymbolLength() { + if(encoding == RADIOLIB_PAGER_BCD) { + return 4; + } else if(encoding == RADIOLIB_PAGER_ASCII) { + return 7; + } else { + // invalid + return 0; + } +} + +// calculate the number of 20-bit data blocks +size_t PagerMessage::getNumDataBlocks() { + size_t numDataBlocks = (data_len * getSymbolLength()) / RADIOLIB_PAGER_MESSAGE_BITS_LENGTH; + if((data_len * getSymbolLength()) % RADIOLIB_PAGER_MESSAGE_BITS_LENGTH > 0) { + numDataBlocks += 1; + } + return numDataBlocks; +} + +// calculate number of batches needed +size_t PagerMessage::getNumBatches(){ + size_t numBatches = (getAddrFrameNr() + 1 + getNumDataBlocks()) / RADIOLIB_PAGER_BATCH_LEN; + if((getAddrFrameNr() + 1 + getNumDataBlocks()) % RADIOLIB_PAGER_BATCH_LEN > 0) { + numBatches += 1; + } + return numBatches; +} + +uint16_t PagerMessage::validate() { + if(address > RADIOLIB_PAGER_ADDRESS_MAX) { + return(RADIOLIB_ERR_INVALID_ADDRESS_WIDTH); + } + + if(((data == NULL) && (data_len > 0)) || ((data != NULL) && (data_len == 0))) { + return(RADIOLIB_ERR_INVALID_PAYLOAD); + } + + if (getSymbolLength() <= 0) { + return(RADIOLIB_ERR_INVALID_ENCODING); + } + + if (function > RADIOLIB_PAGER_FUNC_BITS_ALPHA) { + return(RADIOLIB_ERR_INVALID_FUNCTION); + } + + return(RADIOLIB_ERR_NONE); +} + PagerClient::PagerClient(PhysicalLayer* phy) { phyLayer = phy; #if !defined(RADIOLIB_EXCLUDE_DIRECT_RECEIVE) @@ -58,182 +154,179 @@ int16_t PagerClient::sendTone(uint32_t addr) { int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding, uint8_t function) { return(PagerClient::transmit(str.c_str(), addr, encoding, function)); } +int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding) { + return(PagerClient::transmit(str.c_str(), addr, encoding)); +} #endif int16_t PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding, uint8_t function) { return(PagerClient::transmit((uint8_t*)str, strlen(str), addr, encoding, function)); } +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, uint8_t function) { - 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; - if(encoding == RADIOLIB_PAGER_BCD) { - symbolLength = 4; - - } else if(encoding == RADIOLIB_PAGER_ASCII) { - symbolLength = 7; + PagerMessage message(addr, function, data, len, encoding); + return(PagerClient::transmit(message)); +} +int16_t PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) { + PagerMessage message(addr, data, len, encoding); + return(PagerClient::transmit(message)); +} - } else { - return(RADIOLIB_ERR_INVALID_ENCODING); +int16_t PagerClient::transmitBuffer(uint32_t* buf, size_t num_words) { + size_t num_batches = num_words / RADIOLIB_PAGER_BATCH_LEN; + // transmit the preamble + for(size_t i = 0; i < RADIOLIB_PAGER_PREAMBLE_LENGTH; i++) { + PagerClient::write(RADIOLIB_PAGER_PREAMBLE_CODE_WORD); } - // Automatically set function bits based on given encoding - if (function == RADIOLIB_PAGER_FUNC_AUTO) { - if(encoding == RADIOLIB_PAGER_BCD) { - function = RADIOLIB_PAGER_FUNC_BITS_NUMERIC; - - } else if(encoding == RADIOLIB_PAGER_ASCII) { - function = RADIOLIB_PAGER_FUNC_BITS_ALPHA; - - } else { - return(RADIOLIB_ERR_INVALID_ENCODING); + for(size_t i = 0; i < num_batches; i++) { + // transmit the frame synchronization word + PagerClient::write(RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD); - } - if(len == 0) { - function = RADIOLIB_PAGER_FUNC_BITS_TONE; - } + // transmit the batch + PagerClient::write(buf+i*RADIOLIB_PAGER_BATCH_LEN, RADIOLIB_PAGER_BATCH_LEN); } - if (function > RADIOLIB_PAGER_FUNC_BITS_ALPHA) { - return(RADIOLIB_ERR_INVALID_FUNCTION); - } - - // 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 << RADIOLIB_PAGER_FUNC_BITS_POS); - // 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; - } + // turn transmitter off + phyLayer->standby(); - // 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; - } + return(RADIOLIB_ERR_NONE); +} +int16_t PagerClient::encodeMessage(uint32_t* buf, PagerMessage &message) { // 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 + size_t msgLen = RADIOLIB_PAGER_BATCH_LEN * message.getNumBatches(); - // 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; + // start by setting everything to idle + for(size_t i = 0; i < msgLen; i++) { + buf[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; - } + // construct address frame + uint32_t frameAddr = (message.getAddr_h() << RADIOLIB_PAGER_ADDRESS_POS) | (message.getFunction() << RADIOLIB_PAGER_FUNC_BITS_POS); // write address code word - msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos] = RadioLibBCHInstance.encode(frameAddr); + buf[message.getAddrFrameNr()] = RadioLibBCHInstance.encode(frameAddr); + + + uint8_t symbolLength = message.getSymbolLength(); + uint8_t* data = message.getData(); + uint32_t* buf_data_ptr = buf+message.getFirstDataFrameNr(); // write the data as 20-bit code blocks - if(len > 0) { int8_t remBits = 0; uint8_t dataPos = 0; - for(size_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++; - } + for(uint8_t blockPos = 0; blockPos < message.getNumDataBlocks(); blockPos++) { // mark this as a message code word - msg[blockPos] = RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1); + buf_data_ptr[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::reflect(data[dataPos - 1], 8); prev >>= 1; - msg[blockPos] |= (uint32_t)prev << (RADIOLIB_PAGER_CODE_WORD_LEN - 1 - remBits); + buf_data_ptr[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)) { + uint8_t symbol; + if(dataPos < message.getDataLen()) { + symbol = data[dataPos++]; + } else { // if(dataPos >= message.getDataLen()) { + // we ran out of message symbols + // in BCD mode, pad the rest of the code word with spaces (0xC) + if(message.getEncoding() == RADIOLIB_PAGER_BCD) { + symbol = ' '; + } else { + break; + } + } // for BCD, encode the symbol - uint8_t symbol = data[dataPos++]; - if(encoding == RADIOLIB_PAGER_BCD) { + if(message.getEncoding() == RADIOLIB_PAGER_BCD) { symbol = encodeBCD(symbol); } symbol = Module::reflect(symbol, 8); symbol >>= (8 - symbolLength); // insert the next message symbol - msg[blockPos] |= (uint32_t)symbol << symbolPos; + buf_data_ptr[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::reflect(symbol, 8); - 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); + buf_data_ptr[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] = RadioLibBCHInstance.encode(msg[blockPos]); + buf_data_ptr[blockPos] = RadioLibBCHInstance.encode(buf_data_ptr[blockPos]); } + + return(msgLen); + +} + +int16_t PagerClient::transmit(PagerMessage &message) { + + uint16_t err = message.validate(); + if (err) { + return(err); } - // transmit the message - PagerClient::write(msg, msgLen); + // calculate message length in 32-bit code words + size_t msgLen = RADIOLIB_PAGER_BATCH_LEN * message.getNumBatches(); + + #if defined(RADIOLIB_STATIC_ONLY) + uint32_t msg[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint32_t* msg = new uint32_t[msgLen]; + #endif + + encodeMessage(msg, message); + + transmitBuffer(msg, msgLen); #if !defined(RADIOLIB_STATIC_ONLY) delete[] msg; #endif - // turn transmitter off - phyLayer->standby(); + return(RADIOLIB_ERR_NONE); +} +#if !defined(RADIOLIB_STATIC_ONLY) +int16_t PagerClient::transmitMulti(PagerMessage messages[], size_t num_messages) { + size_t total_words = 0; + for (int i = 0; i < num_messages; i++) { + if (messages[i].validate() == RADIOLIB_ERR_NONE) { + total_words += messages[i].getNumBatches() * RADIOLIB_PAGER_BATCH_LEN; + } else { + uint16_t error = messages[i].validate(); + return(error); + } + } + uint32_t* buffer = new uint32_t[total_words]; + + int pos = 0; + for (int i = 0; i < num_messages; i++) { + pos += encodeMessage(buffer+pos, messages[i]); + } + + transmitBuffer(buffer, total_words); + + delete[] buffer; return(RADIOLIB_ERR_NONE); } +#endif #if !defined(RADIOLIB_EXCLUDE_DIRECT_RECEIVE) int16_t PagerClient::startReceive(uint32_t pin, uint32_t addr, uint32_t mask) { diff --git a/src/protocols/Pager/Pager.h b/src/protocols/Pager/Pager.h index ca84f7331..d2ae32000 100644 --- a/src/protocols/Pager/Pager.h +++ b/src/protocols/Pager/Pager.h @@ -50,11 +50,69 @@ #define RADIOLIB_PAGER_FUNC_BITS_TONE (0b01) #define RADIOLIB_PAGER_FUNC_BITS_ACTIVATION (0b10) #define RADIOLIB_PAGER_FUNC_BITS_ALPHA (0b11) -#define RADIOLIB_PAGER_FUNC_AUTO 0xFF // the maximum allowed address (2^22 - 1) #define RADIOLIB_PAGER_ADDRESS_MAX (2097151) +/*! + \struct PagerMessage_t + \brief Structure to save one message. +*/ +class PagerMessage { + public: + /*! + \brief Default constructor. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved. + \param encoding Encoding to be used (BCD or ASCII). Defaults to RADIOLIB_PAGER_BCD. + \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding + */ + explicit PagerMessage(uint32_t address, uint8_t function, uint8_t* data, size_t data_len, uint8_t encoding); + explicit PagerMessage(uint32_t address, uint8_t* data, size_t data_len, uint8_t encoding); + explicit PagerMessage(uint32_t address, const char* string, uint8_t encoding); + PagerMessage() {}; + + uint32_t getAddr_h(); + uint32_t getAddr_l(); + uint8_t getAddrFrameNr(); + uint8_t getFirstDataFrameNr(); + uint8_t getSymbolLength(); + uint8_t getFunction() { return function; }; + uint8_t* getData() { return data; }; + size_t getDataLen() { return data_len; }; + uint8_t getEncoding() { return encoding; }; + size_t getNumDataBlocks(); + size_t getNumBatches(); + uint16_t validate(); + + private: + /*! + \brief Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved. + */ + uint32_t address; + + /*! + \brief function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding + */ + uint8_t function; + + /*! + \brief Binary data that will be transmitted. + */ + uint8_t* data; + + /*! + \brief Length of binary data to transmit (in bytes). + */ + size_t data_len; + + /*! + \brief Encoding to be used (BCD or ASCII). Defaults to RADIOLIB_PAGER_BCD. + */ + uint8_t encoding; +}; + /*! \class PagerClient \brief Client for Pager communication. @@ -95,7 +153,8 @@ class PagerClient { \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding \returns \ref status_codes */ - int16_t transmit(String& str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD, uint8_t function = RADIOLIB_PAGER_FUNC_AUTO); + int16_t transmit(String& str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD); + int16_t transmit(String& str, uint32_t addr, uint8_t encoding, uint8_t function); #endif /*! @@ -106,7 +165,8 @@ class PagerClient { \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding \returns \ref status_codes */ - int16_t transmit(const char* str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD, uint8_t function = RADIOLIB_PAGER_FUNC_AUTO); + int16_t transmit(const char* str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD); + int16_t transmit(const char* str, uint32_t addr, uint8_t encoding, uint8_t function); /*! \brief Binary transmit method. Will transmit arbitrary binary data. @@ -117,7 +177,23 @@ class PagerClient { \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding \returns \ref status_codes */ - int16_t transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD, uint8_t function = RADIOLIB_PAGER_FUNC_AUTO); + int16_t transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD); + int16_t transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding, uint8_t function); + + /*! + \brief Message transmit method. + \param message Message object that will be transmitted. + \returns \ref status_codes + */ + int16_t transmit(PagerMessage &message); + + int16_t transmitBuffer(uint32_t* buf, size_t num_words); + + #if !defined(RADIOLIB_STATIC_ONLY) + int16_t transmitMulti(PagerMessage messages[], size_t num_messages); + #endif + + int16_t encodeMessage(uint32_t* buf, PagerMessage &message); #if !defined(RADIOLIB_EXCLUDE_DIRECT_RECEIVE) /*!