[Pager] Implemented POCSAG (#7)

This commit is contained in:
jgromes 2022-10-01 14:56:37 +02:00
parent 2980d8dbf1
commit 1f75ee0cc1
8 changed files with 1186 additions and 0 deletions

View file

@ -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 <RadioLib.h>
// 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);
}
}
}

View file

@ -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 <RadioLib.h>
// 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);
}

View file

@ -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

View file

@ -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"

View file

@ -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)
/*!
\}
*/

View file

@ -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, "<tone>", 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<<i)) {
data[j1++]=1;
} else {
data[j1++]=0;
}
}
// reset the M(x)+r array elements
int32_t Mr[RADIOLIB_PAGER_BCH_N];
memset(Mr, 0x00, RADIOLIB_PAGER_BCH_N*sizeof(int32_t));
// copy the contents of data into Mr and add the zeros
memcpy(Mr, data, RADIOLIB_PAGER_BCH_K*sizeof(int32_t));
int32_t j = 0;
int32_t start = 0;
int32_t end = RADIOLIB_PAGER_BCH_N - RADIOLIB_PAGER_BCH_K;
while(end < RADIOLIB_PAGER_BCH_N) {
for(int32_t i = end; i > 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<<i);
}
}
return(iResult);
}

216
src/protocols/Pager/Pager.h Normal file
View file

