[LoRaWAN] Implement SX127x support, fix MAC uplinking, support clock drift

This commit is contained in:
StevenCellist 2023-11-01 16:51:24 +01:00
parent e7b2b27dd5
commit f92e0e59d4
7 changed files with 146 additions and 77 deletions

View file

@ -58,19 +58,35 @@ void inline ArduinoHal::detachInterrupt(uint32_t interruptNum) {
} }
void inline ArduinoHal::delay(unsigned long ms) { void inline ArduinoHal::delay(unsigned long ms) {
#if !defined(RADIOLIB_CLOCK_DRIFT_MS)
::delay(ms); ::delay(ms);
#else
::delay(ms * (1000 - RADIOLIB_CLOCK_DRIFT_MS) / 1000);
#endif
} }
void inline ArduinoHal::delayMicroseconds(unsigned long us) { void inline ArduinoHal::delayMicroseconds(unsigned long us) {
#if !defined(RADIOLIB_CLOCK_DRIFT_MS)
::delayMicroseconds(us); ::delayMicroseconds(us);
#else
::delayMicroseconds(us * (1000 - RADIOLIB_CLOCK_DRIFT_MS) / 1000);
#endif
} }
unsigned long inline ArduinoHal::millis() { unsigned long inline ArduinoHal::millis() {
#if !defined(RADIOLIB_CLOCK_DRIFT_MS)
return(::millis()); return(::millis());
#else
return(::millis() * (1000 - RADIOLIB_CLOCK_DRIFT_MS) / 1000);
#endif
} }
unsigned long inline ArduinoHal::micros() { unsigned long inline ArduinoHal::micros() {
#if !defined(RADIOLIB_CLOCK_DRIFT_MS)
return(::micros()); return(::micros());
#else
return(::micros() * (1000 - RADIOLIB_CLOCK_DRIFT_MS) / 1000);
#endif
} }
long inline ArduinoHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) { long inline ArduinoHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) {

View file

@ -446,6 +446,18 @@
#define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x0180) #define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x0180)
#endif #endif
/*
* Uncomment on boards whose clock runs too slow or too fast
* Set the value according to the following scheme:
* Enable timestamps on your terminal
* Print something to terminal, wait 1000 milliseconds, print something again
* If the difference is e.g. 1014 milliseconds between the prints, set this value to 14
* Or, for more accuracy, wait for 100,000 milliseconds and divide the total drift by 100
*/
#if !defined(RADIOLIB_CLOCK_DRIFT_MS)
//#define RADIOLIB_CLOCK_DRIFT_MS (0)
#endif
// This only compiles on STM32 boards with SUBGHZ module, but also // This only compiles on STM32 boards with SUBGHZ module, but also
// include when generating docs // include when generating docs
#if (!defined(ARDUINO_ARCH_STM32) || !defined(SUBGHZSPI_BASE)) && !defined(DOXYGEN) #if (!defined(ARDUINO_ARCH_STM32) || !defined(SUBGHZSPI_BASE)) && !defined(DOXYGEN)

View file

@ -9,7 +9,7 @@
// list of persistent parameters // list of persistent parameters
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID (0) // this is NOT the LoRaWAN version, but version of this table #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID (0) // this is NOT the LoRaWAN version, but version of this table
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (1) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (1)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION (2) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID (2)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID (3) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID (3)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID (4) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID (4)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID (5) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID (5)

View file

@ -405,6 +405,13 @@ int16_t SX127x::startReceive(uint8_t len, uint8_t mode) {
state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_ADDR_PTR, RADIOLIB_SX127X_FIFO_RX_BASE_ADDR_MAX); state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_ADDR_PTR, RADIOLIB_SX127X_FIFO_RX_BASE_ADDR_MAX);
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// timeout is only used in RxSingle, so when a packet length is defined, force mode to RxSingle
// and set the timeout value to the expected number of symbols (usually preamble + header)
if(len > 0) {
mode = RADIOLIB_SX127X_RXSINGLE;
state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYMB_TIMEOUT_LSB, len);
}
} else if(modem == RADIOLIB_SX127X_FSK_OOK) { } else if(modem == RADIOLIB_SX127X_FSK_OOK) {
// set DIO pin mapping // set DIO pin mapping
state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_PACK_PAYLOAD_READY, 7, 6); state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_PACK_PAYLOAD_READY, 7, 6);
@ -1237,6 +1244,28 @@ uint32_t SX127x::getTimeOnAir(size_t len) {
} }
uint32_t SX127x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) {
(void)numSymbols; // not used for these modules
// numSymbols += (109 / 4) + 1;
float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth;
numSymbols = timeoutUs / symbolLength + 1;
return(numSymbols);
}
int16_t SX127x::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) {
irqFlags = RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DONE;
irqMask = RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DONE;
irqFlags &= RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_TIMEOUT;
irqMask &= RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_TIMEOUT;
return(RADIOLIB_ERR_NONE);
}
bool SX127x::isRxTimeout() {
uint16_t irq = getIRQFlags();
bool rxTimedOut = irq & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT;
return(rxTimedOut);
}
int16_t SX127x::setCrcFiltering(bool enable) { int16_t SX127x::setCrcFiltering(bool enable) {
this->crcOn = enable; this->crcOn = enable;

View file

@ -1039,6 +1039,28 @@ class SX127x: public PhysicalLayer {
*/ */
uint32_t getTimeOnAir(size_t len) override; uint32_t getTimeOnAir(size_t len) override;
/*!
\brief Calculate the timeout value for this specific module / series based on number of symbols or time
\param numSymbols Number of payload symbols to listen for
\param timeoutUs Timeout in microseconds to listen for
\returns Timeout value in a unit that is specific for the used module
*/
uint32_t calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs);
/*!
\brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks
\param irqFlags The flags for which IRQs must be triggered
\param irqMask Mask indicating which IRQ triggers a DIO
\returns \ref status_codes
*/
int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask);
/*!
\brief Check whether the IRQ bit for RxTimeout is set
\returns \ref RxTimeout IRQ is set
*/
bool isRxTimeout();
/*! /*!
\brief Enable CRC filtering and generation. \brief Enable CRC filtering and generation.
\param enable Set or unset CRC filtering and generation. \param enable Set or unset CRC filtering and generation.

View file

@ -38,7 +38,7 @@ int16_t LoRaWANNode::restore() {
// check the magic value // check the magic value
Module* mod = this->phyLayer->getMod(); Module* mod = this->phyLayer->getMod();
uint16_t nvm_table_version = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID); uint8_t nvm_table_version = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID);
// if (RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION > nvm_table_version) { // if (RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION > nvm_table_version) {
// // set default values for variables that are new or something // // set default values for variables that are new or something
// } // }
@ -58,7 +58,7 @@ int16_t LoRaWANNode::restore() {
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
// get session parameters // get session parameters
this->rev = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION); this->rev = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID);
RADIOLIB_DEBUG_PRINTLN("LoRaWAN session: v1.%d", this->rev); RADIOLIB_DEBUG_PRINTLN("LoRaWAN session: v1.%d", this->rev);
this->devNonce = mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); this->devNonce = mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID);
this->joinNonce = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); this->joinNonce = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID);
@ -217,6 +217,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
// send it // send it
state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN);
this->rxDelayStart = mod->hal->millis(); this->rxDelayStart = mod->hal->millis();
RADIOLIB_DEBUG_PRINTLN("Join-request sent <-- Rx Delay start");
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// configure Rx delay for join-accept message - these are re-configured once a valid join-request is received // configure Rx delay for join-accept message - these are re-configured once a valid join-request is received
@ -356,8 +357,8 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
// enqueue the RekeyInd MAC command to be sent in the next uplink // enqueue the RekeyInd MAC command to be sent in the next uplink
LoRaWANMacCommand_t cmd = { LoRaWANMacCommand_t cmd = {
.cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY, .cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY,
.len = sizeof(uint8_t),
.payload = { this->rev }, .payload = { this->rev },
.len = sizeof(uint8_t),
.repeat = 0x01 << RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP, .repeat = 0x01 << RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP,
}; };
state = pushMacCommand(&cmd, &this->commandsUp); state = pushMacCommand(&cmd, &this->commandsUp);
@ -468,8 +469,8 @@ int16_t LoRaWANNode::saveSession() {
Module* mod = this->phyLayer->getMod(); Module* mod = this->phyLayer->getMod();
// store session configuration (MAC commands) // store session configuration (MAC commands)
if(mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION) != this->rev) if(mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID) != this->rev)
mod->hal->setPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION, this->rev); mod->hal->setPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID, this->rev);
if(mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID) != this->devNonce) if(mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID) != this->devNonce)
mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, this->devNonce); mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, this->devNonce);
@ -629,7 +630,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
Module* mod = this->phyLayer->getMod(); Module* mod = this->phyLayer->getMod();
// check if sufficient time has elapsed since the last uplink // check if sufficient time has elapsed since the last uplink
if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) { if(mod->hal->millis() - this->rxDelayStart < this->rxDelays[1]) {
// not enough time elapsed since the last uplink, we may still be in an RX window // not enough time elapsed since the last uplink, we may still be in an RX window
return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); return(RADIOLIB_ERR_UPLINK_UNAVAILABLE);
} }
@ -687,12 +688,14 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// failed to increase Tx power, so try to decrease the datarate // failed to increase Tx power, so try to decrease the datarate
if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMin) { if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMin) {
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]--; this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]--;
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]--;
} else { } else {
// failed to decrease datarate, so enable all available channels // failed to decrease datarate, so enable all available channels
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true;
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled = true;
} }
} }
} }
@ -747,24 +750,30 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// check if we have some MAC commands to append // check if we have some MAC commands to append
if(foptsLen > 0) { if(foptsLen > 0) {
uint8_t foptsNum = this->commandsUp.numCommands;
uint8_t foptsBuff[foptsBufSize]; uint8_t foptsBuff[foptsBufSize];
size_t idx = 0; uint8_t* foptsPtr = foptsBuff;
for (size_t i = 0; i < foptsNum; i++) {
LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, }; // append all MAC replies into fopts buffer
popMacCommand(&cmd, &this->commandsUp, i); size_t i = 0;
if (cmd.cid == 0) { for (; i < this->commandsUp.numCommands; i++) {
LoRaWANMacCommand_t cmd = this->commandsUp.commands[i];
memcpy(foptsPtr, &cmd, 1 + cmd.len);
foptsPtr += cmd.len + 1;
}
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsLen);
// pop the commands from back to front
for (; i >= 0; i--) {
if(this->commandsUp.commands[i].repeat > 0) {
this->commandsUp.commands[i].repeat--;
} else {
deleteMacCommand(this->commandsUp.commands[i].cid, &this->commandsUp);
}
if(i == 0) {
break; break;
} }
foptsBuff[idx] = cmd.cid;
for(size_t i = 0; i < cmd.len; i++) {
foptsBuff[idx + 1 + i] = cmd.payload[i];
} }
idx += cmd.len + 1;
}
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize);
uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen); uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen; uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen;
@ -860,22 +869,22 @@ int16_t LoRaWANNode::downlinkCommon() {
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
} }
// 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
uint32_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000;
uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(0, timeoutHost);
// create the masks that are required for receiving downlinks // create the masks that are required for receiving downlinks
uint16_t irqFlags; uint16_t irqFlags;
uint16_t irqMask; uint16_t irqMask;
this->phyLayer->irqRxDoneRxTimeout(irqFlags, irqMask); this->phyLayer->irqRxDoneRxTimeout(irqFlags, irqMask);
downlinkAction = false;
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction); this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction);
// perform listening in the two Rx windows // perform listening in the two Rx windows
for(uint8_t i = 0; i < 2; i++) { for(uint8_t i = 0; i < 2; i++) {
downlinkAction = false;
// 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
uint32_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000;
uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(0, timeoutHost);
// wait for the start of the Rx window // wait for the start of the Rx window
// the waiting duration is shortened a bit to cover any possible timing errors // the waiting duration is shortened a bit to cover any possible timing errors
@ -886,15 +895,15 @@ int16_t LoRaWANNode::downlinkCommon() {
mod->hal->delay(waitLen); mod->hal->delay(waitLen);
// open Rx window by starting receive with specified timeout // open Rx window by starting receive with specified timeout
state = this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0); state = this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, timeoutMod);
RADIOLIB_DEBUG_PRINTLN("Opening Rx%d window (%d us timeout)... <-- Rx Delay end ", i+1, timeoutHost); RADIOLIB_DEBUG_PRINTLN("Opening Rx%d window (%d us timeout)... <-- Rx Delay end ", i+1, timeoutHost);
// wait for the timeout to complete (and a small additional delay) // wait for the timeout to complete (and a small additional delay)
mod->hal->delay(timeoutHost / 1000 + scanGuard / 2); mod->hal->delay(timeoutHost / 1000 + scanGuard / 2);
RADIOLIB_DEBUG_PRINTLN("closing"); RADIOLIB_DEBUG_PRINTLN("closing");
// check if the DIO has fired indicating a timeout; no timeout means a receive is ongoing // check if the IRQ bit for Rx Timeout is set
if(!downlinkAction) { if(!this->phyLayer->isRxTimeout()) {
break; break;
} else if(i == 0) { } else if(i == 0) {
@ -907,15 +916,12 @@ int16_t LoRaWANNode::downlinkCommon() {
findDataRate(this->rx2.drMax, &dataRate); findDataRate(this->rx2.drMax, &dataRate);
state = this->phyLayer->setDataRate(dataRate); state = this->phyLayer->setDataRate(dataRate);
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000;
timeoutMod = this->phyLayer->calculateRxTimeout(0, timeoutHost);
downlinkAction = false;
} }
} }
// if we got here due to a timeout, stop ongoing activities // if we got here due to a timeout, stop ongoing activities
if(downlinkAction) { if(this->phyLayer->isRxTimeout()) {
this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle?
if(!this->FSK) { if(!this->FSK) {
this->phyLayer->invertIQ(false); this->phyLayer->invertIQ(false);
@ -1113,8 +1119,8 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
while(remLen > 0) { while(remLen > 0) {
LoRaWANMacCommand_t cmd = { LoRaWANMacCommand_t cmd = {
.cid = *foptsPtr, .cid = *foptsPtr,
.len = (uint8_t)(remLen - 1),
.payload = { 0 }, .payload = { 0 },
.len = (uint8_t)(remLen - 1),
.repeat = 0, .repeat = 0,
}; };
memcpy(cmd.payload, foptsPtr + 1, cmd.len); memcpy(cmd.payload, foptsPtr + 1, cmd.len);
@ -1130,24 +1136,30 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// if FOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink // if FOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink
if(this->commandsUp.len > 15) { if(this->commandsUp.len > 15) {
uint8_t foptsNum = this->commandsUp.numCommands;
size_t foptsBufSize = this->commandsUp.len; size_t foptsBufSize = this->commandsUp.len;
uint8_t foptsBuff[foptsBufSize]; uint8_t foptsBuff[foptsBufSize];
size_t idx = 0; uint8_t* foptsPtr = foptsBuff;
for(size_t i = 0; i < foptsNum; i++) { // append all MAC replies into fopts buffer
LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, }; size_t i = 0;
popMacCommand(&cmd, &this->commandsUp, i); for (; i < this->commandsUp.numCommands; i++) {
if(cmd.cid == 0) { LoRaWANMacCommand_t cmd = this->commandsUp.commands[i];
memcpy(foptsPtr, &cmd, 1 + cmd.len);
foptsPtr += cmd.len + 1;
}
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize);
// pop the commands from back to front
for (; i >= 0; i--) {
if(this->commandsUp.commands[i].repeat > 0) {
this->commandsUp.commands[i].repeat--;
} else {
deleteMacCommand(this->commandsUp.commands[i].cid, &this->commandsUp);
}
if(i == 0) {
break; break;
} }
foptsBuff[idx] = cmd.cid;
for(size_t i = 1; i < cmd.len; i++) {
foptsBuff[idx + i] = cmd.payload[i];
} }
idx += cmd.len + 1;
}
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize);
this->isMACPayload = true; this->isMACPayload = true;
this->uplink(foptsBuff, foptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); this->uplink(foptsBuff, foptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
@ -1484,24 +1496,6 @@ int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQ
return(RADIOLIB_ERR_NONE); return(RADIOLIB_ERR_NONE);
} }
int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, size_t index) {
if(queue->numCommands == 0) {
return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY);
}
if(cmd) {
memcpy(cmd, &queue->commands[index], sizeof(LoRaWANMacCommand_t));
}
if(queue->commands[index].repeat > 0) {
queue->commands[index].repeat--;
} else {
deleteMacCommand(queue->commands[index].cid, queue);
}
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue) { int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue) {
if(queue->numCommands == 0) { if(queue->numCommands == 0) {
return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY); return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY);
@ -1512,7 +1506,7 @@ int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* que
queue->len -= (1 + queue->commands[index].len); // 1 byte for command ID, len for payload queue->len -= (1 + queue->commands[index].len); // 1 byte for command ID, len for payload
// move all subsequent commands one forward in the queue // move all subsequent commands one forward in the queue
if(index < RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1) { if(index < RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1) {
memmove(&queue->commands[index], &queue->commands[index + 1], (RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - index) * sizeof(LoRaWANMacCommand_t)); memmove(&queue->commands[index], &queue->commands[index + 1], (RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - index - 1) * sizeof(LoRaWANMacCommand_t));
} }
// set the latest element to all 0 // set the latest element to all 0
memset(&queue->commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1], 0x00, sizeof(LoRaWANMacCommand_t)); memset(&queue->commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1], 0x00, sizeof(LoRaWANMacCommand_t));
@ -1526,7 +1520,6 @@ int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* que
size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len); RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len);
Module* mod = this->phyLayer->getMod();
if(cmd->cid >= RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY) { if(cmd->cid >= RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY) {
// TODO call user-provided callback for proprietary MAC commands? // TODO call user-provided callback for proprietary MAC commands?

View file

@ -295,12 +295,12 @@ struct LoRaWANMacCommand_t {
/*! \brief The command ID */ /*! \brief The command ID */
uint8_t cid; uint8_t cid;
/*! \brief Length of the payload */
uint8_t len;
/*! \brief Payload buffer (5 bytes is the longest possible) */ /*! \brief Payload buffer (5 bytes is the longest possible) */
uint8_t payload[5]; uint8_t payload[5];
/*! \brief Length of the payload */
uint8_t len;
/*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */ /*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */
uint8_t repeat; uint8_t repeat;
}; };
@ -477,12 +477,12 @@ class LoRaWANNode {
LoRaWANMacCommandQueue_t commandsUp = { LoRaWANMacCommandQueue_t commandsUp = {
.numCommands = 0, .numCommands = 0,
.len = 0, .len = 0,
.commands = { { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, } }, .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } },
}; };
LoRaWANMacCommandQueue_t commandsDown = { LoRaWANMacCommandQueue_t commandsDown = {
.numCommands = 0, .numCommands = 0,
.len = 0, .len = 0,
.commands = { { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, } }, .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } },
}; };
// the following is either provided by the network server (OTAA) // the following is either provided by the network server (OTAA)
@ -571,9 +571,6 @@ class LoRaWANNode {
// push MAC command to queue, done by copy // push MAC command to queue, done by copy
int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue); int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue);
// pop MAC command from queue, done by copy unless CMD is NULL
int16_t popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, size_t index);
// delete a specific MAC command from queue, indicated by the command ID // delete a specific MAC command from queue, indicated by the command ID
int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue); int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue);