[LoRaWAN] Change Rx windows from CAD to RxSingle

This commit is contained in:
StevenCellist 2023-10-23 23:14:17 +02:00
parent dd79213461
commit c8bc157090
5 changed files with 120 additions and 112 deletions

View file

@ -1436,6 +1436,36 @@ uint32_t SX126x::getTimeOnAir(size_t len) {
}
}
// Table below defines the size of one symbol as
// symtime = 256us * 2^T(sf,bw)
// 256us is called one symunit.
// SF:
// BW: |__7___8___9__10__11__12
// 125kHz | 2 3 4 5 6 7
// 250kHz | 1 2 3 4 5 6
// 500kHz | 0 1 2 3 4 5
//
uint32_t SX126x::calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUs) {
uint8_t exponent = 0;
exponent += datarate->lora.spreadingFactor - 7;
switch((int)datarate->lora.bandwidth) {
case 125:
exponent += 2;
break;
case 250:
exponent += 1;
break;
default: // includes 500 kHz
exponent += 0;
break;
}
timeoutUs = (uint32_t)numSymbols * 256 * (0x01 << exponent) + offsetUs;
uint32_t timeout = timeoutUs / 15.625;
return(timeout);
}
int16_t SX126x::implicitHeader(size_t len) {
return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, len));
}

View file

