diff --git a/examples/LoRaWAN/LoRaWAN_End_Device_Reference/LoRaWAN_End_Device_Reference.ino b/examples/LoRaWAN/LoRaWAN_End_Device_Reference/LoRaWAN_End_Device_Reference.ino index 52d5904f..2d8c84cf 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device_Reference/LoRaWAN_End_Device_Reference.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device_Reference/LoRaWAN_End_Device_Reference.ino @@ -129,6 +129,10 @@ void setup() { // set a fixed datarate node.setDatarate(5); + // in order to save the datarate persistent across reboot/deepsleep, use the following: + /* + node.setDatarate(5, true); + */ // enable CSMA // this tries to minimize packet loss by searching for a free channel diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 43fe0514..56fd5b63 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -151,7 +151,7 @@ int16_t LoRaWANNode::restore() { RADIOLIB_ASSERT(state); // get MAC state - LoRaWANMacCommand_t cmd; + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID), cmd.payload, cmd.len); @@ -260,7 +260,7 @@ int16_t LoRaWANNode::restoreChannels() { uint8_t bufferUp[numBytesUp] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), bufferUp, numBytesUp); - LoRaWANMacCommand_t cmd; + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { @@ -276,9 +276,9 @@ int16_t LoRaWANNode::restoreChannels() { mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID), bufferDn, numBytesDn); cmd.cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn; for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn; memcpy(cmd.payload, &bufferDn[i * cmd.len], cmd.len); if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) { // only execute if it is not all zeroes (void)execMacCommand(&cmd, false); @@ -290,7 +290,7 @@ int16_t LoRaWANNode::restoreChannels() { uint8_t buffer[numBytes] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), buffer, numBytes); - LoRaWANMacCommand_t cmd; + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; @@ -552,7 +552,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe } - LoRaWANMacCommand_t cmd; + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn; cmd.payload[0] = dlSettings & 0x7F; @@ -921,42 +921,54 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf adrAckReq = true; } // if we hit the Limit + Delay, try one of three, in order: - // set TxPower to max, set DR to min, enable all defined channels + // set TxPower to max, set DR to min, enable all default channels if ((this->fcntUp - this->adrFcnt) == (adrLimit + adrDelay)) { - - // if the TxPower field has some offset, remove it and switch to maximum power - if(this->txPowerCur > 0) { - this->txPowerCur = 0; - // set the maximum power supported by both the module and the band - state = this->setTxPower(this->txPowerMax); - RADIOLIB_ASSERT(state); - - } else { - // 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) { - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]--; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]--; - } else { - - // failed to decrease datarate, so enable all available channels - 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) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled = true; + uint8_t adrStage = 1; + while(adrStage != 0) { + switch(adrStage) { + case(1): { + // if the TxPower field has some offset, remove it and switch to maximum power + if(this->txPowerCur > 0) { + // set the maximum power supported by both the module and the band + state = this->setTxPower(this->txPowerMax, true); + if(state == RADIOLIB_ERR_NONE) { + this->txPowerCur = 0; + adrStage = 0; // successfully did some ADR stuff + } + } + if(adrStage == 1) { // if nothing succeeded, proceed to stage 2 + adrStage = 2; + } } - } + break; + case(2): { + // try to decrease the datarate + if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > 0) { + if(this->setDatarate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] - 1, true) == RADIOLIB_ERR_NONE) { + adrStage = 0; // successfully did some ADR stuff + } + } + if(adrStage == 2) { // if nothing succeeded, proceed to stage 3 + adrStage = 3; + } + } + break; + case(3): { + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->setupChannels(nullptr); // revert to default frequencies + } else { + // if a subband was selected by user, go back to its default state + // hopefully it'll help something, but probably not; at least we tried.. + if(this->selectedSubband >= 0) { + this->selectSubband(this->selectedSubband); + } + } + adrStage = 0; // nothing else to do, so end the cycle + } + break; } - } - LoRaWANMacCommand_t cmd; - cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - cmd.payload[0] = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4); - cmd.payload[0] |= 0; // default to max Tx Power - cmd.payload[3] |= (1 << 7); // set the RFU bit, which means that the channel mask gets ignored - (void)execMacCommand(&cmd); - // we tried something to improve the range, so increase the ADR frame counter by 'ADR delay' this->adrFcnt += adrDelay; } @@ -1452,7 +1464,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) RADIOLIB_DEBUG_PRINTLN("MAC response:"); for (int i = 0; i < this->commandsUp.numCommands; i++) { - RADIOLIB_DEBUG_HEXDUMP(&this->commandsUp[i].cid, sizeof(LoRaWANMacCommand_t)); + RADIOLIB_DEBUG_HEXDUMP(&(this->commandsUp.commands[i].cid), sizeof(LoRaWANMacCommand_t)); } // if FOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink @@ -1638,7 +1650,11 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { int16_t LoRaWANNode::setPhyProperties() { // set the physical layer configuration int8_t pwr = this->txPowerMax - this->txPowerCur * 2; - int16_t state = this->setTxPower(pwr); + int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; + while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { + // go from the highest power and lower it until we hit one supported by the module + state = this->phyLayer->setOutputPower(pwr--); + } RADIOLIB_ASSERT(state); uint8_t syncWord[3] = { 0 }; @@ -1681,7 +1697,7 @@ int16_t LoRaWANNode::setupChannels(uint8_t* cfList) { // if there is a cflist present, parse its frequencies into the next five slots, with datarate range copied from default channel 0 if(cfList != nullptr) { RADIOLIB_DEBUG_PRINTLN("CFList present"); - LoRaWANMacCommand_t cmd; + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; // datarate range for all new channels is equal to the default channels @@ -1700,7 +1716,7 @@ int16_t LoRaWANNode::setupChannels(uint8_t* cfList) { } else { // RADIOLIB_LORAWAN_BAND_FIXED if(cfList != nullptr) { RADIOLIB_DEBUG_PRINTLN("CFList present"); - LoRaWANMacCommand_t cmd; + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; cmd.payload[0] = 0xFF; // same datarate and payload @@ -1746,6 +1762,7 @@ int16_t LoRaWANNode::selectSubband(uint8_t startChannel, uint8_t endChannel) { RADIOLIB_DEBUG_PRINTLN("This is a dynamic band plan which does not support subbands"); return(RADIOLIB_ERR_INVALID_CHANNEL); } + this->selectedSubband = startChannel % 8; // save selected subband - assumed a block of 8 channels uint8_t numChannels = endChannel - startChannel + 1; if(startChannel > this->band->txSpans[0].numChannels) { @@ -1923,24 +1940,37 @@ int16_t LoRaWANNode::selectChannels() { return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::setDatarate(uint8_t drUp) { - // find the minimum and maximum available datarates by checking the enabled uplink channels - uint8_t drMin = RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; - uint8_t drMax = 0; +int16_t LoRaWANNode::setDatarate(uint8_t drUp, bool saveToEeprom) { + // scan through all enabled channels and check if the requested datarate is available + bool isValidDR = false; for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - drMin = RADIOLIB_MIN(drMin, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin); - drMax = RADIOLIB_MAX(drMax, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax); + LoRaWANChannel_t *chnl = &(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]); + if(chnl->enabled) { + if(drUp > chnl->drMin && drUp < chnl->drMax) { + isValidDR = true; + break; + } } } - if((drUp < drMin) || (drUp > drMax)) { - RADIOLIB_DEBUG_PRINTLN("Cannot configure DR %d (min: %d, max: %d)", drUp, drMin, drMax); + if(!isValidDR) { + RADIOLIB_DEBUG_PRINTLN("No defined channel allows datarate %d", drUp); return(RADIOLIB_ERR_DATA_RATE_INVALID); } - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp; - RADIOLIB_DEBUG_PRINTLN("Configured DR up = %d", drUp); + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; + cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; + cmd.payload[0] = (drUp << 4); + cmd.payload[0] |= 0x0F; // keep Tx Power the same + cmd.payload[3] |= (1 << 7); // set the RFU bit, which means that the channel mask gets ignored + cmd.payload[3] |= 0; // keep NbTrans the same + (void)execMacCommand(&cmd, saveToEeprom); + // check if ACK is set for Tx Power + if((cmd.payload[0] >> 1) != 1) { + return(RADIOLIB_ERR_DATA_RATE_INVALID); + } + return(RADIOLIB_ERR_NONE); } @@ -2007,13 +2037,31 @@ uint8_t LoRaWANNode::maxPayloadDwellTime() { return(payLen - 13); // fixed 13-byte header } -int16_t LoRaWANNode::setTxPower(int8_t txPower) { - int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; - while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { - // go from the highest power and lower it until we hit one supported by the module - state = this->phyLayer->setOutputPower(txPower--); +int16_t LoRaWANNode::setTxPower(int8_t txPower, bool saveToEeprom) { + // only allow values within the band's (or MAC state) maximum + if(txPower > this->txPowerMax) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); } - return(state); + // Tx Power is set in steps of two + // the selected value is rounded down to nearest multiple of two away from txPowerMax + // e.g. on EU868, max is 16; if 13 is selected then we set to 12 + uint8_t txPowerNew = (this->txPowerMax - txPower) / 2 + 1; + + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; + cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; + cmd.payload[0] = 0xF0; // keep datarate the same + cmd.payload[0] |= txPowerNew; // set the Tx Power + cmd.payload[3] |= (1 << 7); // set the RFU bit, which means that the channel mask gets ignored + cmd.payload[3] |= 0; // keep NbTrans the same + (void)execMacCommand(&cmd, saveToEeprom); + + // check if ACK is set for Tx Power + if((cmd.payload[0] >> 2) != 1) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + return(RADIOLIB_ERR_NONE); } int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { @@ -2197,13 +2245,16 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { } else { int8_t pwr = this->txPowerMax - 2*txPower; - int16_t state = this->setTxPower(pwr); + int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; + while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { + // go from the highest power and lower it until we hit one supported by the module + state = this->phyLayer->setOutputPower(pwr--); + } // only acknowledge if the requested datarate was succesfully configured if(state == RADIOLIB_ERR_NONE) { pwrAck = 1; this->txPowerCur = txPower; } - } bool isSuccessive = false; diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 81e87dbd..4387070d 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -596,9 +596,10 @@ class LoRaWANNode { /*! \brief Set uplink datarate. This should not be used when ADR is enabled. \param dr Datarate to use for uplinks. + \param saveToEeprom Whether to save this setting to EEPROM or not (default false). \returns \ref status_codes */ - int16_t setDatarate(uint8_t drUp); + int16_t setDatarate(uint8_t drUp, bool saveToEeprom = false); /*! \brief Toggle ADR to on or off. @@ -644,9 +645,10 @@ class LoRaWANNode { /*! \brief Configure TX power of the radio module. \param txPower Output power during TX mode to be set in dBm. + \param saveToEeprom Whether to save this setting to EEPROM or not (default false). \returns \ref status_codes */ - int16_t setTxPower(int8_t txPower); + int16_t setTxPower(int8_t txPower, bool saveToEeprom = false); /*! \brief Select a single subband (8 channels) for fixed bands such as US915. @@ -800,6 +802,9 @@ class LoRaWANNode { // indicates whether an uplink has MAC commands as payload bool isMACPayload = false; + // save the selected subband in case this must be restored in ADR control + int8_t selectedSubband = -1; + #if !defined(RADIOLIB_EEPROM_UNSUPPORTED) /*! \brief Save the current uplink frame counter.