@ -0,0 +1,216 @@
#if !defined(_RADIOLIB_PAGER_H) && !defined(RADIOLIB_EXCLUDE_PAGER)
#define _RADIOLIB_PAGER_H
#include "../../TypeDef.h"
#include "../PhysicalLayer/PhysicalLayer.h"
// frequency shift in Hz
#define RADIOLIB_PAGER_FREQ_SHIFT_HZ (4500)
// supported encoding schemes
#define RADIOLIB_PAGER_ASCII (0)
#define RADIOLIB_PAGER_BCD (1)
// preamble length in 32-bit code words
#define RADIOLIB_PAGER_PREAMBLE_LENGTH (18)
// protocol-specified code words
#define RADIOLIB_PAGER_PREAMBLE_CODE_WORD (0xAAAAAAAA)
#define RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD (0x7CD215D8)
#define RADIOLIB_PAGER_IDLE_CODE_WORD (0x7A89C197)
// code word type identification flags
#define RADIOLIB_PAGER_ADDRESS_CODE_WORD (0UL)
#define RADIOLIB_PAGER_MESSAGE_CODE_WORD (1UL)
// length of code word in bits
#define RADIOLIB_PAGER_CODE_WORD_LEN (32)
// number of message bits in a single code block
#define RADIOLIB_PAGER_ADDRESS_POS (13)
#define RADIOLIB_PAGER_FUNC_BITS_POS (11)
#define RADIOLIB_PAGER_MESSAGE_BITS_LENGTH (20)
#define RADIOLIB_PAGER_MESSAGE_END_POS (11)
// number of code words in a batch
#define RADIOLIB_PAGER_BATCH_LEN (16)
// mask for address bits in a single code word
#define RADIOLIB_PAGER_ADDRESS_BITS_MASK (0x7FFFE000UL)
// mask for function bits in a single code word
#define RADIOLIB_PAGER_FUNCTION_BITS_MASK (0x00001800UL)
// mask for BCH bits in a single code word
#define RADIOLIB_PAGER_BCH_BITS_MASK (0x000007FFUL)
// message type functional bits
#define RADIOLIB_PAGER_FUNC_BITS_NUMERIC (0b00UL << RADIOLIB_PAGER_FUNC_BITS_POS)
#define RADIOLIB_PAGER_FUNC_BITS_TONE (0b01UL << RADIOLIB_PAGER_FUNC_BITS_POS)
#define RADIOLIB_PAGER_FUNC_BITS_ALPHA (0b11UL << RADIOLIB_PAGER_FUNC_BITS_POS)
// the maximum allowed address (2^22 - 1)
#define RADIOLIB_PAGER_ADDRESS_MAX (2097151)
// BCH(31, 21) code constants
#define RADIOLIB_PAGER_BCH_M (5)
#define RADIOLIB_PAGER_BCH_N (31)
#define RADIOLIB_PAGER_BCH_K (21)
#define RADIOLIB_PAGER_BCH_D (5)
// BCH(31, 21) primitive polynomial x^5 + x^2 + 1
#define RADIOLIB_PAGER_BCH_PRIMITIVE_POLY (0x25)
/*!
\class PagerClient
\brief Client for Pager communication.
*/
class PagerClient {
public:
/*!
\brief Default constructor.
\param phy Pointer to the wireless module providing PhysicalLayer communication.
*/
explicit PagerClient(PhysicalLayer* phy);
// basic methods
/*!
\brief Initialization method.
\param base Base (center) frequency to be used in MHz.
\param speed Bit rate to use in bps. Common POCSAG decoders can receive 512, 1200 and 2400 bps.
\returns \ref status_codes
*/
int16_t begin(float base, uint16_t speed);
/*!
\brief Method to send a tone-only alert to a destination pager.
\param addr Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved.
\returns \ref status_codes
*/
int16_t sendTone(uint32_t addr);
/*!
\brief Arduino String transmit method.
\param str Address of Arduino string that will be transmitted.
\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.
\returns \ref status_codes
*/
int16_t transmit(String& str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD);
/*!
\brief C-string transmit method.
\param str C-string that will be transmitted.
\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.
\returns \ref status_codes
*/
int16_t transmit(const char* str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD);
/*!
\brief Binary transmit method. Will transmit arbitrary binary data.
\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.
\returns \ref status_codes
*/
int16_t transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD);
/*!
\brief Start reception of POCSAG packets.
\param pin Pin to receive digital data on (e.g., DIO2 for SX127x).
\param addr Address of this "pager". Allowed values are 0 to 2097151 - values above 2000000 are reserved.
\param mask Address filter mask - set individual bits to enable or disable match on that bit of the address.¨Set to 0xFFFFF (all bits checked) by default.
\returns \ref status_codes
*/
int16_t startReceive(RADIOLIB_PIN_TYPE pin, uint32_t addr, uint32_t mask = 0xFFFFF);
/*!
\brief Get the number of POCSAG batches available in buffer. Limited by the size of direct mode buffer!
\returns Number of available batches.
*/
size_t available();
/*!
\brief Reads data that was received after calling startReceive method.
\param str Address of Arduino String to save the received data.
\param len Expected number of characters in the message. When set to 0, the message length will be retreived automatically.
When more bytes than received are requested, only the number of bytes requested will be returned.
\returns \ref status_codes
*/
int16_t readData(String& str, size_t len = 0);
/*!
\brief Reads data that was received after calling startReceive method.
\param data Pointer to array to save the received message.
\param len Pointer to variable holding the number of bytes that will be read. When set to 0, the packet length will be retreived automatically.
When more bytes than received are requested, only the number of bytes requested will be returned.
Upon completion, the number of bytes received will be written to this variable.
\returns \ref status_codes
*/
int16_t readData(uint8_t* data, size_t* len);
#if !defined(RADIOLIB_GODMODE)
private:
#endif
PhysicalLayer* _phy;
float _base;
float _speed;
uint32_t _baseRaw;
uint16_t _shift;
uint16_t _bitDuration;
uint32_t _readBatchPos;
uint32_t _filterAddr;
uint32_t _filterMask;
// BCH encoder
int32_t _bchAlphaTo[RADIOLIB_PAGER_BCH_N + 1];
int32_t _bchIndexOf[RADIOLIB_PAGER_BCH_N + 1];
int32_t _bchG[RADIOLIB_PAGER_BCH_N - RADIOLIB_PAGER_BCH_K + 1];
void write(uint32_t* data, size_t len);
void write(uint32_t codeWord);
uint32_t read();
uint8_t encodeBCD(char c);
char decodeBCD(uint8_t b);
void encoderInit();
uint32_t encodeBCH(uint32_t data);
};
#endif

View file

@ -190,6 +190,24 @@ class PhysicalLayer {
// configuration methods
/*!
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 525.0 MHz.
\param freq Carrier frequency to be set in MHz.
\returns \ref status_codes
*/
virtual int16_t setFrequency(float freq) = 0;
/*!
\brief Sets FSK bit rate. Allowed values range from 1.2 to 300 kbps. Only available in FSK mode.
\param br Bit rate to be set (in kbps).
\returns \ref status_codes
*/
virtual int16_t setBitRate(float br) = 0;
/*!
\brief Sets FSK frequency deviation from carrier frequency. Allowed values depend on bit rate setting and must be lower than 200 kHz.
Only available in FSK mode. Must be implemented in module class.
@ -353,6 +371,7 @@ class PhysicalLayer {
friend class SSTVClient;
friend class AX25Client;
friend class FSK4Client;
friend class PagerClient;
};
#endif