diff --git a/keywords.txt b/keywords.txt index 6ee6222c8..2cfc5eae4 100644 --- a/keywords.txt +++ b/keywords.txt @@ -293,6 +293,10 @@ setModem KEYWORD2 # LoRaWAN wipe KEYWORD2 +getBufferNonces KEYWORD2 +setBufferNonces KEYWORD2 +getBufferSession KEYWORD2 +setBufferSession KEYWORD2 restore KEYWORD2 beginOTAA KEYWORD2 beginABP KEYWORD2 diff --git a/src/ArduinoHal.cpp b/src/ArduinoHal.cpp index 59b5fb5e5..9c5fd571a 100644 --- a/src/ArduinoHal.cpp +++ b/src/ArduinoHal.cpp @@ -2,10 +2,6 @@ #if defined(RADIOLIB_BUILD_ARDUINO) -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) -#include -#endif - ArduinoHal::ArduinoHal(): RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING), spi(&RADIOLIB_DEFAULT_SPI), initInterface(true) {} ArduinoHal::ArduinoHal(SPIClass& spi, SPISettings spiSettings): RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING), spi(&spi), spiSettings(spiSettings) {} @@ -118,49 +114,6 @@ void inline ArduinoHal::spiEnd() { spi->end(); } -void ArduinoHal::readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len) { - #if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - #if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040) - EEPROM.begin(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE); - #elif defined(ARDUINO_ARCH_APOLLO3) - EEPROM.init(); - #endif - for(size_t i = 0; i < len; i++) { - buff[i] = EEPROM.read(addr + i); - } - #if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040) - EEPROM.end(); - #endif - #else - (void)addr; - (void)buff; - (void)len; - #endif -} - -void ArduinoHal::writePersistentStorage(uint32_t addr, uint8_t* buff, size_t len) { - #if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - #if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040) - EEPROM.begin(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE); - #elif defined(ARDUINO_ARCH_APOLLO3) - EEPROM.init(); - #endif - for(size_t i = 0; i < len; i++) { - if(EEPROM.read(addr + i) != buff[i]) { // only write if value is new - EEPROM.write(addr + i, buff[i]); - } - } - #if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040) - EEPROM.commit(); - EEPROM.end(); - #endif - #else - (void)addr; - (void)buff; - (void)len; - #endif -} - void inline ArduinoHal::tone(uint32_t pin, unsigned int frequency, unsigned long duration) { #if !defined(RADIOLIB_TONE_UNSUPPORTED) if(pin == RADIOLIB_NC) { diff --git a/src/ArduinoHal.h b/src/ArduinoHal.h index bcda95aec..00074c1d3 100644 --- a/src/ArduinoHal.h +++ b/src/ArduinoHal.h @@ -51,9 +51,6 @@ class ArduinoHal : public RadioLibHal { void spiEndTransaction() override; void spiEnd() override; - void readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len) override; - void writePersistentStorage(uint32_t addr, uint8_t* buff, size_t len) override; - // implementations of virtual RadioLibHal methods void init() override; void term() override; diff --git a/src/BuildOpt.h b/src/BuildOpt.h index a7adfbbc0..635acd9ce 100644 --- a/src/BuildOpt.h +++ b/src/BuildOpt.h @@ -104,21 +104,6 @@ #define RADIOLIB_STATIC_ARRAY_SIZE (256) #endif -// the base address for persistent storage -// some protocols (e.g. LoRaWAN) require a method -// to store some data persistently -// on Arduino, this will use EEPROM, on non-Arduino platform, -// it will use anything provided by the hardware abstraction layer -// RadioLib will place these starting at this address -#if !defined(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE) - #define RADIOLIB_HAL_PERSISTENT_STORAGE_BASE (0) -#endif - -// the amount of space allocated to the persistent storage -#if !defined(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE) - #define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x01C0) -#endif - /* * Uncomment on boards whose clock runs too slow or too fast * Set the value according to the following scheme: @@ -238,7 +223,6 @@ #elif defined(SAMD_SERIES) // Adafruit SAMD boards (M0 and M4) #define RADIOLIB_PLATFORM "Adafruit SAMD" - #define RADIOLIB_EEPROM_UNSUPPORTED #elif defined(ARDUINO_ARCH_SAMD) // Arduino SAMD (Zero, MKR, etc.) @@ -246,18 +230,15 @@ #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) - #define RADIOLIB_EEPROM_UNSUPPORTED #elif defined(__SAM3X8E__) // Arduino Due #define RADIOLIB_PLATFORM "Arduino Due" #define RADIOLIB_TONE_UNSUPPORTED - #define RADIOLIB_EEPROM_UNSUPPORTED #elif (defined(NRF52832_XXAA) || defined(NRF52840_XXAA)) && !defined(ARDUINO_ARDUINO_NANO33BLE) // Adafruit nRF52 boards #define RADIOLIB_PLATFORM "Adafruit nRF52" - #define RADIOLIB_EEPROM_UNSUPPORTED #elif defined(ARDUINO_ARC32_TOOLS) // Intel Curie @@ -280,7 +261,6 @@ #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) - #define RADIOLIB_EEPROM_UNSUPPORTED // Arduino mbed OS boards have a really bad tone implementation which will crash after a couple seconds #define RADIOLIB_TONE_UNSUPPORTED @@ -292,7 +272,6 @@ #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) - #define RADIOLIB_EEPROM_UNSUPPORTED // Arduino mbed OS boards have a really bad tone implementation which will crash after a couple seconds #define RADIOLIB_TONE_UNSUPPORTED @@ -314,7 +293,6 @@ #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) - #define RADIOLIB_EEPROM_UNSUPPORTED // Arduino mbed OS boards have a really bad tone implementation which will crash after a couple seconds #define RADIOLIB_TONE_UNSUPPORTED @@ -585,4 +563,4 @@ #define RADIOLIB_VERSION (((RADIOLIB_VERSION_MAJOR) << 24) | ((RADIOLIB_VERSION_MINOR) << 16) | ((RADIOLIB_VERSION_PATCH) << 8) | (RADIOLIB_VERSION_EXTRA)) -#endif +#endif \ No newline at end of file diff --git a/src/Hal.cpp b/src/Hal.cpp index 1b4e818e2..1a42aa15a 100644 --- a/src/Hal.cpp +++ b/src/Hal.cpp @@ -33,50 +33,3 @@ void RadioLibHal::yield() { uint32_t RadioLibHal::pinToInterrupt(uint32_t pin) { return(pin); } - -void RadioLibHal::readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len) { - // these are only needed for some protocols, so it's not needed to have them by default - (void)addr; - (void)buff; - (void)len; -} - -void RadioLibHal::writePersistentStorage(uint32_t addr, uint8_t* buff, size_t len) { - // these are only needed for some protocols, so it's not needed to have them by default - (void)addr; - (void)buff; - (void)len; -} - -void RadioLibHal::wipePersistentStorage() { - uint8_t dummy = 0; - for(size_t i = 0; i < RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE; i++) { - this->writePersistentStorage(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE + i, &dummy, sizeof(uint8_t)); - } -} - -uint32_t RadioLibHal::getPersistentAddr(uint32_t id) { - return(RadioLibPersistentParamTable[id]); -} - -template -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] + offset, ptr, sizeof(T)); -} - -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) { - T val = 0; - uint8_t *ptr = (uint8_t*)&val; - this->readPersistentStorage(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE + RadioLibPersistentParamTable[id], ptr, sizeof(T)); - return(val); -} - -template uint8_t RadioLibHal::getPersistentParameter(uint32_t id); -template uint16_t RadioLibHal::getPersistentParameter(uint32_t id); -template uint32_t RadioLibHal::getPersistentParameter(uint32_t id); diff --git a/src/Hal.h b/src/Hal.h index 4a0c72c3e..03bf174ce 100644 --- a/src/Hal.h +++ b/src/Hal.h @@ -6,88 +6,6 @@ #include "BuildOpt.h" -#define RADIOLIB_EEPROM_TABLE_VERSION (0x0002) - -// list of persistent parameters -enum RADIOLIB_EEPROM_PARAMS { - RADIOLIB_EEPROM_TABLE_VERSION_ID, // table layout version - RADIOLIB_EEPROM_LORAWAN_CLASS_ID, // class A, B or C - RADIOLIB_EEPROM_LORAWAN_MODE_ID, // none, OTAA or ABP - RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID, // checksum of keys used for device activation - RADIOLIB_EEPROM_LORAWAN_VERSION_ID, // LoRaWAN version - RADIOLIB_EEPROM_LORAWAN_LAST_TIME_ID, // last heard time through DeviceTimeReq or Beacon - RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID, - RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID, - RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID, - RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID, - RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID, - RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID, - RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID, - RADIOLIB_EEPROM_LORAWAN_HOME_NET_ID, - RADIOLIB_EEPROM_LORAWAN_A_FCNT_DOWN_ID, - RADIOLIB_EEPROM_LORAWAN_N_FCNT_DOWN_ID, - RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_UP_ID, - RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_DOWN_ID, - RADIOLIB_EEPROM_LORAWAN_ADR_FCNT_ID, - RADIOLIB_EEPROM_LORAWAN_RJ_COUNT0_ID, - RADIOLIB_EEPROM_LORAWAN_RJ_COUNT1_ID, - RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID, - RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID, - RADIOLIB_EEPROM_LORAWAN_DUTY_CYCLE_ID, - RADIOLIB_EEPROM_LORAWAN_RX_PARAM_SETUP_ID, - RADIOLIB_EEPROM_LORAWAN_RX_TIMING_SETUP_ID, - RADIOLIB_EEPROM_LORAWAN_TX_PARAM_SETUP_ID, - RADIOLIB_EEPROM_LORAWAN_ADR_PARAM_SETUP_ID, - RADIOLIB_EEPROM_LORAWAN_REJOIN_PARAM_SETUP_ID, - RADIOLIB_EEPROM_LORAWAN_BEACON_FREQ_ID, - RADIOLIB_EEPROM_LORAWAN_PING_SLOT_CHANNEL_ID, - RADIOLIB_EEPROM_LORAWAN_PERIODICITY_ID, - RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, - RADIOLIB_EEPROM_LORAWAN_MAC_QUEUE_UL_ID, - RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID, - RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID -}; - -static const uint32_t RadioLibPersistentParamTable[] = { - 0x00, // RADIOLIB_EEPROM_TABLE_VERSION_ID - 0x02, // RADIOLIB_EEPROM_LORAWAN_CLASS_ID - 0x03, // RADIOLIB_EEPROM_LORAWAN_MODE_ID - 0x05, // RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID - 0x07, // RADIOLIB_EEPROM_LORAWAN_VERSION_ID - 0x08, // RADIOLIB_EEPROM_LORAWAN_LAST_TIME_ID - 0x0C, // RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID - 0x10, // RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID - 0x20, // RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID - 0x30, // RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID - 0x40, // RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID - 0x50, // RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID - 0x54, // RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID - 0x58, // RADIOLIB_EEPROM_LORAWAN_HOME_NET_ID - 0x5C, // RADIOLIB_EEPROM_LORAWAN_A_FCNT_DOWN_ID - 0x60, // RADIOLIB_EEPROM_LORAWAN_N_FCNT_DOWN_ID - 0x64, // RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_UP_ID - 0x68, // RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_DOWN_ID - 0x6C, // RADIOLIB_EEPROM_LORAWAN_ADR_FCNT_ID - 0x70, // RADIOLIB_EEPROM_LORAWAN_RJ_COUNT0_ID - 0x72, // RADIOLIB_EEPROM_LORAWAN_RJ_COUNT1_ID - 0x74, // RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID - 0xA0, // RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID - 0xA4, // RADIOLIB_EEPROM_LORAWAN_DUTY_CYCLE_ID - 0xA5, // RADIOLIB_EEPROM_LORAWAN_RX_PARAM_SETUP_ID - 0xA9, // RADIOLIB_EEPROM_LORAWAN_RX_TIMING_SETUP_ID - 0xAA, // RADIOLIB_EEPROM_LORAWAN_TX_PARAM_SETUP_ID - 0xAB, // RADIOLIB_EEPROM_LORAWAN_ADR_PARAM_SETUP_ID - 0xAC, // RADIOLIB_EEPROM_LORAWAN_REJOIN_PARAM_SETUP_ID - 0xAD, // RADIOLIB_EEPROM_LORAWAN_BEACON_FREQ_ID - 0xB0, // RADIOLIB_EEPROM_LORAWAN_PING_SLOT_CHANNEL_ID - 0xB4, // RADIOLIB_EEPROM_LORAWAN_PERIODICITY_ID - 0xB5, // RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID - 0xB6, // RADIOLIB_EEPROM_LORAWAN_MAC_QUEUE_UL_ID - 0x0100, // RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID - 0x0180, // RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID - 0x01C0, // end -}; - /*! \class RadioLibHal \brief Hardware abstraction library base interface. @@ -289,56 +207,6 @@ class RadioLibHal { \returns The interrupt number of a given pin. */ virtual uint32_t pinToInterrupt(uint32_t pin); - - /*! - \brief Method to read from persistent storage (e.g. EEPROM). - \param addr Address to start reading at. - \param buff Buffer to read into. - \param len Number of bytes to read. - */ - virtual void readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len); - - /*! - \brief Method to write to persistent storage (e.g. EEPROM). - \param addr Address to start writing to. - \param buff Buffer to write. - \param len Number of bytes to write. - */ - virtual void writePersistentStorage(uint32_t addr, uint8_t* buff, size_t len); - - /*! - \brief Method to wipe the persistent storage by writing to 0. - Will write at most RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE bytes. - */ - void wipePersistentStorage(); - - /*! - \brief Method to convert from persistent parameter ID to its physical address. - \param id Parameter ID. - \returns Parameter physical address. - */ - uint32_t getPersistentAddr(uint32_t id); - - /*! - \brief Method to set arbitrary parameter to persistent storage. - This method DOES NOT perform any endianness conversion, so the value - 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, uint32_t offset = 0); - - /*! - \brief Method to get arbitrary parameter from persistent storage. - This method DOES NOT perform any endianness conversion, so the value - will be retrieved in the system endian! - \param id Parameter ID to load from. - \returns The loaded value. - */ - template - T getPersistentParameter(uint32_t id); }; #endif diff --git a/src/TypeDef.h b/src/TypeDef.h index 42334988d..0135cac2a 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -558,6 +558,11 @@ */ #define RADIOLIB_ERR_DWELL_TIME_EXCEEDED (-1115) +/*! + \brief The buffer integrity check did not match the supplied checksum value. +*/ +#define RADIOLIB_ERR_CHECKSUM_MISMATCH (-1116) + /*! \} */ diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index fef8ff811..d4295cde0 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -6,10 +6,6 @@ #if !RADIOLIB_EXCLUDE_LORAWAN -#if defined(RADIOLIB_EEPROM_UNSUPPORTED) - #warning "Persistent storage not supported!" -#endif - // flag to indicate whether there was some action during Rx mode (timeout or downlink) static volatile bool downlinkAction = false; @@ -48,172 +44,210 @@ void LoRaWANNode::setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA this->enableCSMA = enableCSMA; } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) void LoRaWANNode::wipe() { - Module* mod = this->phyLayer->getMod(); - mod->hal->wipePersistentStorage(); + memset(this->bufferNonces, 0, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); +} + +uint8_t* LoRaWANNode::getBufferNonces() { + return(this->bufferNonces); +} + +int16_t LoRaWANNode::setBufferNonces(uint8_t* persistentBuffer) { + if(this->isJoined()) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Did not update buffer: session already active"); + return(RADIOLIB_ERR_NONE); + } + + int16_t state = LoRaWANNode::checkBufferCommon(persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + RADIOLIB_ASSERT(state); + + // copy the whole buffer over + memcpy(this->bufferNonces, persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + + // revert to inactive as long as no session is restored + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; + + return(state); } -int16_t LoRaWANNode::restore() { +uint8_t* LoRaWANNode::getBufferSession() { + // update buffer contents + this->saveSession(); + + return(this->bufferSession); +} + +int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { + if(this->isJoined()) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Did not update buffer: session already active"); + return(RADIOLIB_ERR_NONE); + } + + int16_t state = LoRaWANNode::checkBufferCommon(persistentBuffer, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + RADIOLIB_ASSERT(state); + + // the Nonces buffer holds a checksum signature - compare this to the signature that is in the session buffer + uint16_t signatureNonces = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE]); + uint16_t signatureInSession = LoRaWANNode::ntoh(&persistentBuffer[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE]); + if(signatureNonces != signatureInSession) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The supplied session buffer does not match the Nonces buffer"); + return(RADIOLIB_ERR_CHECKSUM_MISMATCH); + } + + // copy the whole buffer over + memcpy(this->bufferSession, persistentBuffer, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + + // as both the Nonces and session are restored, revert to active session + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; + + return(state); +} + +int16_t LoRaWANNode::checkBufferCommon(uint8_t *buffer, uint16_t size) { + // check if there are actually values in the buffer + size_t i = 0; + for(; i < size; i++) { + if(buffer[i]) { + break; + } + } + if(i == size) { + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + // check integrity of the whole buffer (compare checksum to included checksum) + uint16_t checkSum = LoRaWANNode::checkSum16(buffer, size - 2); + uint16_t signature = LoRaWANNode::ntoh(&buffer[size - 2]); + if(signature != checkSum) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Calculated checksum: %04X, expected: %04X", checkSum, signature); + return(RADIOLIB_ERR_CHECKSUM_MISMATCH); + } + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::restore(uint16_t checkSum, uint16_t lwMode, uint8_t lwClass, uint8_t freqPlan) { // if already joined, ignore if(this->activeMode != RADIOLIB_LORAWAN_MODE_NONE) { - return(this->activeMode); + return(RADIOLIB_ERR_NONE); } - Module* mod = this->phyLayer->getMod(); + bool isSameKeys = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM]) == checkSum; + bool isSameMode = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE]) == lwMode; + bool isSameClass = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS]) == lwClass; + bool isSamePlan = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN]) == freqPlan; - uint8_t nvm_table_version = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_TABLE_VERSION_ID); - // if (RADIOLIB_EEPROM_LORAWAN_TABLE_VERSION > nvm_table_version) { - // // set default values for variables that are new or something - // } - (void)nvm_table_version; - - // check the mode value - uint16_t lwMode = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_MODE_ID); - if(lwMode == RADIOLIB_LORAWAN_MODE_NONE) { - #if RADIOLIB_DEBUG_PROTOCOL - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("mode value not set (no saved session)"); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("first 16 bytes of NVM:"); - uint8_t nvmBuff[16]; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(0), nvmBuff, 16); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(nvmBuff, 16); - #endif - // the mode value is not set, user will have to do perform the join procedure + // check if Nonces buffer matches the current configuration + if(!isSameKeys || !isSameMode || !isSameClass || !isSamePlan) { + // if configuration did not match, discard whatever is currently in the buffers and start fresh + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Configuration mismatch (checksum: %d, mode: %d, class: %d, plan: %d)", isSameKeys, isSameMode, isSameClass, isSamePlan); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Nonces buffer:"); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Clearing buffer and starting fresh"); + this->wipe(); return(RADIOLIB_ERR_NETWORK_NOT_JOINED); } - if(!this->isValidSession()) { + if(lwMode == RADIOLIB_LORAWAN_MODE_OTAA) { + // Nonces buffer is OK, so we can at least restore Nonces + this->devNonce = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE]); + this->joinNonce = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], 3); + } + + // uint8_t nvm_table_version = this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION]; + // if (RADIOLIB_LORAWAN_NONCES_VERSION_VAL > nvm_table_version) { + // // set default values for variables that are new or something + // } + + if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] == 0) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No active session in progress; please join the network"); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); return(RADIOLIB_ERR_NETWORK_NOT_JOINED); } // pull all authentication keys from persistent storage - this->devAddr = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID); - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); - - // get session parameters - this->rev = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_VERSION_ID); + this->devAddr = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR]); + memcpy(this->appSKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], RADIOLIB_AES128_BLOCK_SIZE); + memcpy(this->nwkSEncKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], RADIOLIB_AES128_BLOCK_SIZE); + memcpy(this->fNwkSIntKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE); + memcpy(this->sNwkSIntKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE); + + // restore session parameters + this->rev = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION]); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRaWAN session: v1.%d", this->rev); - this->devNonce = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID); - this->joinNonce = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID); - this->aFcntDown = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_A_FCNT_DOWN_ID); - this->nFcntDown = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_N_FCNT_DOWN_ID); - this->confFcntUp = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_UP_ID); - this->confFcntDown = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_DOWN_ID); - this->adrFcnt = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_ADR_FCNT_ID); + this->homeNetId = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID]); + this->aFcntDown = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN]); + this->nFcntDown = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN]); + this->confFcntUp = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP]); + this->confFcntDown = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN]); + this->adrFcnt = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT]); + this->fcntUp = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP]); - // fcntUp is stored in highly efficient wear-leveling system, so parse it - this->restoreFcntUp(); + int16_t state = RADIOLIB_ERR_UNKNOWN; - // get the defined channels - int16_t state = this->restoreChannels(); - RADIOLIB_ASSERT(state); + // for dynamic bands, first restore the defined channels before restoring ADR + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // restore the defined channels + state = this->restoreChannels(); + RADIOLIB_ASSERT(state); + } - // get MAC state + // restore the complete MAC state LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, + .cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, + .len = MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn, .repeat = 0, }; + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cmd.len); + (void)execMacCommand(&cmd); - // only apply the single ADR command on dynamic bands; fixed bands is done through channel restore - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); + cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], cmd.len); + (void)execMacCommand(&cmd); + + // for fixed bands, first restore ADR, then the defined channels + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + state = this->restoreChannels(); + RADIOLIB_ASSERT(state); } cmd.cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_DUTY_CYCLE_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], cmd.len); + (void)execMacCommand(&cmd); cmd.cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_RX_PARAM_SETUP_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], cmd.len); + (void)execMacCommand(&cmd); cmd.cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_RX_TIMING_SETUP_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); - - cmd.cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_TX_PARAM_SETUP_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], cmd.len); + (void)execMacCommand(&cmd); cmd.cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP].lenDn; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_ADR_PARAM_SETUP_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], cmd.len); + (void)execMacCommand(&cmd); cmd.cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP].lenDn; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_REJOIN_PARAM_SETUP_ID), cmd.payload, cmd.len); - execMacCommand(&cmd, false); + memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], cmd.len); + (void)execMacCommand(&cmd); - uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)] = { 0 }; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_MAC_QUEUE_UL_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); - memcpy(&this->commandsUp, queueBuff, sizeof(LoRaWANMacCommandQueue_t)); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Number of MAC commands: %d", this->commandsUp.numCommands); + // copy uplink MAC command queue back in place + memcpy(&this->commandsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], sizeof(LoRaWANMacCommandQueue_t)); state = this->setPhyProperties(); RADIOLIB_ASSERT(state); // full session is restored, so set joined flag to whichever mode is restored - this->activeMode = lwMode; - - return(this->activeMode); -} + this->activeMode = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE]); -void LoRaWANNode::restoreFcntUp() { - Module* mod = this->phyLayer->getMod(); - - uint8_t fcntBuffStart = mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID); - uint8_t fcntBuffEnd = mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID + 1); - uint8_t buffSize = fcntBuffEnd - fcntBuffStart; - #if RADIOLIB_STATIC_ONLY - uint8_t fcntBuff[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* fcntBuff = new uint8_t[buffSize]; - #endif - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID), fcntBuff, buffSize); - - // copy the two most significant bytes from the first two bytes - uint32_t bits_30_22 = (uint32_t)fcntBuff[0]; - uint32_t bits_22_14 = (uint32_t)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; - } - } - uint32_t bits_14_7 = (uint32_t)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 < buffSize; idx++) { - if(fcntBuff[idx] >> 7 != state) { - break; - } - } - uint32_t bits_7_0 = (uint32_t)fcntBuff[idx-1] & 0x7F; - #if !RADIOLIB_STATIC_ONLY - delete[] fcntBuff; - #endif - - this->fcntUp = (bits_30_22 << 22) | (bits_22_14 << 14) | (bits_14_7 << 7) | bits_7_0; + return(state); } int16_t LoRaWANNode::restoreChannels() { @@ -227,41 +261,32 @@ int16_t LoRaWANNode::restoreChannels() { Module* mod = this->phyLayer->getMod(); uint8_t bufferZeroes[5] = { 0 }; if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - uint8_t numBytesUp = RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - uint8_t bufferUp[RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), bufferUp, numBytesUp); - + uint8_t *startChannelsUp = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; + LoRaWANMacCommand_t cmd = { .cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, .payload = { 0 }, .len = 0, .repeat = 0 }; for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - memcpy(cmd.payload, &(bufferUp[i * cmd.len]), cmd.len); + memcpy(cmd.payload, startChannelsUp + (i * cmd.len), cmd.len); if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) { // only execute if it is not all zeroes cmd.repeat = 1; - (void)execMacCommand(&cmd, false); + (void)execMacCommand(&cmd); } } - uint8_t numBytesDn = RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn; - uint8_t bufferDn[RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID), bufferDn, numBytesDn); - + uint8_t *startChannelsDown = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS]; + cmd.cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; - 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); + memcpy(cmd.payload, startChannelsDown + (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); + (void)execMacCommand(&cmd); } } } else { // RADIOLIB_LORAWAN_BAND_FIXED - uint8_t numADRCommands = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Restoring %d stored channel masks", numADRCommands); - uint8_t numBytes = numADRCommands * MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - uint8_t buffer[RADIOLIB_LORAWAN_MAX_NUM_ADR_COMMANDS * RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), buffer, numBytes); - + uint8_t *startMACpayload = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; + LoRaWANMacCommand_t cmd = { .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, .payload = { 0 }, @@ -269,60 +294,21 @@ int16_t LoRaWANNode::restoreChannels() { .repeat = 0, }; - for(int i = 0; i < numADRCommands; i++) { + // there are at most 8 channel masks present + for(int i = 0; i < 8; i++) { cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - memcpy(cmd.payload, &buffer[i * cmd.len], cmd.len); + memcpy(cmd.payload, startMACpayload + (i * cmd.len), cmd.len); // there COULD, according to spec, be an all zeroes ADR command - meh - if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) { - cmd.repeat = (i+1); - execMacCommand(&cmd, false); + if(memcmp(cmd.payload, bufferZeroes, cmd.len) == 0) { + break; } + cmd.repeat = (i+1); + (void)execMacCommand(&cmd); } } return(RADIOLIB_ERR_NONE); } -void LoRaWANNode::clearSession() { - Module* mod = this->phyLayer->getMod(); - uint8_t zeroes[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID), zeroes, sizeof(uint32_t)); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID), zeroes, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID), zeroes, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID), zeroes, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID), zeroes, RADIOLIB_AES128_BLOCK_SIZE); - this->activeMode = RADIOLIB_LORAWAN_MODE_NONE; -} - -bool LoRaWANNode::isValidSession() { - uint8_t mask = 0; - Module* mod = this->phyLayer->getMod(); - uint8_t dummyBuf[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID), dummyBuf, sizeof(uint32_t)); - for(size_t i = 0; i < sizeof(uint32_t); i++) { - mask |= dummyBuf[i]; - } - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID), dummyBuf, RADIOLIB_AES128_BLOCK_SIZE); - for(size_t i = 0; i < RADIOLIB_AES128_BLOCK_SIZE; i++) { - mask |= dummyBuf[i]; - } - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID), dummyBuf, RADIOLIB_AES128_BLOCK_SIZE); - for(size_t i = 0; i < RADIOLIB_AES128_BLOCK_SIZE; i++) { - mask |= dummyBuf[i]; - } - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID), dummyBuf, RADIOLIB_AES128_BLOCK_SIZE); - for(size_t i = 0; i < RADIOLIB_AES128_BLOCK_SIZE; i++) { - mask |= dummyBuf[i]; - } - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID), dummyBuf, RADIOLIB_AES128_BLOCK_SIZE); - for(size_t i = 0; i < RADIOLIB_AES128_BLOCK_SIZE; i++) { - mask |= dummyBuf[i]; - } - - return(mask > 0); -} - -#endif // RADIOLIB_EEPROM_UNSUPPORTED - void LoRaWANNode::beginCommon(uint8_t joinDr) { // in case a new session is started while there is an ongoing session // clear the MAC queues completely @@ -433,54 +419,36 @@ void LoRaWANNode::beginCommon(uint8_t joinDr) { (void)execMacCommand(&cmd); } -int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, uint8_t joinDr, bool force) { +int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force, uint8_t joinDr) { // if not forced and already joined, don't do anything if(!force && this->isJoined()) { - return(this->activeMode); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("beginOTAA(): Did not rejoin: session already active"); + return(RADIOLIB_ERR_NONE); } - - // check if we actually need to send the join request - Module* mod = this->phyLayer->getMod(); -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // generate activation key checksum uint16_t checkSum = 0; checkSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&joinEUI), 8); checkSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&devEUI), 8); checkSum ^= LoRaWANNode::checkSum16(nwkKey, 16); checkSum ^= LoRaWANNode::checkSum16(appKey, 16); - bool isValidCheckSum = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID) == checkSum; - bool isValidMode = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_MODE_ID) == RADIOLIB_LORAWAN_MODE_OTAA; - - if(isValidCheckSum && isValidMode) { - // if not forced and a valid session is stored, restore it - if(!force && this->isValidSession()) { - return(this->restore()); - } - // either forced or no active session (a join was issued previously but didn't result in an active session) - this->clearSession(); - // the credentials are still the same, so restore the DevNonce and JoinNonce - this->devNonce = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID); - this->joinNonce = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID); - } else { - // either invalid key checksum or mode, so wipe either way - #if RADIOLIB_DEBUG_PROTOCOL - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Didn't restore session (checksum: %d, mode: %d)", isValidCheckSum, isValidMode); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("First 16 bytes of NVM:"); - uint8_t nvmBuff[16]; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(0), nvmBuff, 16); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(nvmBuff, 16); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Wiping EEPROM and starting a clean session"); - #endif - - this->wipe(); + // if The Force is used, disable the active session; + // as a result, restore() will only restore Nonces if they are available, not the session + if(force) { + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; } -#else - (void)force; -#endif - int16_t state = RADIOLIB_ERR_NONE; + state = this->restore(checkSum, RADIOLIB_LORAWAN_MODE_OTAA, RADIOLIB_LORAWAN_CLASS_A, this->band->bandNum); + + if(!force) { + return(state); + } + Module* mod = this->phyLayer->getMod(); + // setup join-request uplink/downlink frequencies and datarates if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { state = this->setupChannelsDyn(true); @@ -509,9 +477,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // increment devNonce as we are sending another join-request this->devNonce += 1; -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID, this->devNonce); -#endif + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce); // build the join-request message uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; @@ -710,28 +676,32 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe this->confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; this->adrFcnt = 0; -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) // save the activation keys checksum, device address & keys as well as JoinAccept values; these are only ever set when joining - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_TABLE_VERSION_ID, RADIOLIB_EEPROM_TABLE_VERSION); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID, checkSum); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_MODE_ID, RADIOLIB_LORAWAN_MODE_OTAA); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID, this->joinNonce); -#endif - + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], RADIOLIB_LORAWAN_MODE_OTAA); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], RADIOLIB_LORAWAN_CLASS_A); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], checkSum); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], this->joinNonce, 3); + + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; this->activeMode = RADIOLIB_LORAWAN_MODE_OTAA; + // generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature); + 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 not forced and already joined, don't do anything if(!force && this->isJoined()) { - return(this->activeMode); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("beginABP(): Did not rejoin: session already active"); + return(RADIOLIB_ERR_NONE); } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - // only needed for persistent storage - Module* mod = this->phyLayer->getMod(); + int16_t state = RADIOLIB_ERR_UNKNOWN; // check if we actually need to restart from a clean session uint16_t checkSum = 0; @@ -741,32 +711,17 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, if(fNwkSIntKey) { checkSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, 16); } if(sNwkSIntKey) { checkSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, 16); } - bool isValidCheckSum = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID) == checkSum; - bool isValidMode = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_MODE_ID) == RADIOLIB_LORAWAN_MODE_ABP; - - if(isValidCheckSum && isValidMode) { - // if not forced and a valid session is stored, restore it - if(!force && this->isValidSession()) { - return(this->restore()); - } - // either forced or no active session (a join was issued previously but didn't result in an active session) - this->clearSession(); - } else { - // either invalid key checksum or mode, so wipe either way - #if RADIOLIB_DEBUG_PROTOCOL - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Didn't restore session (checksum: %d, mode: %d)", isValidCheckSum, isValidMode); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("First 16 bytes of NVM:"); - uint8_t nvmBuff[16]; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(0), nvmBuff, 16); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(nvmBuff, 16); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Wiping EEPROM and starting a clean session"); - #endif + // if The Force is used, disable the active session; + // as a result, restore() will not restore the session (and there are no Nonces in ABP mode) + if(force) { + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; + } - this->wipe(); + state = this->restore(checkSum, RADIOLIB_LORAWAN_MODE_ABP, RADIOLIB_LORAWAN_CLASS_A, this->band->bandNum); + + if(!force) { + return(state); } -#else - (void)force; -#endif this->devAddr = addr; memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); @@ -781,8 +736,6 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } - int16_t state = RADIOLIB_ERR_NONE; - // setup the uplink/downlink channels and initial datarate if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { this->setupChannelsDyn(); @@ -805,15 +758,20 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, this->confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; this->adrFcnt = 0; -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - // save the activation keys checksum, device address & keys - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_TABLE_VERSION_ID, RADIOLIB_EEPROM_TABLE_VERSION); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID, checkSum); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_MODE_ID, RADIOLIB_LORAWAN_MODE_ABP); -#endif + // save the activation keys checksum, mode, class, frequency plan + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], RADIOLIB_LORAWAN_MODE_ABP); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], RADIOLIB_LORAWAN_CLASS_A); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], checkSum); + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; this->activeMode = RADIOLIB_LORAWAN_MODE_ABP; + // generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature); + return(RADIOLIB_ERR_NONE); } @@ -821,110 +779,42 @@ bool LoRaWANNode::isJoined() { return(this->activeMode != RADIOLIB_LORAWAN_MODE_NONE); } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) int16_t LoRaWANNode::saveSession() { Module* mod = this->phyLayer->getMod(); // store DevAddr and all keys - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID, this->devAddr); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR], this->devAddr); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + + // copy the signature of the Nonces buffer over to the Session buffer + uint16_t noncesSignature = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE]); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], noncesSignature); // store network parameters - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_HOME_NET_ID, this->homeNetId); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_VERSION_ID, this->rev); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID], this->homeNetId); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev); // store all frame counters - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_A_FCNT_DOWN_ID, this->aFcntDown); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_N_FCNT_DOWN_ID, this->nFcntDown); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_UP_ID, this->confFcntUp); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_DOWN_ID, this->confFcntDown); - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_ADR_FCNT_ID, this->adrFcnt); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN], this->aFcntDown); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN], this->nFcntDown); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP], this->confFcntUp); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN], this->confFcntDown); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT], this->adrFcnt); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP], this->fcntUp); - // fcntUp is saved using highly efficient wear-leveling as this is by far going to be written most often - this->saveFcntUp(); + // save the current uplink MAC command queue + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); - // 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_EEPROM_LORAWAN_MAC_QUEUE_UL_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_EEPROM_LORAWAN_MAC_QUEUE_UL_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); - } + // generate the signature of the Session buffer, and store it in the last two bytes of the Session buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferSession, RADIOLIB_LORAWAN_SESSION_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SIGNATURE], signature); return(RADIOLIB_ERR_NONE); } -void LoRaWANNode::saveFcntUp() { - Module* mod = this->phyLayer->getMod(); - - uint8_t fcntBuffStart = mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID); - uint8_t fcntBuffEnd = mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID + 1); - uint8_t buffSize = fcntBuffEnd - fcntBuffStart; - #if RADIOLIB_STATIC_ONLY - uint8_t fcntBuff[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* fcntBuff = new uint8_t[buffSize]; - #endif - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID), fcntBuff, buffSize); - - // 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_EEPROM_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_EEPROM_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; - } - } - // check if the last written byte is equal to current, only rewrite if different - uint8_t bits_14_7 = (this->fcntUp >> 7) & 0x7F; - if((fcntBuff[idx - 1] & 0x7F) != bits_14_7) { - // find next index to write - idx = idx < 5 ? idx : 2; - - // 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_EEPROM_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 < buffSize; idx++) { - if(fcntBuff[idx] >> 7 != state) { - break; - } - } - idx = idx < buffSize ? 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_EEPROM_LORAWAN_FCNT_UP_ID, bits_7_0, idx); - -} -#endif // RADIOLIB_EEPROM_UNSUPPORTED - #if defined(RADIOLIB_BUILD_ARDUINO) int16_t LoRaWANNode::uplink(String& str, uint8_t port, bool isConfirmed, LoRaWANEvent_t* event) { return(this->uplink(str.c_str(), port, isConfirmed, event)); @@ -968,7 +858,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf this->isMACPayload = false; } - int16_t state = RADIOLIB_ERR_NONE; + int16_t state = RADIOLIB_ERR_UNKNOWN; // check if there are some MAC commands to piggyback (only when piggybacking onto a application-frame) uint8_t foptsLen = 0; @@ -1004,7 +894,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf // 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); + state = this->setTxPower(this->txPowerMax); if(state == RADIOLIB_ERR_NONE) { this->txPowerCur = 0; adrStage = 0; // successfully did some ADR stuff @@ -1018,7 +908,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf 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) { + if(this->setDatarate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] - 1) == RADIOLIB_ERR_NONE) { adrStage = 0; // successfully did some ADR stuff } } @@ -1154,9 +1044,8 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FcntUp: %d", this->fcntUp); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink (FcntUp = %d) decoded:", this->fcntUp); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("uplinkMsg pre-MIC:"); RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(uplinkMsg, uplinkMsgLen); // calculate authentication codes @@ -1322,7 +1211,7 @@ int16_t LoRaWANNode::downlinkCommon() { #if defined(RADIOLIB_BUILD_ARDUINO) int16_t LoRaWANNode::downlink(String& str, LoRaWANEvent_t* event) { - int16_t state = RADIOLIB_ERR_NONE; + int16_t state = RADIOLIB_ERR_UNKNOWN; // build a temporary buffer // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL @@ -1344,7 +1233,7 @@ int16_t LoRaWANNode::downlink(String& str, LoRaWANEvent_t* event) { #endif int16_t LoRaWANNode::downlink(LoRaWANEvent_t* event) { - int16_t state = RADIOLIB_ERR_NONE; + int16_t state = RADIOLIB_ERR_UNKNOWN; // build a temporary buffer // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL @@ -1364,7 +1253,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) // get the packet length size_t downlinkMsgLen = this->phyLayer->getPacketLength(); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink message length: %d", downlinkMsgLen); // check the minimum required frame length // an extra byte is subtracted because downlink frames may not have a port @@ -1414,15 +1302,10 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFcntUp); } - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("downlinkMsg:"); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); - // calculate length of FOpts and payload uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; int payLen = downlinkMsgLen - 8 - foptsLen - sizeof(uint32_t); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FOpts: %02X", downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS]); - // in LoRaWAN v1.1, a frame can be a network frame if there is no Application payload // i.e., no payload at all (empty frame or FOpts only), or MAC only payload (FPort = 0) // TODO "NFCntDown is used for MAC communication on port 0 and when the FPort field is missing" @@ -1439,7 +1322,9 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) isAppDownlink = false; } } - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FOptsLen: %d", foptsLen); + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFcntDown = %d) encoded:", isAppDownlink ? "A" : "N", fcnt16); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); // check the FcntDown value (Network or Application) uint32_t fcntDownPrev = 0; @@ -1449,8 +1334,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) fcntDownPrev = this->nFcntDown; } - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("fcnt: %d, fcntPrev: %d, isAppDownlink: %d", fcnt16, fcntDownPrev, (int)isAppDownlink); - // if this is not the first downlink... // assume a 16-bit to 32-bit rollover if difference between counters in LSB is smaller than MAX_FCNT_GAP // if that isn't the case and the received fcnt is smaller or equal to the last heard fcnt, then error @@ -1522,9 +1405,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); } - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("fopts:"); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fopts, foptsLen); - bool hasADR = false; uint8_t numADR = 0; uint8_t lastCID = 0; @@ -1557,8 +1437,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) .repeat = (cid == RADIOLIB_LORAWAN_MAC_LINK_ADR ? numADR : (uint8_t)0), }; memcpy(cmd.payload, foptsPtr + 1, macLen); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[%02X]: %02X %02X %02X %02X %02X (%d)", - cmd.cid, cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3], cmd.payload[4], cmd.len); // process the MAC command bool sendUp = execMacCommand(&cmd); @@ -1570,18 +1448,12 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) remLen -= (macLen + 1); foptsPtr += (macLen + 1); lastCID = cid; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processed: %d, remaining: %d", (macLen + 1), remLen); } #if !RADIOLIB_STATIC_ONLY delete[] fopts; #endif - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MAC response:"); - for (int i = 0; i < this->commandsUp.numCommands; i++) { - RADIOLIB_DEBUG_PROTOCOL_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 if(this->commandsUp.len > 15) { size_t foptsBufSize = this->commandsUp.len; @@ -1833,19 +1705,21 @@ int16_t LoRaWANNode::setupChannelsDyn(bool joinRequest) { } for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %d %d %6.3f (%d - %d) | DL: %d %d %6.3f (%d - %d)", - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax - ); + if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, + + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax + ); + } } return(RADIOLIB_ERR_NONE); @@ -1855,6 +1729,12 @@ int16_t LoRaWANNode::setupChannelsDyn(bool joinRequest) { // WARNING: subBand starts at 1 (corresponds to all populair schemes) int16_t LoRaWANNode::setupChannelsFix(uint8_t subBand) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up fixed channels (subband %d)", subBand); + + // clear all existing channels + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + // randomly select one of 8 or 9 channels and find corresponding datarate uint8_t numChannels = this->band->numTxSpans == 1 ? 8 : 9; uint8_t rand = this->phyLayer->random(numChannels) + 1; // range 1-8 or 1-9 @@ -1870,49 +1750,27 @@ int16_t LoRaWANNode::setupChannelsFix(uint8_t subBand) { uint8_t numBanks8 = this->band->txSpans[0].numChannels / 8; subBand = this->devNonce % numBanks8; } - - // chMask is set for 16 channels at once, so widen the Cntl value - uint8_t chMaskCntl = (subBand - 1) / 2; // compensate the 1 offset - - uint8_t numADR = 1; - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; + uint8_t chMaskCntl = 0; + uint16_t chMask = 0; + // if there are two channel spans, first set the channel from second span if(this->band->numTxSpans == 2) { - cmd.payload[0] = (drJR << 4); // set join-request datarate - cmd.payload[0] |= 0; // set Tx power to maximum - // enable channel that belongs to this subband - cmd.payload[1] = (1 << (subBand - 1)); // set channel mask - cmd.payload[2] = 0; - cmd.payload[3] = (7 << 4); // set the chMaskCntl value to all channels off - cmd.payload[3] |= 0; // keep NbTrans the same - cmd.repeat = numADR++; - (void)execMacCommand(&cmd, false); + chMaskCntl = 7; + chMask = (1 << (subBand - 1)); // set channel mask + this->applyChannelMaskFix(chMaskCntl, chMask); } - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - cmd.payload[0] = (drJR << 4); // set join-request datarate - cmd.payload[0] |= 0; // set Tx power to maximum + // chMask is set for 16 channels at once, so widen the Cntl value + chMaskCntl = (subBand - 1) / 2; // compensate the 1 offset + // now select the correct bank of 8 channels - // 0x00 0xFF channel mask for subband = 2, 4.. (even) - // 0xFF 0x00 channel mask for subband = 1, 3.. (odd) - if(subBand % 2 == 0) { - cmd.payload[1] = 0x00; - cmd.payload[2] = 0xFF; + if(subBand % 2 == 0) { // even subbands + chMask = 0xFF00; } else { - cmd.payload[1] = 0xFF; - cmd.payload[2] = 0x00; + chMask = 0x00FF; // odd subbands } - cmd.payload[3] = (chMaskCntl << 4); // set the chMaskCntl value - cmd.payload[3] |= 0; // keep NbTrans the same - cmd.repeat = numADR++; - (void)execMacCommand(&cmd, false); + this->applyChannelMaskFix(chMaskCntl, chMask); return(RADIOLIB_ERR_NONE); } @@ -2016,7 +1874,7 @@ int16_t LoRaWANNode::selectChannels() { return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::setDatarate(uint8_t drUp, bool saveToEeprom) { +int16_t LoRaWANNode::setDatarate(uint8_t drUp) { // 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++) { @@ -2043,7 +1901,7 @@ int16_t LoRaWANNode::setDatarate(uint8_t drUp, bool saveToEeprom) { 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); + (void)execMacCommand(&cmd); // check if ACK is set for Tx Power if((cmd.payload[0] >> 1) != 1) { @@ -2116,7 +1974,7 @@ uint8_t LoRaWANNode::maxPayloadDwellTime() { return(payLen - 13); // fixed 13-byte header } -int16_t LoRaWANNode::setTxPower(int8_t txPower, bool saveToEeprom) { +int16_t LoRaWANNode::setTxPower(int8_t txPower) { // only allow values within the band's (or MAC state) maximum if(txPower > this->txPowerMax) { return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); @@ -2136,7 +1994,7 @@ int16_t LoRaWANNode::setTxPower(int8_t txPower, bool saveToEeprom) { cmd.payload[0] |= numSteps; // 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); + (void)execMacCommand(&cmd); // check if ACK is set for Tx Power if((cmd.payload[0] >> 2) != 1) { @@ -2264,14 +2122,12 @@ int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* que return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); } -bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len); - - Module* mod = this->phyLayer->getMod(); -#if defined(RADIOLIB_EEPROM_UNSUPPORTED) - (void)saveToEeprom; - (void)mod; -#endif +bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { + RADIOLIB_DEBUG_PROTOCOL_PRINT("[MAC] 0x%02X %s", cmd->cid, cmd->len ? "= 0x" : ""); + for(uint8_t i = 0; i < cmd->len; i++) { + RADIOLIB_DEBUG_PROTOCOL_PRINT("%02X", cmd->payload[i]); + } + RADIOLIB_DEBUG_PROTOCOL_PRINTLN(); if(cmd->cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) { // TODO call user-provided callback for proprietary MAC commands? @@ -2282,7 +2138,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { case(RADIOLIB_LORAWAN_MAC_RESET): { // get the server version uint8_t srvVersion = cmd->payload[0]; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Server version: 1.%d", srvVersion); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ResetConf: server version 1.%d", srvVersion); if(srvVersion == this->rev) { // valid server version, stop sending the ResetInd MAC command deleteMacCommand(RADIOLIB_LORAWAN_MAC_RESET, &this->commandsUp); @@ -2291,6 +2147,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { } break; case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]"); // delete any existing response (does nothing if there is none) deleteMacCommand(RADIOLIB_LORAWAN_MAC_LINK_CHECK, &this->commandsDown); @@ -2306,10 +2163,12 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { // but we don't bother and try to set each individual command uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4; uint8_t txPower = cmd->payload[0] & 0x0F; + bool isInternalTxDr = cmd->payload[3] >> 7; + uint16_t chMask = LoRaWANNode::ntoh(&cmd->payload[1]); uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4; uint8_t nbTrans = cmd->payload[3] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %02x, nbTrans = %d", drUp, txPower, chMask, chMaskCntl, nbTrans); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRReq: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %d, nbTrans = %d", drUp, txPower, chMask, chMaskCntl, nbTrans); // apply the configuration uint8_t drAck = 0; @@ -2347,6 +2206,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { } else { int8_t pwr = this->txPowerMax - 2*txPower; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: TX = %d dBm", pwr); 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 @@ -2362,8 +2222,8 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { uint8_t chMaskAck = 1; // only apply channel mask when the RFU bit is not set - // (which is set on the internal MAC command when creating new session) - if((cmd->payload[3] >> 7) == 0) { + // (which is only set in internal MAC commands for changing Tx/Dr) + if(!isInternalTxDr) { if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { chMaskAck = (uint8_t)this->applyChannelMaskDyn(chMaskCntl, chMask); @@ -2372,13 +2232,17 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { if(cmd->repeat == 1) { // if this is the first ADR command in the queue, clear all saved channels // so we can apply the new channel mask - clearChannels = true; RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR mask: clearing channels"); + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + // clear all previous channel masks + memset(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS], 0, 16*8); } else { // if this is not the first ADR command, clear the ADR response that was in the queue (void)deleteMacCommand(RADIOLIB_LORAWAN_MAC_LINK_ADR, &this->commandsUp); } - chMaskAck = (uint8_t)this->applyChannelMaskFix(chMaskCntl, chMask, clearChannels); + chMaskAck = (uint8_t)this->applyChannelMaskFix(chMaskCntl, chMask); } } @@ -2389,66 +2253,58 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { this->nbTrans = nbTrans; } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { // if RFU bit is set, this is just a change in Datarate or TxPower, so read ADR command and overwrite first byte - if((cmd->payload[3] >> 7) == 1) { - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID) + 1, &(cmd->payload[1]), 3); + if(isInternalTxDr) { + memcpy(&(cmd->payload[1]), &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR] + 1, 3); } + // if there was no channel mask (all zeroes), we should never apply that channel mask, so set RFU bit again if(cmd->payload[1] == 0 && cmd->payload[2] == 0) { cmd->payload[3] |= (1 << 7); } // save to the single ADR MAC location - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID), &(cmd->payload[0]), payLen); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], &(cmd->payload[0]), cmd->len); } else { // RADIOLIB_LORAWAN_BAND_FIXED - // if RFU bit is set, this is just a change in Datarate or TxPower - // so read bytes 1..3 from last stored ADR command into the current MAC payload and re-store it - if((cmd->payload[3] >> 7) == 1) { - // read how many ADR masks are already stored - uint8_t numMacADR = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID); - if(numMacADR > 0) { - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (numMacADR - 1) * payLen + 1, &(cmd->payload[1]), 3); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (numMacADR - 1) * payLen, &(cmd->payload[0]), payLen); - } + + // save Tx/Dr to the Link ADR position in the session buffer + uint8_t bufTxDr[cmd->len] = { 0 }; + bufTxDr[0] = cmd->payload[0]; + bufTxDr[3] = 1 << 7; + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], bufTxDr, cmd->len); - } else { - // save to the uplink channel location, to the cmd->repeat-th slot of 4 bytes - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (cmd->repeat - 1) * payLen, &(cmd->payload[0]), payLen); - // saved an ADR mask, so re-store counter - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, cmd->repeat); + // if RFU bit is set, this is just a change in Datarate or TxPower, in which case we don't save the channel masks + // if the RFU bit is not set, we must save this channel mask + if(!isInternalTxDr) { + // save the channel mask to the uplink channels position in session buffer, with Tx and DR set to 'same' + cmd->payload[0] = 0xFF; + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + (cmd->repeat - 1) * cmd->len, cmd->payload, cmd->len); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Saving mask to ULChannels[%d]:", (cmd->repeat - 1) * cmd->len); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + (cmd->repeat - 1) * cmd->len, cmd->len); } + } - } -#endif // send the reply cmd->len = 1; cmd->payload[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0); cmd->repeat = 0; // discard any repeat value that may have been set - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR ANS: status = 0x%02x", cmd->payload[0]); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRAns: status = 0x%02x", cmd->payload[0]); return(true); } break; case(RADIOLIB_LORAWAN_MAC_DUTY_CYCLE): { uint8_t maxDutyCycle = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Max duty cycle: 1/2^%d", maxDutyCycle); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle); if(maxDutyCycle == 0) { this->dutyCycle = this->band->dutyCycle; } else { this->dutyCycle = (uint32_t)60 * (uint32_t)60 * (uint32_t)1000 / (uint32_t)(1UL << maxDutyCycle); } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_DUTY_CYCLE_ID, cmd->payload[0]); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], cmd->payload, cmd->len); cmd->len = 0; return(true); @@ -2462,7 +2318,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { uint8_t rx2Ack = 1; uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); this->rx2.freq = (float)freqRaw/10000.0; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Rx param REQ: rx1DrOffset = %d, rx2DataRate = %d, freq = %f", this->rx1DrOffset, this->rx2.drMax, this->rx2.freq); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupReq: rx1DrOffset = %d, rx2DataRate = %d, freq = %f", this->rx1DrOffset, this->rx2.drMax, this->rx2.freq); // apply the configuration uint8_t chanAck = 0; @@ -2471,29 +2327,24 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn; - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_RX_PARAM_SETUP_ID), &(cmd->payload[0]), payLen); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], cmd->payload, cmd->len); // TODO this should be sent repeatedly until the next downlink cmd->len = 1; cmd->payload[0] = (rx1OffsAck << 2) | (rx2Ack << 1) | (chanAck << 0); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Rx param ANS: status = 0x%02x", cmd->payload[0]); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupAns: status = 0x%02x", cmd->payload[0]); return(true); } break; case(RADIOLIB_LORAWAN_MAC_DEV_STATUS): { // set the uplink reply + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusReq"); cmd->len = 2; cmd->payload[1] = this->battLevel; int8_t snr = this->phyLayer->getSNR(); cmd->payload[0] = snr & 0x3F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatus ANS: status = 0x%02x%02x", cmd->payload[0], cmd->payload[1]); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", cmd->payload[0], cmd->payload[1]); return(true); } break; @@ -2504,7 +2355,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { float freq = (float)freqRaw/10000.0; uint8_t maxDr = (cmd->payload[4] & 0xF0) >> 4; uint8_t minDr = cmd->payload[4] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("New channel: index = %d, freq = %f MHz, maxDr = %d, minDr = %d", chIndex, freq, maxDr, minDr); + uint8_t newChAck = 0; uint8_t freqAck = 0; @@ -2524,7 +2375,8 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); } - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %d %d %6.3f (%d - %d) | DL: %d %d %6.3f (%d - %d)", + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("NewChannelReq:"); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].enabled, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].freq, @@ -2538,20 +2390,12 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].drMax ); -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - // save to uplink channels location, to the chIndex-th slot of 5 bytes - uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Saving channel:"); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(&(cmd->payload[0]), payLen); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + chIndex * payLen, &(cmd->payload[0]), payLen); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + chIndex * cmd->len, cmd->payload, cmd->len); // send the reply cmd->len = 1; cmd->payload[0] = (newChAck << 1) | (freqAck << 0); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("NewChannelAns: status = 0x%02x", cmd->payload[0]); return(true); } break; @@ -2561,7 +2405,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { uint8_t chIndex = cmd->payload[0]; uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); float freq = (float)freqRaw/10000.0; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DL channel: index = %d, freq = %f MHz", chIndex, freq); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelReq: index = %d, freq = %f MHz", chIndex, freq); uint8_t freqDlAck = 0; uint8_t freqUlAck = 0; @@ -2582,18 +2426,12 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { } } -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - // save to downlink channels location, to the chIndex-th slot of 4 bytes - uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn; - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID) + chIndex * payLen, &(cmd->payload[0]), payLen); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS] + chIndex * cmd->len, cmd->payload, cmd->len); // TODO send this repeatedly until a downlink is received cmd->len = 1; cmd->payload[0] = (freqUlAck << 1) | (freqDlAck << 0); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelAns: status = 0x%02x", cmd->payload[0]); return(true); } break; @@ -2601,7 +2439,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): { // get the configuration uint8_t delay = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RX timing: delay = %d sec", delay); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay); // apply the configuration if(delay == 0) { @@ -2610,12 +2448,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { this->rxDelays[0] = delay * 1000; this->rxDelays[1] = this->rxDelays[0] + 1000; -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_RX_TIMING_SETUP_ID, cmd->payload[0]); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], cmd->payload, cmd->len); // send the reply cmd->len = 0; @@ -2632,7 +2465,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { // who the f came up with this ... const uint8_t eirpEncoding[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 }; this->txPowerMax = eirpEncoding[maxEirpRaw]; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("TX timing: dlDwell = %d, ulDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, this->txPowerMax); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("TxParamSetupReq: dlDwell = %d, ulDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, eirpEncoding[maxEirpRaw]); this->dwellTimeEnabledUp = ulDwell ? true : false; this->dwellTimeUp = ulDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; @@ -2640,12 +2473,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { this->dwellTimeEnabledDn = dlDwell ? true : false; this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_TX_PARAM_SETUP_ID, cmd->payload[0]); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cmd->payload, cmd->len); cmd->len = 0; return(true); @@ -2654,7 +2482,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { case(RADIOLIB_LORAWAN_MAC_REKEY): { // get the server version uint8_t srvVersion = cmd->payload[0]; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Server version: 1.%d", srvVersion); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RekeyConf: server version = 1.%d", srvVersion); if((srvVersion > 0) && (srvVersion <= this->rev)) { // valid server version, stop sending the ReKey MAC command deleteMacCommand(RADIOLIB_LORAWAN_MAC_REKEY, &this->commandsUp); @@ -2665,20 +2493,16 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): { this->adrLimitExp = (cmd->payload[0] & 0xF0) >> 4; this->adrDelayExp = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR param setup: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_ADR_PARAM_SETUP_ID, cmd->payload[0]); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], cmd->payload, cmd->len); cmd->len = 0; return(true); } break; case(RADIOLIB_LORAWAN_MAC_DEVICE_TIME): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DeviceTimeAns: [user]"); // delete any existing response (does nothing if there is none) deleteMacCommand(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, &this->commandsDown); @@ -2689,12 +2513,12 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): { // TODO implement this - uint16_t rejoinReq = LoRaWANNode::ntoh(&cmd->payload[0]); + uint16_t rejoinReq = LoRaWANNode::ntoh(cmd->payload); uint8_t period = (rejoinReq & 0x3800) >> 11; uint8_t maxRetries = (rejoinReq & 0x0700) >> 8; uint8_t rejoinType = (rejoinReq & 0x0070) >> 4; uint8_t dr = rejoinReq & 0x000F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Force rejoin: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); (void)period; (void)maxRetries; (void)rejoinType; @@ -2706,17 +2530,13 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { // TODO implement this uint8_t maxTime = (cmd->payload[0] & 0xF0) >> 4; uint8_t maxCount = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Rejoin setup: maxTime = %d, maxCount = %d", maxTime, maxCount); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount); -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - if(saveToEeprom) { - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_REJOIN_PARAM_SETUP_ID, cmd->payload[0]); - - } -#endif + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], cmd->payload, cmd->len); cmd->len = 0; cmd->payload[0] = (1 << 1) | 1; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupAns: status = 0x%02x", cmd->payload[0]); (void)maxTime; (void)maxCount; @@ -2751,31 +2571,29 @@ bool LoRaWANNode::applyChannelMaskDyn(uint8_t chMaskCntl, uint16_t chMask) { } for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %d %d %6.3f (%d - %d) | DL: %d %d %6.3f (%d - %d)", - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax - ); + if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, + + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax + ); + } } return(true); } -bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask, bool clear) { +bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask); - if(clear) { - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; - } - } + // find out how many channels have already been configured uint8_t idx = 0; for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { @@ -2831,7 +2649,7 @@ bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask, bool uint16_t mask = 1 << i; if(mask & chMask) { // enable bank of 8 channels from first span - for(uint8_t j = 0; j < 8; i++) { + for(uint8_t j = 0; j < 8; j++) { uint8_t chNum = i * 8 + j; chnl.enabled = true; chnl.idx = chNum; @@ -2900,19 +2718,21 @@ bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask, bool } for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %d %d %6.3f (%d - %d) | DL: %d %d %6.3f (%d - %d)", - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax - ); + if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, + + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax + ); + } } return(true); @@ -2974,7 +2794,7 @@ void LoRaWANNode::performCSMA() { bool channelFreeDuringDIFS = true; for (uint8_t i = 0; i < this->difsSlots; i++) { if (performCAD()) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("OCCUPIED CHANNEL DURING DIFS"); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Occupied channel during DIFS"); channelFreeDuringDIFS = false; // Channel is occupied during DIFS, hop to another. this->selectChannels(); @@ -2986,7 +2806,7 @@ void LoRaWANNode::performCSMA() { // Continue decrementing BO with per each CAD reporting free channel. while (BO > 0) { if (performCAD()) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("OCCUPIED CHANNEL DURING BO"); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Occupied channel during BO"); // Channel is busy during CAD, hop to another and return to DIFS state again. this->selectChannels(); break; // Exit loop. Go back to DIFS state. @@ -3045,16 +2865,14 @@ void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out } } -uint16_t LoRaWANNode::checkSum16(uint8_t *key, uint8_t keyLen) { - if(keyLen > RADIOLIB_AES128_KEY_SIZE / 2) { - keyLen = RADIOLIB_AES128_KEY_SIZE / 2; - } - uint16_t buf16[RADIOLIB_AES128_KEY_SIZE / 2] = { 0 }; - uint8_t bufLen = keyLen / 2; - memcpy(buf16, key, keyLen); +uint16_t LoRaWANNode::checkSum16(uint8_t *key, uint16_t keyLen) { uint16_t checkSum = 0; - for(int i = 0; i < bufLen; i++) { - checkSum ^= buf16[i]; + for(uint16_t i = 0; i < keyLen; i += 2) { + checkSum ^= ((uint16_t)key[i] << 8) | key[i + 1]; + } + if(keyLen % 2 == 1) { + uint16_t val = ((uint16_t)key[keyLen - 1] << 8); + checkSum ^= val; } return(checkSum); } diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 44941a4de..9c90202b8 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -6,14 +6,14 @@ #include "../../utils/Cryptography.h" // activation mode -#define RADIOLIB_LORAWAN_MODE_OTAA (0x01AA) +#define RADIOLIB_LORAWAN_MODE_OTAA (0x07AA) #define RADIOLIB_LORAWAN_MODE_ABP (0x0AB9) #define RADIOLIB_LORAWAN_MODE_NONE (0x0000) // operation mode -#define RADIOLIB_LORAWAN_CLASS_A (0x00) -#define RADIOLIB_LORAWAN_CLASS_B (0x01) -#define RADIOLIB_LORAWAN_CLASS_C (0x02) +#define RADIOLIB_LORAWAN_CLASS_A (0x0A) +#define RADIOLIB_LORAWAN_CLASS_B (0x0B) +#define RADIOLIB_LORAWAN_CLASS_C (0x0C) // preamble format #define RADIOLIB_LORAWAN_LORA_SYNC_WORD (0x34) @@ -226,6 +226,56 @@ const LoRaWANMacSpec_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS + 1] = { { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, true } }; +#define RADIOLIB_LORAWAN_NONCES_VERSION_VAL (0x0001) + +enum LoRaWANSchemeBase_t { + RADIOLIB_LORAWAN_NONCES_VERSION = 0x00, // 2 bytes + RADIOLIB_LORAWAN_NONCES_MODE = 0x02, // 2 bytes + RADIOLIB_LORAWAN_NONCES_CLASS = 0x04, // 1 byte + RADIOLIB_LORAWAN_NONCES_PLAN = 0x05, // 1 byte + RADIOLIB_LORAWAN_NONCES_CHECKSUM = 0x06, // 2 bytes + RADIOLIB_LORAWAN_NONCES_DEV_NONCE = 0x08, // 2 bytes + RADIOLIB_LORAWAN_NONCES_JOIN_NONCE = 0x0A, // 3 bytes + RADIOLIB_LORAWAN_NONCES_ACTIVE = 0x0D, // 1 byte + RADIOLIB_LORAWAN_NONCES_SIGNATURE = 0x0E, // 2 bytes + RADIOLIB_LORAWAN_NONCES_BUF_SIZE = 0x10 // = 16 bytes +}; + +enum LoRaWANSchemeSession_t { + RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY = 0x00, // 16 bytes + RADIOLIB_LORAWAN_SESSION_APP_SKEY = 0x10, // 16 bytes + RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = 0x20, // 16 bytes + RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = 0x30, // 16 bytes + RADIOLIB_LORAWAN_SESSION_DEV_ADDR = 0x40, // 4 bytes + RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE = 0x44, // 2 bytes + RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN = 0x46, // 4 bytes + RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP = 0x4A, // 4 bytes + RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN = 0x4E, // 4 bytes + RADIOLIB_LORAWAN_SESSION_RJ_COUNT0 = 0x52, // 2 bytes + RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 = 0x54, // 2 bytes + RADIOLIB_LORAWAN_SESSION_HOMENET_ID = 0x56, // 4 bytes + RADIOLIB_LORAWAN_SESSION_VERSION = 0x5A, // 1 byte + RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE = 0x5B, // 1 byte + RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP = 0x5C, // 4 bytes + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = 0x60, // 1 byte + RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = 0x61, // 1 byte + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = 0x62, // 1 byte + RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = 0x63, // 1 byte + RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = 0x64, // 3 bytes + RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL = 0x67, // 4 bytes + RADIOLIB_LORAWAN_SESSION_PERIODICITY = 0x6B, // 1 byte + RADIOLIB_LORAWAN_SESSION_LAST_TIME = 0x6C, // 4 bytes + RADIOLIB_LORAWAN_SESSION_UL_CHANNELS = 0x70, // 16*8 bytes + RADIOLIB_LORAWAN_SESSION_DL_CHANNELS = 0xF0, // 16*4 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL = 0x0130, // 9*8+2 bytes + RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = 0x017A, // 4 bytes + RADIOLIB_LORAWAN_SESSION_ADR_FCNT = 0x017E, // 4 bytes + RADIOLIB_LORAWAN_SESSION_LINK_ADR = 0x0182, // 4 bytes + RADIOLIB_LORAWAN_SESSION_FCNT_UP = 0x0186, // 4 bytes + RADIOLIB_LORAWAN_SESSION_SIGNATURE = 0x018A, // 2 bytes + RADIOLIB_LORAWAN_SESSION_BUF_SIZE = 0x018C // 396 bytes +}; + /*! \struct LoRaWANChannelSpan_t \brief Structure to save information about LoRaWAN channels. @@ -284,6 +334,9 @@ struct LoRaWANChannelSpan_t { \brief Structure to save information about LoRaWAN band */ struct LoRaWANBand_t { + /*! \brief Identier for this band */ + uint8_t bandNum; + /*! \brief Whether the channels are fixed per specification, or dynamically allocated through the network (plus defaults) */ uint8_t bandType; @@ -415,20 +468,43 @@ class LoRaWANNode { */ LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand = 0); -#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. */ void wipe(); + /*! + \brief Returns the pointer to the internal buffer that holds the LW base parameters + \returns Pointer to uint8_t array of size RADIOLIB_LORAWAN_NONCES_BUF_SIZE + */ + uint8_t* getBufferNonces(); + + /*! + \brief Fill the internal buffer that holds the LW base parameters with a supplied buffer + \param persistentBuffer Buffer that should match the internal format (previously extracted using getBufferNonces) + \returns \ref status_codes + */ + int16_t setBufferNonces(uint8_t* persistentBuffer); + + /*! + \brief Returns the pointer to the internal buffer that holds the LW session parameters + \returns Pointer to uint8_t array of size RADIOLIB_LORAWAN_SESSION_BUF_SIZE + */ + uint8_t* getBufferSession(); + + /*! + \brief Fill the internal buffer that holds the LW session parameters with a supplied buffer + \param persistentBuffer Buffer that should match the internal format (previously extracted using getBufferSession) + \returns \ref status_codes + */ + int16_t setBufferSession(uint8_t* persistentBuffer); + /*! \brief Restore session by loading information from persistent storage. - \returns \ref status_codes in case of error, - else LoRaWAN session mode (0 = no active session, 0xAA / 170 = OTAA, 0xAB / 171 = ABP) + \returns \ref status_codes */ - int16_t restore(); -#endif + int16_t restore(uint16_t checkSum, uint16_t lwMode, uint8_t lwClass, uint8_t freqPlan); /*! \brief Join network by performing over-the-air activation. By this procedure, @@ -437,12 +513,11 @@ class LoRaWANNode { \param devEUI 8-byte device identifier. \param nwkKey Pointer to the network AES-128 key. \param appKey Pointer to the application AES-128 key. - \param joinDr (OTAA:) The datarate at which to send the join-request; (ABP:) ignored \param force Set to true to force joining even if previously joined. - + \param joinDr The datarate at which to send the join-request and any subsequent uplinks (unless ADR is enabled) \returns \ref status_codes */ - int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, uint8_t joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, bool force = false); + int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false, uint8_t joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED); /*! \brief Join network by performing activation by personalization. @@ -450,8 +525,8 @@ class LoRaWANNode { \param addr Device address. \param nwkSKey Pointer to the network session AES-128 key (LoRaWAN 1.0) or MAC command network session key (LoRaWAN 1.1). \param appSKey Pointer to the application session AES-128 key. - \param fNwkSIntKey Pointer to the network session F key (LoRaWAN 1.1), unused for LoRaWAN 1.0. - \param sNwkSIntKey Pointer to the network session S key (LoRaWAN 1.1), unused for LoRaWAN 1.0. + \param fNwkSIntKey Pointer to the Forwarding network session (LoRaWAN 1.1), unused for LoRaWAN 1.0. + \param sNwkSIntKey Pointer to the Serving network session (LoRaWAN 1.1), unused for LoRaWAN 1.0. \param force Set to true to force a new session, even if one exists. \returns \ref status_codes */ @@ -461,8 +536,7 @@ class LoRaWANNode { bool isJoined(); /*! - \brief Save the current state of the session. - All variables are compared to what is saved and only the differences are rewritten. + \brief Save the current state of the session to the session buffer. \returns \ref status_codes */ int16_t saveSession(); @@ -637,10 +711,9 @@ 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, bool saveToEeprom = false); + int16_t setDatarate(uint8_t drUp); /*! \brief Toggle ADR to on or off. @@ -686,10 +759,9 @@ 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, bool saveToEeprom = false); + int16_t setTxPower(int8_t txPower); /*! \brief Configures CSMA for LoRaWAN as per TR-13, LoRa Alliance. @@ -732,8 +804,16 @@ class LoRaWANNode { PhysicalLayer* phyLayer = NULL; const LoRaWANBand_t* band = NULL; + static int16_t checkBufferCommon(uint8_t *buffer, uint16_t size); + void beginCommon(uint8_t joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED); + // a buffer that holds all LW base parameters that should persist at all times! + uint8_t bufferNonces[RADIOLIB_LORAWAN_NONCES_BUF_SIZE] = { 0 }; + + // a buffer that holds all LW session parameters that preferably persist, but can be afforded to get lost + uint8_t bufferSession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE] = { 0 }; + LoRaWANMacCommandQueue_t commandsUp = { .numCommands = 0, .len = 0, @@ -776,7 +856,7 @@ class LoRaWANNode { bool FSK = false; // flag that shows whether the device is joined and there is an ongoing session (none, ABP or OTAA) - uint16_t activeMode = 0; + uint16_t activeMode = RADIOLIB_LORAWAN_MODE_NONE; // ADR is enabled by default bool adrEnabled = true; @@ -835,26 +915,6 @@ class LoRaWANNode { // save the selected sub-band in case this must be restored in ADR control uint8_t subBand = 0; -#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) - /*! - \brief Save the current uplink frame counter. - Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling. - */ - void saveFcntUp(); - - /*! - \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. - */ - void restoreFcntUp(); - - // set all keys to zero - void clearSession(); - - // test if saved keys are non-zero - bool isValidSession(); -#endif - // wait for, open and listen during Rx1 and Rx2 windows; only performs listening int16_t downlinkCommon(); @@ -901,13 +961,13 @@ class LoRaWANNode { int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue, uint8_t* payload = NULL); // execute mac command, return the number of processed bytes for sequential processing - bool execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom = true); + bool execMacCommand(LoRaWANMacCommand_t* cmd); // apply a channel mask to a set of readily defined channels (dynamic bands only) bool applyChannelMaskDyn(uint8_t chMaskCntl, uint16_t chMask); // define or delete channels from a fixed set of channels (fixed bands only) - bool applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask, bool clear); + bool applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask); // get the payload length for a specific MAC command uint8_t getMacPayloadLength(uint8_t cid); @@ -922,7 +982,7 @@ class LoRaWANNode { void processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter); // 16-bit checksum method that takes a uint8_t array of even length and calculates the checksum - static uint16_t checkSum16(uint8_t *key, uint8_t keyLen); + static uint16_t checkSum16(uint8_t *key, uint16_t keyLen); // network-to-host conversion method - takes data from network packet and converts it to the host endians template diff --git a/src/protocols/LoRaWAN/LoRaWANBands.cpp b/src/protocols/LoRaWAN/LoRaWANBands.cpp index e8b244bd9..ad0b1b9ee 100644 --- a/src/protocols/LoRaWAN/LoRaWANBands.cpp +++ b/src/protocols/LoRaWAN/LoRaWANBands.cpp @@ -2,7 +2,21 @@ #if !RADIOLIB_EXCLUDE_LORAWAN +enum LoRaWANBandNum_t { + BandNone, + BandEU868, + BandUS915, + BandCN780, + BandEU433, + BandAU915, + BandCN500, + BandAS923, + BandKR920, + BandIN865 +}; + const LoRaWANBand_t EU868 = { + .bandNum = BandEU868, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, @@ -48,6 +62,7 @@ const LoRaWANBand_t EU868 = { }; const LoRaWANBand_t US915 = { + .bandNum = BandUS915, .bandType = RADIOLIB_LORAWAN_BAND_FIXED, .payloadLenMax = { 19, 61, 133, 250, 250, 0, 0, 0, 41, 117, 230, 230, 230, 230, 0 }, .powerMax = 30, @@ -114,6 +129,7 @@ const LoRaWANBand_t US915 = { }; const LoRaWANBand_t CN780 = { + .bandNum = BandCN780, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 250, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 12, @@ -159,6 +175,7 @@ const LoRaWANBand_t CN780 = { }; const LoRaWANBand_t EU433 = { + .bandNum = BandEU433, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 12, @@ -204,6 +221,7 @@ const LoRaWANBand_t EU433 = { }; const LoRaWANBand_t AU915 = { + .bandNum = BandAU915, .bandType = RADIOLIB_LORAWAN_BAND_FIXED, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 0, 41, 117, 230, 230, 230, 230, 0 }, .powerMax = 30, @@ -270,6 +288,7 @@ const LoRaWANBand_t AU915 = { }; const LoRaWANBand_t CN500 = { + .bandNum = BandCN500, .bandType = RADIOLIB_LORAWAN_BAND_FIXED, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 19, @@ -329,6 +348,7 @@ const LoRaWANBand_t CN500 = { }; const LoRaWANBand_t AS923 = { + .bandNum = BandAS923, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, @@ -374,6 +394,7 @@ const LoRaWANBand_t AS923 = { }; const LoRaWANBand_t KR920 = { + .bandNum = BandKR920, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 14, @@ -419,6 +440,7 @@ const LoRaWANBand_t KR920 = { }; const LoRaWANBand_t IN865 = { + .bandNum = BandIN865, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 30,