@ -56,7 +56,7 @@
#define RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS 0x8C
#define RADIOLIB_SX126X_CMD_SET_CAD_PARAMS 0x88
#define RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS 0x8F
#define RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT 0x0A
#define RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT 0xA0
// status commands
#define RADIOLIB_SX126X_CMD_GET_STATUS 0xC0
@ -219,7 +219,7 @@
#define RADIOLIB_SX126X_IRQ_HEADER_ERR 0b0000000000100000 // 5 5 LoRa header CRC error
#define RADIOLIB_SX126X_IRQ_HEADER_VALID 0b0000000000010000 // 4 4 valid LoRa header received
#define RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID 0b0000000000001000 // 3 3 valid sync word detected
#define RADIOLIB_SX126X_IRQ_RADIOLIB_PREAMBLE_DETECTED 0b0000000000000100 // 2 2 preamble detected
#define RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED 0b0000000000000100 // 2 2 preamble detected
#define RADIOLIB_SX126X_IRQ_RX_DONE 0b0000000000000010 // 1 1 packet received
#define RADIOLIB_SX126X_IRQ_TX_DONE 0b0000000000000001 // 0 0 packet transmission completed
#define RADIOLIB_SX126X_IRQ_RX_DEFAULT 0b0000001001100010 // 14 0 default for Rx (RX_DONE, TIMEOUT, CRC_ERR and HEADER_ERR)
@ -949,6 +949,16 @@ class SX126x: public PhysicalLayer {
*/
uint32_t getTimeOnAir(size_t len) override;
/*!
\brief Get the Rx timeout required to listen to a preamble of a certain number of symbols
\param numSymbols Number of symbols to listen for
\param datarate The datarate for which to calculate the timeout
\param offsetUs Additional offset in microseconds to allow increasing the timeout
\param timeoutUs Returns the timeout in microseconds for the host to sleep
\returns Timeout value in a unit that is specific for the used module
*/
uint32_t calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUss);
/*!
\brief Set implicit header mode for future reception/transmission.
\param len Payload length in bytes.

View file

@ -20,14 +20,14 @@ static void LoRaWANNodeOnDownlink(void) {
}
// flag to indicate whether channel scan operation is complete
static volatile bool scanFlag = false;
static volatile bool downlinkTimeout = false;
// interrupt service routine to handle downlinks automatically
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#endif
static void LoRaWANNodeOnChannelScan(void) {
scanFlag = true;
downlinkTimeout = true;
}
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
@ -629,7 +629,7 @@ int16_t LoRaWANNode::downlink(String& str) {
int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// check if there are any upcoming Rx windows
Module* mod = this->phyLayer->getMod();
const uint32_t scanGuard = 10;
const uint32_t scanGuard = 50;
if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) {
// time since last Tx is greater than RX2 delay + some guard period
// we have nothing to downlink
@ -646,18 +646,22 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
RADIOLIB_ASSERT(state);
}
// calculate the channel scanning timeout
// according to the spec, this must be at least enough time to effectively detect a preamble
uint32_t scanTimeout = this->phyLayer->getTimeOnAir(0)/1000;
// set up everything for channel scan
downlinkTimeout = false;
downlinkReceived = false;
scanFlag = false;
bool packetDetected = false;
this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan);
// calculate the Rx timeout
// according to the spec, this must be at least enough time to effectively detect a preamble
// but pad it a bit on both sides (start and end) to make sure it is wide enough
DataRate_t dataRate;
findDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK], &dataRate);
uint32_t timeoutHost = 0;
uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(8, &dataRate, 2*scanGuard*1000, timeoutHost);
RADIOLIB_DEBUG_PRINTLN("RxTimeout host: %d, module: %06x", timeoutHost, timeoutMod);
// perform listening in the two Rx windows
for(uint8_t i = 0; i < 2; i++) {
// wait for the start of the Rx window
// the waiting duration is shortened a bit to cover any possible timing errors
uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart);
@ -668,49 +672,27 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
mod->hal->delay(waitLen);
RADIOLIB_DEBUG_PRINTLN("Opening Rx%d window...", (i+1));
// wait until we get a preamble
uint32_t scanStart = mod->hal->millis();
while((mod->hal->millis() - scanStart) < (scanTimeout + scanGuard)) {
// check channel detection timeout
state = this->phyLayer->startChannelScan();
RADIOLIB_ASSERT(state);
// TODO somehow make this module-independent, currently configured for SX126x
uint16_t irqFlags = 0b0000000000000010; // RxDone
uint16_t irqMask = 0b0000000000000010;
irqFlags |= 0b0000001000000000; // RxTimeout
irqMask |= 0b0000001000000000;
// wait with some timeout, though it should not be hit
uint32_t cadStart = mod->hal->millis();
while(!scanFlag) {
mod->hal->yield();
if(mod->hal->millis() - cadStart >= 3000) {
// timed out, stop waiting
break;
}
}
// check the scan result
scanFlag = false;
state = this->phyLayer->getChannelScanResult();
if((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) {
packetDetected = true;
// channel scan is finished, swap the actions
this->phyLayer->clearChannelScanAction();
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
// start receiving
state = this->phyLayer->startReceive(0x00, 0b0000001001100010, 0b0000000000000010, 0); // RxSingle
RADIOLIB_ASSERT(state);
break;
}
uint32_t rxStart = mod->hal->millis();
this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0);
RADIOLIB_DEBUG_PRINT("Opened Rx%d window (%d us timeout)... ", i+1, timeoutHost);
while(!downlinkTimeout && ((mod->hal->millis() - rxStart) < (timeoutHost / 1000))) {
mod->hal->yield();
}
RADIOLIB_DEBUG_PRINTLN("closing");
// check if we have a packet
if(packetDetected) {
RADIOLIB_DEBUG_PRINTLN("Detected a packet...");
// check if we detected something of a packet
if(!downlinkTimeout) {
break;
} else if(i == 0) {
// nothing in the first window, configure for the second
this->phyLayer->standby();
state = this->phyLayer->setFrequency(this->rx2.freq);
RADIOLIB_ASSERT(state);
@ -718,12 +700,15 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
findDataRate(this->rx2.drMax, &dataRate);
state = this->phyLayer->setDataRate(dataRate);
RADIOLIB_ASSERT(state);
timeoutMod = this->phyLayer->calculateRxTimeout(8, &dataRate, 2*scanGuard*1000, timeoutHost);
downlinkTimeout = false;
}
}
this->phyLayer->clearChannelScanAction();
// check if we received a packet at all
if(!packetDetected) {
if(downlinkTimeout) {
this->phyLayer->standby();
if(!this->FSK) {
this->phyLayer->invertIQ(false);
@ -731,27 +716,14 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
return(RADIOLIB_ERR_RX_TIMEOUT);
}
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
// wait for reception with some timeout
uint32_t rxStart = mod->hal->millis();
while(!downlinkReceived) {
mod->hal->yield();
// 3 seconds is maximum airtime
if(mod->hal->millis() - rxStart >= 3000) {
// timed out
RADIOLIB_DEBUG_PRINTLN("Packet length: %d", this->phyLayer->getPacketLength());
this->phyLayer->standby();
if(!this->FSK) {
this->phyLayer->invertIQ(false);
}
// return(RADIOLIB_ERR_RX_TIMEOUT);
break;
}
}
// we have a message, clear actions, go to standby and reset the IQ inversion
downlinkReceived = false;
this->phyLayer->standby();
this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle?
this->phyLayer->clearPacketReceivedAction();
if(!this->FSK) {
state = this->phyLayer->invertIQ(false);
@ -760,6 +732,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// get the packet length
size_t downlinkMsgLen = this->phyLayer->getPacketLength();
RADIOLIB_DEBUG_PRINTLN("Downlink message length: %d", downlinkMsgLen);
// check the minimum required frame length
// an extra byte is subtracted because downlink frames may not have a port
@ -803,7 +776,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// TODO cache the ADR bit?
uint16_t fcnt16 = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt16);
uint32_t fcnt32 = fcnt16; // calculate possible rollover once decided if this is network downlink or application downlink
RADIOLIB_DEBUG_PRINTLN("downlinkMsg:");
RADIOLIB_DEBUG_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
@ -825,9 +797,12 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
fcntDownPrev = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID);
}
RADIOLIB_DEBUG_PRINTLN("fcnt: %d, fcntPrev: %d, isAppDownlink: %d", fcnt16, fcntDownPrev, (int)isAppDownlink);
// if this is not the first downlink...
// assume a 16-bit to 32-bit rollover if difference between counters in LSB is smaller than MAX_FCNT_GAP
// if that isn't the case and the received fcnt is smaller or equal to the last heard fcnt, then error
uint32_t fcnt32 = fcnt16;
if(fcntDownPrev > 0) {
if((fcnt16 <= fcntDownPrev) && ((0xFFFF - (uint16_t)fcntDownPrev + fcnt16) > RADIOLIB_LORAWAN_MAX_FCNT_GAP)) {
#if !defined(RADIOLIB_STATIC_ONLY)
@ -839,7 +814,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
return(RADIOLIB_ERR_N_FCNT_DOWN_INVALID);
}
} else if (fcnt16 <= fcntDownPrev) {
uint16_t msb = (fcntDownPrev >> 16) + 1; // assume a rollover
uint16_t msb = (fcntDownPrev >> 16) + 1; // assume a rollover
fcnt32 |= (msb << 16); // add back the MSB part
}
}
@ -923,7 +898,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize);
isMACPayload = true;
this->isMACPayload = true;
this->uplink(foptsBuff, foptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
}
@ -951,9 +926,9 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// there is payload, and so there should be a port too
// TODO pass the port?
*len = payLen - 1;
// TODO it COULD be the case that the assumed rollover is incorrect, then figure out a way to catch this and retry with just fcnt16
// TODO does the erratum hold here as well?
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], payLen - 1, this->appSKey, data, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
@ -1061,20 +1036,20 @@ int16_t LoRaWANNode::setupChannels(uint8_t* cfList) {
num++;
}
}
// if(cfList != nullptr) {
// for(uint8_t i = 0; i < 5; i++) {
// chnl.enabled = true;
// chnl.idx = num;
// uint32_t freq = LoRaWANNode::ntoh<uint32_t>(&cfList[3*i], 3);
// chnl.freq = (float)freq/10000.0;
// chnl.drMin = this->band->txFreqs[0].drMin; // drMin is equal for all channels
// chnl.drMax = this->band->txFreqs[0].drMax; // drMax is equal for all channels
// availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl;
// availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = chnl;
// RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
// num++;
// }
// }
if(cfList != nullptr) {
for(uint8_t i = 0; i < 5; i++) {
chnl.enabled = true;
chnl.idx = num;
uint32_t freq = LoRaWANNode::ntoh<uint32_t>(&cfList[3*i], 3);
chnl.freq = (float)freq/10000.0;
chnl.drMin = this->band->txFreqs[0].drMin; // drMin is equal for all channels
chnl.drMax = this->band->txFreqs[0].drMax; // drMax is equal for all channels
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl;
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = chnl;
RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
num++;
}
}
} else { // RADIOLIB_LORAWAN_CFLIST_TYPE_MASK
uint8_t chSpan = 0;
uint8_t chNum = 0;
@ -1301,15 +1276,6 @@ int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQ
}
memcpy(&queue->commands[queue->numCommands], cmd, sizeof(LoRaWANMacCommand_t));
/*RADIOLIB_DEBUG_PRINTLN("push MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
queue->commands[queue->numCommands - 1].cid,
queue->commands[queue->numCommands - 1].len,
queue->commands[queue->numCommands - 1].payload[0],
queue->commands[queue->numCommands - 1].payload[1],
queue->commands[queue->numCommands - 1].payload[2],
queue->commands[queue->numCommands - 1].payload[3],
queue->commands[queue->numCommands - 1].payload[4],
queue->commands[queue->numCommands - 1].repeat);*/
queue->numCommands++;
queue->len += 1 + cmd->len; // 1 byte for command ID, len bytes for payload
@ -1322,15 +1288,6 @@ int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQu
}
if(cmd) {
// RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
// queue->commands[index].cid,
// queue->commands[index].len,
// queue->commands[index].payload[0],
// queue->commands[index].payload[1],
// queue->commands[index].payload[2],
// queue->commands[index].payload[3],
// queue->commands[index].payload[4],
// queue->commands[index].repeat);
memcpy(cmd, &queue->commands[index], sizeof(LoRaWANMacCommand_t));
}
@ -1350,15 +1307,6 @@ int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* que
for(size_t index = 0; index < queue->numCommands; index++) {
if(queue->commands[index].cid == cid) {
// RADIOLIB_DEBUG_PRINTLN("delete MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
// queue->commands[index].cid,
// queue->commands[index].len,
// queue->commands[index].payload[0],
// queue->commands[index].payload[1],
// queue->commands[index].payload[2],
// queue->commands[index].payload[3],
// queue->commands[index].payload[4],
// queue->commands[index].repeat);
queue->len -= (1 + queue->commands[index].len); // 1 byte for command ID, len for payload
// move all subsequent commands one forward in the queue
if(index < RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1) {
@ -1412,7 +1360,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
uint16_t chMask = LoRaWANNode::ntoh<uint16_t>(&cmd->payload[1]);
uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4;
uint8_t nbTrans = cmd->payload[3] & 0x0F;
RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %02x, nbTrans = %d", dr, txPower, chMask, chMaskCntl, nbTrans);
RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%02x, chMaskCntl = %02x, nbTrans = %d", dr, txPower, chMask, chMaskCntl, nbTrans);
// apply the configuration
uint8_t drAck = 0;
@ -1447,17 +1395,18 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
this->nbTrans = nbTrans;
// TODO implement channel mask
uint8_t chMaskAck = 1;
(void)chMask;
(void)chMaskCntl;
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) {
for(uint8_t i = 0; i < 16; i++) {
// check if this channel ID should be enabled
RADIOLIB_DEBUG_PRINTLN("ADR channel %d: %d --> %d", i, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, (chMask >> i) & 0x01);
if(chMask & (1UL << i)) {
// if it should be enabled but is not currently defined, stop immediately
if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled == false) {
chMaskAck = 0;
break;
}
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true;
} else {
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = false;
}
@ -1732,6 +1681,7 @@ void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out
// on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext
size_t remLen = len;
for(size_t i = 0; i < numBlocks; i++) {
if(counter) {
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
}

View file

@ -293,6 +293,14 @@ uint32_t PhysicalLayer::getTimeOnAir(size_t len) {
(void)len;
return(0);
}
uint32_t PhysicalLayer::calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUs) {
(void)numSymbols;
(void)datarate;
(void)offsetUs;
(void)timeoutUs;
return(RADIOLIB_ERR_UNSUPPORTED);
}
int16_t PhysicalLayer::startChannelScan() {
return(RADIOLIB_ERR_UNSUPPORTED);

View file

@ -309,6 +309,16 @@ class PhysicalLayer {
\returns Expected time-on-air in microseconds.
*/
virtual uint32_t getTimeOnAir(size_t len);
/*!
\brief Get the Rx timeout required to listen to a preamble of a certain number of symbols
\param numSymbols Number of symbols to listen for
\param datarate The datarate for which to calculate the timeout
\param offsetUs Additional offset in microseconds to allow increasing the timeout
\param timeoutUs Returns the timeout in microseconds for the host to sleep
\returns Timeout value in a unit that is specific for the used module
*/
virtual uint32_t calculateRxTimeout(uint8_t numSymbols, DataRate_t* datarate, uint32_t offsetUs, uint32_t& timeoutUs);
/*!
\brief Interrupt-driven channel activity detection method. interrupt will be activated