From 7c3670c430dd2eb0fdc240f82adac3cc742e0e84 Mon Sep 17 00:00:00 2001 From: StevenCellist Date: Fri, 27 Oct 2023 01:18:53 +0200 Subject: [PATCH] [LoRaWAN] improve persistence, better Rx windows, wear leveling, confirmed frames --- src/Hal.cpp | 10 +- src/Hal.h | 7 +- src/modules/SX126x/SX126x.cpp | 43 +- src/modules/SX126x/SX126x.h | 12 + src/protocols/LoRaWAN/LoRaWAN.cpp | 617 ++++++++++++------ src/protocols/LoRaWAN/LoRaWAN.h | 59 +- src/protocols/PhysicalLayer/PhysicalLayer.cpp | 11 +- src/protocols/PhysicalLayer/PhysicalLayer.h | 12 + 8 files changed, 538 insertions(+), 233 deletions(-) diff --git a/src/Hal.cpp b/src/Hal.cpp index 6ba08bb7..1b4e818e 100644 --- a/src/Hal.cpp +++ b/src/Hal.cpp @@ -60,14 +60,14 @@ uint32_t RadioLibHal::getPersistentAddr(uint32_t id) { } template -void RadioLibHal::setPersistentParameter(uint32_t id, T val) { +void RadioLibHal::setPersistentParameter(uint32_t id, T val, uint32_t offset) { uint8_t *ptr = (uint8_t*)&val; - this->writePersistentStorage(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE + RadioLibPersistentParamTable[id], ptr, sizeof(T)); + this->writePersistentStorage(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE + RadioLibPersistentParamTable[id] + offset, ptr, sizeof(T)); } -template void RadioLibHal::setPersistentParameter(uint32_t id, uint8_t val); -template void RadioLibHal::setPersistentParameter(uint32_t id, uint16_t val); -template void RadioLibHal::setPersistentParameter(uint32_t id, uint32_t val); +template void RadioLibHal::setPersistentParameter(uint32_t id, uint8_t val, uint32_t offset); +template void RadioLibHal::setPersistentParameter(uint32_t id, uint16_t val, uint32_t offset); +template void RadioLibHal::setPersistentParameter(uint32_t id, uint32_t val, uint32_t offset); template T RadioLibHal::getPersistentParameter(uint32_t id) { diff --git a/src/Hal.h b/src/Hal.h index 415e53fb..34930938 100644 --- a/src/Hal.h +++ b/src/Hal.h @@ -11,7 +11,7 @@ #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (1) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION (2) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID (3) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_MAX_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_RX2FREQ_ID (6) #define RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID (7) @@ -38,7 +38,7 @@ static const uint32_t RadioLibPersistentParamTable[] = { 0x01, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID 0x03, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION 0x04, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID - 0x05, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_MAX_ID + 0x05, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID 0x06, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID 0x07, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID 0x0A, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID @@ -299,9 +299,10 @@ class RadioLibHal { will be stored in the system endian! \param id Parameter ID to save at. \param val Value to set. + \param offset An additional offset added to the address. */ template - void setPersistentParameter(uint32_t id, T val); + void setPersistentParameter(uint32_t id, T val, uint32_t offset = 0); /*! \brief Method to get arbitrary parameter from persistent storage. diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index 77412c5c..4080d2ff 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -1436,36 +1436,29 @@ 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 symbolTime = (0x00000001 << datarate->lora.spreadingFactor) * 1000 / datarate->lora.bandwidth; + timeoutUs = (uint32_t)numSymbols * symbolTime + offsetUs; uint32_t timeout = timeoutUs / 15.625; return(timeout); } +bool SX126x::isRxTimeout() { + uint16_t irq = getIrqStatus(); + bool isRxTimeout = false; + if(irq & RADIOLIB_SX126X_IRQ_TIMEOUT) { + isRxTimeout = true; + } + return(isRxTimeout); +} + +uint16_t SX126x::readIrq(bool clear) { + uint16_t irq = getIrqStatus(); + if(clear) + clearIrqStatus(); + return(irq); +} + int16_t SX126x::implicitHeader(size_t len) { return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, len)); } diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index 1dcf04c3..3e4c69df 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -959,6 +959,18 @@ class SX126x: public PhysicalLayer { */ uint32_t calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUss); + /*! + \brief Check whether there is a RxTimeout flag set + \returns RxTimeout flag is set + */ + bool isRxTimeout(); + + /*! + \brief Check whether there is a RxTimeout flag set + \returns RxTimeout flag is set + */ + uint16_t readIrq(bool clear = false); + /*! \brief Set implicit header mode for future reception/transmission. \param len Payload length in bytes. diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 19aaf898..3b522153 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -39,6 +39,7 @@ LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) { this->rx2 = this->band->rx2; } +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) void LoRaWANNode::wipe() { Module* mod = this->phyLayer->getMod(); mod->hal->wipePersistentStorage(); @@ -65,27 +66,47 @@ int16_t LoRaWANNode::restore() { mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, 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 this->rev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION); RADIOLIB_DEBUG_PRINTLN("LoRaWAN session: v1.%d", this->rev); + this->devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); + this->joinNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); + + // get MAC state uint8_t txDrRx2Dr = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID); - uint8_t txPwrCurMax = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_MAX_ID); + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = (txDrRx2Dr >> 4) & 0x0F; + + this->txPwrCur = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID); + uint8_t rx1DrOffDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID); - uint8_t rx2FreqBuf[3]; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); - uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); - uint8_t adrLimDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID); - this->nbTrans = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID); - - this->rx2.drMax = (txDrRx2Dr & 0x0F) >> 0; - this->rx1DrOffset = (rx1DrOffDel & 0xF0) >> 4; - this->rxDelays[0] = ((rx1DrOffDel & 0x0F) >> 0) * 1000; + this->rx1DrOffset = (rx1DrOffDel >> 4) & 0x0F; + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] + this->band->rx1DataRateBase + this->rx1DrOffset; + this->rxDelays[0] = ((rx1DrOffDel >> 0) & 0x0F) * 1000; if(this->rxDelays[0] == 0) { this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS; } this->rxDelays[1] = this->rxDelays[0] + 1000; + + uint8_t rx2FreqBuf[3]; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); + uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); + this->rx2.drMax = (txDrRx2Dr >> 0) & 0x0F; this->rx2.freq = (float)rx2Freq / 10000.0; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = (txDrRx2Dr & 0xF0) >> 4; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] + this->band->rx1DataRateBase + this->rx1DrOffset; + + uint8_t adrLimDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID); + this->adrLimitExp = (adrLimDel >> 4) & 0x0F; + this->adrDelayExp = (adrLimDel >> 0) & 0x0F; + + this->nbTrans = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID); + + this->aFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID); + this->nFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID); + this->confFcntUp = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID); + this->confFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID); + this->adrFcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID); + + // fcntUp is stored in highly efficient wear-leveling system, so parse it as required + this->restoreFcntUp(); uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); @@ -101,30 +122,95 @@ int16_t LoRaWANNode::restore() { return(RADIOLIB_ERR_NONE); } +int16_t LoRaWANNode::restoreFcntUp() { + Module* mod = this->phyLayer->getMod(); + + uint8_t fcntBuff[30] = { 0 }; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID), fcntBuff, 30); + RADIOLIB_DEBUG_HEXDUMP(fcntBuff, 30); + + // copy the two most significant bytes from the first two bytes + uint8_t bits_30_22 = fcntBuff[0]; + uint8_t bits_22_14 = fcntBuff[1]; + + // the next 7 bits must be retrieved from the byte to which was written most recently + // this is the last byte that has its state bit (most significant bit) set equal to its predecessor + // we find the first byte that has its state bit different, and subtract one + uint8_t idx = 2; + uint8_t state = fcntBuff[idx] >> 7; + for(; idx < 5; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + uint8_t bits_14_7 = fcntBuff[idx-1] & 0x7F; + + // equally, the last 7 bits must be retrieved from the byte to which was written most recently + // this is the last byte that has its state bit (most significant bit) set equal to its predecessor + // we find the first byte that has its state bit different, and subtract one + idx = 5; + state = fcntBuff[idx] >> 7; + for(; idx < 30; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + uint8_t bits_7_0 = fcntBuff[idx-1] & 0x7F; + + this->fcntUp = (bits_30_22 << 22) | (bits_22_14 << 14) | (bits_14_7 << 7) | bits_7_0; + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::restoreChannels() { + uint8_t bytesPerChannel = 5; + uint8_t numBytes = 2 * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel; + uint8_t buffer[numBytes]; + Module* mod = this->phyLayer->getMod(); + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes); + for(uint8_t dir = 0; dir < 2; dir++) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t chBuff[5] = { 0 }; + memcpy(chBuff, &buffer[(dir * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel) + i * bytesPerChannel], bytesPerChannel); + this->availableChannels[dir][i].enabled = (chBuff[0] & 0x80) >> 7; + this->availableChannels[dir][i].idx = chBuff[0] & 0x7F; + uint32_t freq = LoRaWANNode::ntoh(&chBuff[1], 3); + this->availableChannels[dir][i].freq = (float)freq/10000.0; + this->availableChannels[dir][i].drMax = (chBuff[0] & 0xF0) >> 4; + this->availableChannels[dir][i].drMin = (chBuff[0] & 0x0F) >> 0; + } + } + return(RADIOLIB_ERR_NONE); +} +#endif + int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) { // check if we actually need to send the join request Module* mod = this->phyLayer->getMod(); + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) if(!force && (mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) { // the device has joined already, we can just pull the data from persistent storage return(this->restore()); } +#endif // set the physical layer configuration + this->txPwrCur = this->band->powerMax; int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); - // get dev nonce from persistent storage and increment it - uint16_t devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1); - // setup uplink/downlink frequencies and datarates - state = this->selectChannelsJR(devNonce); + state = this->selectChannelsJR(this->devNonce); RADIOLIB_ASSERT(state); // configure for uplink with default configuration state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); RADIOLIB_ASSERT(state); + // increment devNonce as we are sending another join-request + this->devNonce += 1; + // build the join-request message uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; @@ -132,7 +218,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI); LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI); - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce); + LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], this->devNonce); // add the authentication code uint32_t mic = this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), nwkKey); @@ -141,7 +227,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // send it state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); RADIOLIB_ASSERT(state); - + // configure for downlink with default configuration state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); RADIOLIB_ASSERT(state); @@ -159,7 +245,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // start receiving uint32_t start = mod->hal->millis(); downlinkReceived = false; - state = this->phyLayer->startReceive(0x00, 0b0000001001100010, 0b0000000000000010, 0); + state = this->phyLayer->startReceive(); RADIOLIB_ASSERT(state); // wait for the reply or timeout @@ -216,15 +302,15 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe RADIOLIB_DEBUG_PRINTLN("joinAcceptMsg:"); RADIOLIB_DEBUG_HEXDUMP(joinAcceptMsg, lenRx); - // get current JoinNonce from downlink and previous JoinNonce from NVM - uint32_t joinNonce = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); - uint32_t joinNoncePrev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); - RADIOLIB_DEBUG_PRINTLN("JoinNoncePrev: %d, JoinNonce: %d", joinNoncePrev, joinNonce); - - // JoinNonce received must be greater than the last JoinNonce heard, else error - if((joinNoncePrev > 0) && (joinNonce <= joinNoncePrev)) { + // get current JoinNonce from downlink and previous JoinNonce from persistent storage + uint32_t joinNonceNew = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); + + RADIOLIB_DEBUG_PRINTLN("JoinNoncePrev: %d, JoinNonce: %d", this->joinNonce, joinNonceNew); + // JoinNonce received must be greater than the last JoinNonce heard, else error + if((this->joinNonce > 0) && (joinNonceNew <= this->joinNonce)) { return(RADIOLIB_ERR_JOIN_NONCE_INVALID); } + this->joinNonce = joinNonceNew; // check LoRaWAN revision (the MIC verification depends on this) uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS]; @@ -246,7 +332,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE; LoRaWANNode::hton(&micBuff[1], joinEUI); - LoRaWANNode::hton(&micBuff[9], devNonce); + LoRaWANNode::hton(&micBuff[9], this->devNonce); memcpy(&micBuff[11], joinAcceptMsg, lenRx); if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) { @@ -262,7 +348,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe } // parse other contents - uint32_t homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); + this->homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); this->devAddr = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]); // parse Rx1 delay (and subsequently Rx2) @@ -280,7 +366,6 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe } else { this->setupChannels(nullptr); } - this->saveChannels(); // prepare buffer for key derivation uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; @@ -290,7 +375,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe if(this->rev == 1) { // 1.1 version, derive the keys LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], this->devNonce); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(appKey); @@ -320,8 +405,8 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe } else { // 1.0 version, just derive the keys - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], this->homeNetId, 3); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], this->devNonce); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); @@ -334,56 +419,49 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe memcpy(this->nwkSEncKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } - - // store session configuration - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION, this->rev); - uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; - uint8_t txPwrCurMax; - uint8_t rx1DrOffDel = (this->rx1DrOffset << 4) | (this->rxDelays[0] / 1000); - uint32_t rx2Freq = uint32_t(this->rx2.freq * 10000); - uint8_t rx2FreqBuf[3]; - LoRaWANNode::hton(&rx2FreqBuf[0], rx2Freq, 3); - uint8_t adrLimDel = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4) | (RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP << 0); - uint8_t nbTrans = (0x01 << 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_MAX_ID, txPwrCurMax); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID, adrLimDel); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID, nbTrans); - // save the device address & keys + // reset all frame counters + this->fcntUp = 0; + this->aFcntDown = 0; + this->nFcntDown = 0; + this->confFcntUp = RADIOLIB_LORAWAN_FCNT_NONE; + this->confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; + this->adrFcnt = 0; + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // save the device address & keys as well as JoinAccept values; these are only ever set when joining mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); - // save uplink parameters - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID, homeNetId); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID, joinNonce); + // save join-request parameters + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID, this->homeNetId); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID, this->joinNonce); - // all complete, reset all frame counters and set the magic number - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); + this->saveSession(); + this->saveChannels(); - // everything written to NVM, write current table version to NVM + // everything written to NVM, write current table version to persistent storage and set magic number mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); +#endif + return(RADIOLIB_ERR_NONE); } int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, bool force) { + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) // check if we actually need to restart from a clean session Module* mod = this->phyLayer->getMod(); if(!force && (mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) { // the device has joined already, we can just pull the data from persistent storage return(this->restore()); } +#endif + this->devAddr = addr; memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); memcpy(this->nwkSEncKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE); @@ -398,6 +476,7 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, } // set the physical layer configuration + this->txPwrCur = this->band->powerMax; int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); @@ -405,24 +484,185 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, state = this->setupChannels(nullptr); RADIOLIB_ASSERT(state); - // everything written to NVM, write current version to NVM +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // save the device address & keys + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); + + this->saveSession(); + this->saveChannels(); + + // everything written to NVM, write current table version to persistent storage and set magic number mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); +#endif return(RADIOLIB_ERR_NONE); } +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) +int16_t LoRaWANNode::saveSession() { + Module* mod = this->phyLayer->getMod(); + + // store session configuration (MAC commands) + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION) != this->rev) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION, this->rev); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID) != this->devNonce) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, this->devNonce); + + uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID) != txDrRx2Dr) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID) != this->txPwrCur) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID, this->txPwrCur); + + uint8_t rx1DrOffDel = (this->rx1DrOffset << 4) | (this->rxDelays[0] / 1000); + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID) != rx1DrOffDel) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); + + uint8_t rx2FreqBuf[3]; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); + uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); + if(rx2Freq != uint32_t(this->rx2.freq * 10000)) { + rx2Freq = uint32_t(this->rx2.freq * 10000); + LoRaWANNode::hton(&rx2FreqBuf[0], rx2Freq, 3); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); + } + + uint8_t adrLimDel = (this->adrLimitExp << 4) | (this->adrDelayExp << 0); + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID) != adrLimDel) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID, adrLimDel); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID) != this->nbTrans) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID, this->nbTrans); + + // store all frame counters + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID) != this->aFcntDown) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, this->aFcntDown); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID) != this->nFcntDown) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, this->nFcntDown); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID) != this->confFcntUp) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID, this->confFcntUp); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID) != this->confFcntDown) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID, this->confFcntDown); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID) != this->adrFcnt) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID, this->adrFcnt); + + // fcntUp is saved using highly efficient wear-leveling as this is by far going to be written most often + this->saveFcntUp(); + + // if there is, or was, any MAC command in the queue, overwrite with the current MAC queue + uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)] = { 0 }; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + LoRaWANMacCommandQueue_t cmdTemp; + memcpy(&cmdTemp, queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + if(this->commandsUp.numCommands > 0 || cmdTemp.numCommands > 0) { + memcpy(queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::saveFcntUp() { + Module* mod = this->phyLayer->getMod(); + + uint8_t fcntBuff[30] = { 0 }; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID), fcntBuff, 30); + RADIOLIB_DEBUG_HEXDUMP(fcntBuff, 30); + + // we discard the first two bits - your flash will likely be far dead by the time you reach 2^30 uplinks + // the first two bytes of the remaining 30 bytes are stored straight into storage without additional wear leveling + // because they hardly ever change + uint8_t bits_30_22 = (uint8_t)(this->fcntUp >> 22); + if(fcntBuff[0] != bits_30_22) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_30_22, 0); + uint8_t bits_22_14 = (uint8_t)(this->fcntUp >> 14); + if(fcntBuff[1] != bits_22_14) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_22_14, 1); + + // the next 7 bits are stored into one of few indices + // this index is indicated by the first byte that has its state (most significant bit) different from its predecessor + // if all have an equal state, restart from the beginning + // always flip the state bit of the byte that we write to, to indicate that this is the most recently written byte + uint8_t idx = 2; + uint8_t state = fcntBuff[idx] >> 7; + for(; idx < 5; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + idx = idx < 5 ? idx : 2; + uint8_t bits_14_7 = (this->fcntUp >> 7) & 0x7F; + + // flip the first bit of this byte to indicate that we just wrote here + bits_14_7 |= (~(fcntBuff[idx] >> 7)) << 7; + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_14_7, idx); + + // equally, the last 7 bits are stored into one of many indices + // this index is indicated by the first byte that has its state (most significant bit) different from its predecessor + // if all have an equal state, restart from the beginning + // always flip the state bit of the byte that we write to, to indicate that this is the most recently written byte + idx = 5; + state = fcntBuff[idx] >> 7; + for(; idx < 30; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + idx = idx < 30 ? idx : 5; + uint8_t bits_7_0 = (this->fcntUp >> 0) & 0x7F; + + // flip the first bit of this byte to indicate that we just wrote here + bits_7_0 |= (~(fcntBuff[idx] >> 7)) << 7; + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_7_0, idx); + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::saveChannels() { + uint8_t bytesPerChannel = 5; + uint8_t numBytes = 2 * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel; + uint8_t buffer[numBytes]; + for(uint8_t dir = 0; dir < 2; dir++) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t chBuff[5] = { 0 }; + chBuff[0] |= (uint8_t)this->availableChannels[dir][i].enabled << 7; + chBuff[0] |= this->availableChannels[dir][i].idx; + uint32_t freq = this->availableChannels[dir][i].freq*10000.0; + LoRaWANNode::hton(&chBuff[1], freq, 3); + chBuff[4] = this->availableChannels[dir][i].drMax << 4; + chBuff[4] |= this->availableChannels[dir][i].drMin << 0; + memcpy(&buffer[(dir * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel) + i * bytesPerChannel], chBuff, bytesPerChannel); + } + } + Module* mod = this->phyLayer->getMod(); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes); + return(RADIOLIB_ERR_NONE); +} +#endif // RADIOLIB_EEPROM_UNSUPPORTED + + #if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::uplink(String& str, uint8_t port) { - return(this->uplink(str.c_str(), port)); +int16_t LoRaWANNode::uplink(String& str, uint8_t port, bool isConfirmed, bool adrEnabled) { + return(this->uplink(str.c_str(), port, isConfirmed, adrEnabled)); } #endif -int16_t LoRaWANNode::uplink(const char* str, uint8_t port) { - return(this->uplink((uint8_t*)str, strlen(str), port)); +int16_t LoRaWANNode::uplink(const char* str, uint8_t port, bool isConfirmed, bool adrEnabled) { + return(this->uplink((uint8_t*)str, strlen(str), port, isConfirmed, adrEnabled)); } -int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { - bool adrEn = true; +int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed, bool adrEnabled) { // check destination port if(port > 0xDF) { return(RADIOLIB_ERR_INVALID_PORT); @@ -452,26 +692,47 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { return(RADIOLIB_ERR_PACKET_TOO_LONG); } - // get frame counter from persistent storage - uint32_t fcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1; - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt); - uint32_t adrFcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID); - uint8_t adrParams = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID); - uint32_t adrLimit = 0x01 << ((adrParams & 0xF0) >> 4); - uint32_t adrDelay = 0x01 << ((adrParams & 0x0F) >> 0); - bool adrAckReq = false; - if((fcnt - adrFcnt) == adrLimit) { - adrAckReq = true; - // add MAC command to queue - } else if ((fcnt - adrFcnt) == (adrLimit + adrDelay)) { - // set TX power to max + // increase frame counter by one + this->fcntUp += 1; - // decrease DR if possible - if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMin) { - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]--; - } else { - // enable all channels + // check if we need to do ADR stuff + uint32_t adrLimit = 0x01 << this->adrLimitExp; + uint32_t adrDelay = 0x01 << this->adrDelayExp; + bool adrAckReq = false; + if((this->fcntUp - this->adrFcnt) >= adrLimit) { + adrAckReq = true; + } else if ((this->fcntUp - this->adrFcnt) == (adrLimit + adrDelay)) { + // try one of three, in order: set TxPower to max, set DR to min, enable all defined channels + + // set the maximum power supported by both the module and the band + int8_t pwr = this->band->powerMax; + int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; + while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { + // go from the highest power in band and lower it until we hit one supported by the module + state = this->phyLayer->setOutputPower(pwr--); } + RADIOLIB_ASSERT(state); + if(pwr == this->txPwrCur) { + + // 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]--; + } 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; + } + } + } + + } else { + this->txPwrCur = pwr; + } + + // we tried something to improve the range, so increase the ADR frame counter by 'ADR delay' + this->adrFcnt += adrDelay; } // configure for uplink @@ -495,19 +756,30 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { #endif // set the packet fields - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + if(isConfirmed) { + uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; + this->confFcntUp = this->fcntUp; + } else { + uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; + } + uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); // length of fopts will be added later uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; - if(adrEn) { + if(adrEnabled) { uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; if(adrAckReq) { uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; } } - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt); + // if the saved confirm-fcnt is equal to the last app-downlink, set the ACK bit and clear the confirm-fcnt + if(this->confFcntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; + } + + LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fcntUp); // check if we have some MAC commands to append if(foptsLen > 0) { @@ -534,12 +806,8 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen; // encrypt it - processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); + processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fcntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); - // write the current MAC command queue to nvm for next uplink - uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)]; - memcpy(queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); } // set the port @@ -552,15 +820,17 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { } // encrypt the frame payload - // TODO check ctrId --> erratum says it should be 0x01? - processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); + processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], this->fcntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); // create blocks for MIC calculation uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + if(this->confFcntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFcntDown); + } block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt); + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fcntUp); block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = uplinkMsgLen - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; @@ -600,6 +870,10 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { // set the timestamp so that we can measure when to start receiving this->rxDelayStart = txStart + timeOnAir; + + // the downlink confirmation was transmitted, so clear the counter value + this->confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; + return(RADIOLIB_ERR_NONE); } @@ -629,7 +903,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 = 50; + const uint32_t scanGuard = 10; 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 @@ -648,7 +922,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { downlinkTimeout = false; downlinkReceived = false; - this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan); + // this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan); // calculate the Rx timeout // according to the spec, this must be at least enough time to effectively detect a preamble @@ -656,7 +930,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { 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); + uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(23, &dataRate, 2*scanGuard*1000, timeoutHost); RADIOLIB_DEBUG_PRINTLN("RxTimeout host: %d, module: %06x", timeoutHost, timeoutMod); // perform listening in the two Rx windows @@ -668,9 +942,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { if(waitLen > scanGuard) { waitLen -= scanGuard; } - RADIOLIB_DEBUG_PRINTLN("Waiting for %d ms...", waitLen); mod->hal->delay(waitLen); - RADIOLIB_DEBUG_PRINTLN("Opening Rx%d window...", (i+1)); // TODO somehow make this module-independent, currently configured for SX126x uint16_t irqFlags = 0b0000000000000010; // RxDone @@ -681,10 +953,11 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { 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(); - } + mod->hal->delay(timeoutHost / 1000 + scanGuard); RADIOLIB_DEBUG_PRINTLN("closing"); + if(this->phyLayer->isRxTimeout()) { + downlinkTimeout = true; + } // check if we detected something of a packet if(!downlinkTimeout) { @@ -700,12 +973,12 @@ 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); + timeoutMod = this->phyLayer->calculateRxTimeout(22, &dataRate, 2*scanGuard*1000, timeoutHost); downlinkTimeout = false; } } - this->phyLayer->clearChannelScanAction(); + // this->phyLayer->clearChannelScanAction(); // check if we received a packet at all if(downlinkTimeout) { @@ -716,9 +989,11 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { return(RADIOLIB_ERR_RX_TIMEOUT); } - this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); - while(!downlinkReceived) { + // this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); + + // while(!downlinkReceived) { + while(!this->phyLayer->readIrq()) { mod->hal->yield(); } @@ -773,9 +1048,16 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { } // get the frame counter and set it to the MIC calculation block - // TODO cache the ADR bit? uint16_t fcnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt16); + + // if this downlink is confirming an uplink, its MIC was generated with the least-significant 16 bits of that fcntUp + // TODO get this to the user somehow + bool isConfirmingUp = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK) && (this->rev == 1)) { + isConfirmingUp = true; + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFcntUp); + } RADIOLIB_DEBUG_PRINTLN("downlinkMsg:"); RADIOLIB_DEBUG_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); @@ -792,9 +1074,9 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { // check the FcntDown value (Network or Application) uint32_t fcntDownPrev = 0; if (isAppDownlink) { - fcntDownPrev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID); + fcntDownPrev = this->aFcntDown; } else { - fcntDownPrev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID); + fcntDownPrev = this->nFcntDown; } RADIOLIB_DEBUG_PRINTLN("fcnt: %d, fcntPrev: %d, isAppDownlink: %d", fcnt16, fcntDownPrev, (int)isAppDownlink); @@ -819,13 +1101,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { } } - // save current fcnt to NVM - if (isAppDownlink) { - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, fcnt32); - } else { - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, fcnt32); - } - // check the MIC if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { #if !defined(RADIOLIB_STATIC_ONLY) @@ -833,6 +1108,19 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { #endif return(RADIOLIB_ERR_CRC_MISMATCH); } + + // save current fcnt to respective frame counter + if (isAppDownlink) { + this->aFcntDown = fcnt32; + } else { + this->nFcntDown = fcnt32; + } + + // if this is a confirmed frame, save the downlink number (only app frames can be confirmed) + bool isConfirmedDown = false; + if(downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] && 0xFE == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { + this->confFcntDown = this->aFcntDown; + } // check the address uint32_t addr = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]); @@ -900,17 +1188,16 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { this->isMACPayload = true; this->uplink(foptsBuff, foptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); + uint8_t strDown[this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]]; + size_t lenDown = 0; + state = this->downlink(strDown, &lenDown); + RADIOLIB_ASSERT(state); } - // write the MAC command queue to nvm for next uplink - uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)]; - memcpy(queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); } // a downlink was received, so reset the ADR counter to this uplink's fcnt - uint32_t fcntUp = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID, fcntUp); + this->adrFcnt = this->fcntUp; // process payload (if there is any) if(payLen <= 0) { @@ -989,13 +1276,13 @@ int16_t LoRaWANNode::setPhyProperties() { // RADIOLIB_ASSERT(state); // set the maximum power supported by both the module and the band - int8_t pwr = this->band->powerMax; state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { // go from the highest power in band and lower it until we hit one supported by the module - state = this->phyLayer->setOutputPower(pwr--); + state = this->phyLayer->setOutputPower(this->txPwrCur--); } RADIOLIB_ASSERT(state); + this->txPwrCur++; uint8_t syncWord[3] = { 0 }; uint8_t syncWordLen = 0; @@ -1109,7 +1396,6 @@ int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce) { // configure data rates for TX and RX1: for TX the (floored) average of min and max; for RX1 identical with base offset this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = int((channelUp.drMax + channelUp.drMin) / 2); - // TODO check bounds this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], this->rx1DrOffset, this->band->rx1DataRateBase, channelDn.drMin, channelDn.drMax); } else { // RADIOLIB_LORAWAN_CFLIST_TYPE_MASK @@ -1133,16 +1419,12 @@ int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce) { // configure data rates for TX and RX1: for TX the specified value for this band; for RX1 identical with base offset this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = this->band->txSpans[spanID].joinRequestDataRate; - // TODO check bounds this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], this->rx1DrOffset, this->band->rx1DataRateBase, channelDn.drMin, channelDn.drMax); } this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = channelUp; this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDn; - Module* mod = this->phyLayer->getMod(); - uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); return(RADIOLIB_ERR_NONE); } @@ -1216,7 +1498,6 @@ int16_t LoRaWANNode::configureChannel(uint8_t dir) { // set the frequency RADIOLIB_DEBUG_PRINTLN(""); RADIOLIB_DEBUG_PRINTLN("Channel frequency %cL = %f MHz", dir ? 'D' : 'U', this->currentChannels[dir].freq); - RADIOLIB_DEBUG_PRINTLN("Datarate index: %d", this->dataRates[dir]); int state = this->phyLayer->setFrequency(this->currentChannels[dir].freq); RADIOLIB_ASSERT(state); @@ -1228,48 +1509,6 @@ int16_t LoRaWANNode::configureChannel(uint8_t dir) { return(state); } -int16_t LoRaWANNode::saveChannels() { - uint8_t bytesPerChannel = 5; - uint8_t numBytes = 2 * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel; - uint8_t buffer[numBytes]; - for(uint8_t dir = 0; dir < 2; dir++) { - for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - uint8_t chBuff[5] = { 0 }; - chBuff[0] |= (uint8_t)this->availableChannels[dir][i].enabled << 7; - chBuff[0] |= this->availableChannels[dir][i].idx; - uint32_t freq = this->availableChannels[dir][i].freq*10000.0; - LoRaWANNode::hton(&chBuff[1], freq, 3); - chBuff[4] = this->availableChannels[dir][i].drMax << 4; - chBuff[4] |= this->availableChannels[dir][i].drMin << 0; - memcpy(&buffer[(dir * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel) + i * bytesPerChannel], chBuff, bytesPerChannel); - } - } - Module* mod = this->phyLayer->getMod(); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes); - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::restoreChannels() { - uint8_t bytesPerChannel = 5; - uint8_t numBytes = 2 * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel; - uint8_t buffer[numBytes]; - Module* mod = this->phyLayer->getMod(); - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes); - for(uint8_t dir = 0; dir < 2; dir++) { - for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - uint8_t chBuff[5] = { 0 }; - memcpy(chBuff, &buffer[(dir * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel) + i * bytesPerChannel], bytesPerChannel); - this->availableChannels[dir][i].enabled = (chBuff[0] & 0x80) >> 7; - this->availableChannels[dir][i].idx = chBuff[0] & 0x7F; - uint32_t freq = LoRaWANNode::ntoh(&chBuff[1], 3); - this->availableChannels[dir][i].freq = (float)freq/10000.0; - this->availableChannels[dir][i].drMax = (chBuff[0] & 0xF0) >> 4; - this->availableChannels[dir][i].drMin = (chBuff[0] & 0x0F) >> 0; - } - } - return(RADIOLIB_ERR_NONE); -} - int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue) { if(queue->numCommands >= RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) { return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); @@ -1355,6 +1594,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { case(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR): { // get the ADR configuration + // TODO all these configuration should only be set if all ACKs are set, otherwise retain previous state uint8_t dr = (cmd->payload[0] & 0xF0) >> 4; uint8_t txPower = cmd->payload[0] & 0x0F; uint16_t chMask = LoRaWANNode::ntoh(&cmd->payload[1]); @@ -1375,8 +1615,6 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax; } - uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); drAck = 1; } @@ -1390,6 +1628,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { RADIOLIB_DEBUG_PRINTLN("ADR set pwr = %d", pwr); pwrAck = 1; } + this->txPwrCur = pwr; } this->nbTrans = nbTrans; @@ -1449,12 +1688,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); } - // update saved values - uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; - uint8_t rx1DrOffDel = (this->rx1DrOffset << 4) | (this->rxDelays[0] / 1000); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); - + // TODO this should be sent repeatedly until the next downlink // send the reply cmd->len = 1; cmd->payload[0] = (rx1OffsAck << 2) | (rx2Ack << 1) | (chanAck << 0); @@ -1508,8 +1742,10 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { } } +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) // update saved frequencies this->saveChannels(); +#endif // send the reply cmd->len = 1; @@ -1546,9 +1782,11 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { } } +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) // update saved frequencies this->saveChannels(); - +#endif + // send the reply cmd->len = 1; cmd->payload[0] = (freqUlAck << 1) | (freqDlAck << 0); @@ -1570,10 +1808,6 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { this->rxDelays[0] = delay * 1000; this->rxDelays[1] = this->rxDelays[0] + 1000; - // update saved values - uint8_t rx1DrOffDel = (this->rx1DrOffset << 4) | (this->rxDelays[0] / 1000); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); - // send the reply cmd->len = 0; @@ -1615,9 +1849,6 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { uint8_t limitExp = (cmd->payload[0] & 0xF0) >> 4; uint8_t delayExp = cmd->payload[0] & 0x0F; RADIOLIB_DEBUG_PRINTLN("ADR param setup: limitExp = %d, delayExp = %d", limitExp, delayExp); - - // update saved values - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID, cmd->payload[0]); return(1); } break; diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 636c70c3..b88c414b 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -135,6 +135,7 @@ // payload encryption/MIC blocks common layout #define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0) +#define RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS (1) #define RADIOLIB_LORAWAN_BLOCK_DIR_POS (5) #define RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS (6) #define RADIOLIB_LORAWAN_BLOCK_FCNT_POS (10) @@ -171,6 +172,9 @@ #define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP (0x0F) #define RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY (0x80) +// unused frame counter value +#define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) + // the length of internal MAC command queue - hopefully this is enough for most use cases #define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (8) @@ -334,9 +338,6 @@ class LoRaWANNode { // RX2 channel properties - may be changed by MAC command LoRaWANChannel_t rx2; - // Number of allowed frame retransmissions - uint8_t nbTrans; - /*! \brief Default constructor. \param phy Pointer to the PhysicalLayer radio module. @@ -344,6 +345,7 @@ class LoRaWANNode { */ LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band); +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) /*! \brief Wipe internal persistent parameters. This will reset all counters and saved variables, so the device will have to rejoin the network. @@ -356,6 +358,14 @@ class LoRaWANNode { */ int16_t restore(); + /*! + \brief Restore frame counter for uplinks from persistent storage. + Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling. + \returns \ref status_codes + */ + int16_t restoreFcntUp(); +#endif + /*! \brief Join network by performing over-the-air activation. By this procedure, the device will perform an exchange with the network server and set all necessary configuration. @@ -381,32 +391,52 @@ class LoRaWANNode { */ int16_t beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL, bool force = false); + /*! + \brief Save the current state of the session. + All variables are compared to what is saved and only the differences are rewritten. + \returns \ref status_codes + */ + int16_t saveSession(); + + /*! + \brief Save the current uplink frame counter. + Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling. + \returns \ref status_codes + */ + int16_t saveFcntUp(); + #if defined(RADIOLIB_BUILD_ARDUINO) /*! \brief Send a message to the server. \param str Address of Arduino String that will be transmitted. \param port Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param adrEnabled Whether ADR is enabled or not. \returns \ref status_codes */ - int16_t uplink(String& str, uint8_t port); + int16_t uplink(String& str, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); #endif /*! \brief Send a message to the server. \param str C-string that will be transmitted. \param port Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param adrEnabled Whether ADR is enabled or not. \returns \ref status_codes */ - int16_t uplink(const char* str, uint8_t port); + int16_t uplink(const char* str, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); /*! \brief Send a message to the server. \param data Data to send. \param len Length of the data. \param port Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param adrEnabled Whether ADR is enabled or not. \returns \ref status_codes */ - int16_t uplink(uint8_t* data, size_t len, uint8_t port); + int16_t uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); #if defined(RADIOLIB_BUILD_ARDUINO) /*! @@ -457,6 +487,23 @@ class LoRaWANNode { uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; uint8_t jSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + + // device-specific parameters, persistent through sessions + uint16_t devNonce = 0; + uint32_t joinNonce = 0; + + // session-specific parameters + uint32_t homeNetId = 0; + uint8_t adrLimitExp = RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP; + uint8_t adrDelayExp = RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; + uint8_t nbTrans = 1; // Number of allowed frame retransmissions + uint8_t txPwrCur = 0; + uint32_t fcntUp = 0; + uint32_t aFcntDown = 0; + uint32_t nFcntDown = 0; + uint32_t confFcntUp = RADIOLIB_LORAWAN_FCNT_NONE; + uint32_t confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; + uint32_t adrFcnt = 0; // available channel frequencies from list passed during OTA activation LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS] = { { 0 }, { 0 } }; diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.cpp b/src/protocols/PhysicalLayer/PhysicalLayer.cpp index de31e571..d558d479 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.cpp +++ b/src/protocols/PhysicalLayer/PhysicalLayer.cpp @@ -299,7 +299,16 @@ uint32_t PhysicalLayer::calculateRxTimeout(uint8_t numSymbols, DataRate_t *datar (void)datarate; (void)offsetUs; (void)timeoutUs; - return(RADIOLIB_ERR_UNSUPPORTED); + return(0); +} + +bool PhysicalLayer::isRxTimeout() { + return(0); +} + +uint16_t PhysicalLayer::readIrq(bool clear) { + (void)clear; + return(0); } int16_t PhysicalLayer::startChannelScan() { diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.h b/src/protocols/PhysicalLayer/PhysicalLayer.h index 12ce0a7e..e9f9f4da 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.h +++ b/src/protocols/PhysicalLayer/PhysicalLayer.h @@ -319,6 +319,18 @@ class PhysicalLayer { \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 Check whether there is a RxTimeout flag set + \returns RxTimeout flag is set + */ + virtual bool isRxTimeout(); + + /*! + \brief Check whether there is a RxTimeout flag set + \returns RxTimeout flag is set + */ + virtual uint16_t readIrq(bool clear = false); /*! \brief Interrupt-driven channel activity detection method. interrupt will be activated