Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Multiple POCSAG messages at once (fixes #743) #774

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 201 additions & 108 deletions src/protocols/Pager/Pager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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++) {

Check failure

Code scanning / CodeQL

Comparison of narrow type with wide type in loop condition

Comparison between [blockPos](1) of type uint8_t and [call to getNumDataBlocks](2) of wider type size_t.

// 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) {
Expand Down
Loading