diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b933b935..e6da05ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,6 +83,7 @@ jobs: echo "index-url=--additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json" >> $GITHUB_OUTPUT - id: esp8266:esp8266:generic run: | + echo "skip-pattern=(STM32WL|LR11x0_Firmware_Update)" >> $GITHUB_OUTPUT echo "options=':xtal=80,ResetMethod=ck,CrystalFreq=26,FlashFreq=40,FlashMode=qio,eesz=512K'" >> $GITHUB_OUTPUT echo "index-url=--additional-urls http://arduino.esp8266.com/stable/package_esp8266com_index.json" >> $GITHUB_OUTPUT - id: SparkFun:apollo3:sfe_artemis @@ -107,7 +108,7 @@ jobs: - id: MegaCoreX:megaavr:4809 run: | echo "index-url=--additional-urls https://mcudude.github.io/MegaCoreX/package_MCUdude_MegaCoreX_index.json" >> $GITHUB_OUTPUT - echo "skip-pattern=(STM32WL|LR11x0_Firmware_Update)" >> $GITHUB_OUTPUT + echo "skip-pattern=(STM32WL|LR11x0_Firmware_Update|LoRaWAN)" >> $GITHUB_OUTPUT - id: arduino:mbed_rp2040:pico - id: rp2040:rp2040:rpipico run: echo "index-url=--additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json" >> $GITHUB_OUTPUT diff --git a/examples/LoRaWAN/LoRaWAN_ABP/LoRaWAN_ABP.ino b/examples/LoRaWAN/LoRaWAN_ABP/LoRaWAN_ABP.ino index 0fa63de2..1442e0d4 100644 --- a/examples/LoRaWAN/LoRaWAN_ABP/LoRaWAN_ABP.ino +++ b/examples/LoRaWAN/LoRaWAN_ABP/LoRaWAN_ABP.ino @@ -10,12 +10,12 @@ After your device is registered, you can run this example. The device will join the network and start uploading data. - LoRaWAN v1.1 requires the use of persistent storage. + LoRaWAN v1.0.4/v1.1 requires the use of persistent storage. As this example does not use persistent storage, running this examples REQUIRES you to check "Resets frame counters" on your LoRaWAN dashboard. Refer to the notes or the network's documentation on how to do this. - To comply with LoRaWAN v1.1's persistent storage, refer to + To comply with LoRaWAN's persistent storage, refer to https://github.com/radiolib-org/radiolib-persistence For default module settings, see the wiki page @@ -66,8 +66,20 @@ void loop() { // Perform an uplink int state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload)); - debug((state != RADIOLIB_LORAWAN_NO_DOWNLINK) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false); + debug(state < RADIOLIB_ERR_NONE, F("Error in sendReceive"), state, false); + + // Check if a downlink was received + // (state 0 = no downlink, state 1/2 = downlink in window Rx1/Rx2) + if(state > 0) { + Serial.println(F("Received a downlink")); + } else { + Serial.println(F("No downlink received")); + } + + Serial.print(F("Next uplink in ")); + Serial.print(uplinkIntervalSeconds); + Serial.println(F(" seconds\n")); // Wait until next uplink - observing legal & TTN FUP constraints - delay(uplinkIntervalSeconds * 1000UL); + delay(uplinkIntervalSeconds * 1000UL); // delay needs milli-seconds } diff --git a/examples/LoRaWAN/LoRaWAN_ABP/configABP.h b/examples/LoRaWAN/LoRaWAN_ABP/configABP.h index ac742180..61bc63e0 100644 --- a/examples/LoRaWAN/LoRaWAN_ABP/configABP.h +++ b/examples/LoRaWAN/LoRaWAN_ABP/configABP.h @@ -110,16 +110,16 @@ String stateDecode(const int16_t result) { return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED"; case RADIOLIB_ERR_CHECKSUM_MISMATCH: return "RADIOLIB_ERR_CHECKSUM_MISMATCH"; - case RADIOLIB_LORAWAN_NO_DOWNLINK: - return "RADIOLIB_LORAWAN_NO_DOWNLINK"; + case RADIOLIB_ERR_NO_JOIN_ACCEPT: + return "RADIOLIB_ERR_NO_JOIN_ACCEPT"; case RADIOLIB_LORAWAN_SESSION_RESTORED: return "RADIOLIB_LORAWAN_SESSION_RESTORED"; case RADIOLIB_LORAWAN_NEW_SESSION: return "RADIOLIB_LORAWAN_NEW_SESSION"; - case RADIOLIB_LORAWAN_NONCES_DISCARDED: - return "RADIOLIB_LORAWAN_NONCES_DISCARDED"; - case RADIOLIB_LORAWAN_SESSION_DISCARDED: - return "RADIOLIB_LORAWAN_SESSION_DISCARDED"; + case RADIOLIB_ERR_NONCES_DISCARDED: + return "RADIOLIB_ERR_NONCES_DISCARDED"; + case RADIOLIB_ERR_SESSION_DISCARDED: + return "RADIOLIB_ERR_SESSION_DISCARDED"; } return "See https://jgromes.github.io/RadioLib/group__status__codes.html"; } diff --git a/examples/LoRaWAN/LoRaWAN_Reference/LoRaWAN_Reference.ino b/examples/LoRaWAN/LoRaWAN_Reference/LoRaWAN_Reference.ino index 54e0c2be..28b29467 100644 --- a/examples/LoRaWAN/LoRaWAN_Reference/LoRaWAN_Reference.ino +++ b/examples/LoRaWAN/LoRaWAN_Reference/LoRaWAN_Reference.ino @@ -10,11 +10,11 @@ Also, most of the possible and available functions are shown here for reference. - LoRaWAN v1.1 requires the use of EEPROM (persistent storage). + LoRaWAN v1.0.4/v1.1 requires the use of EEPROM (persistent storage). Running this examples REQUIRES you to check "Resets DevNonces" on your LoRaWAN dashboard. Refer to the notes or the network's documentation on how to do this. - To comply with LoRaWAN v1.1's persistent storage, refer to + To comply with LoRaWAN's persistent storage, refer to https://github.com/radiolib-org/radiolib-persistence For default module settings, see the wiki page @@ -59,11 +59,11 @@ void setup() { Serial.print("[LoRaWAN] DevAddr: "); Serial.println((unsigned long)node.getDevAddr(), HEX); - // Disable the ADR algorithm (on by default which is preferable) - node.setADR(false); + // Enable the ADR algorithm (on by default which is preferable) + node.setADR(true); - // Set a fixed datarate - node.setDatarate(4); + // Set a datarate to start off with + node.setDatarate(5); // Manages uplink intervals to the TTN Fair Use Policy node.setDutyCycle(true, 1250); @@ -105,25 +105,28 @@ void loop() { LoRaWANEvent_t uplinkDetails; LoRaWANEvent_t downlinkDetails; - uint8_t Port = 10; + uint8_t fPort = 10; // Retrieve the last uplink frame counter - uint32_t fcntUp = node.getFCntUp(); + uint32_t fCntUp = node.getFCntUp(); - // Send a confirmed uplink every 64th frame + // Send a confirmed uplink on the second uplink // and also request the LinkCheck and DeviceTime MAC commands - if(fcntUp % 64 == 0) { - Serial.println(F("[LoRaWAN] Requesting LinkCheck and DeviceTime")); + Serial.println(F("Sending uplink")); + if(fCntUp == 1) { + Serial.println(F("and requesting LinkCheck and DeviceTime")); node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_LINK_CHECK); node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_DEVICE_TIME); - state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), Port, downlinkPayload, &downlinkSize, true, &uplinkDetails, &downlinkDetails); + state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), fPort, downlinkPayload, &downlinkSize, true, &uplinkDetails, &downlinkDetails); } else { - state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), Port, downlinkPayload, &downlinkSize); + state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), fPort, downlinkPayload, &downlinkSize, false, &uplinkDetails, &downlinkDetails); } - debug((state != RADIOLIB_LORAWAN_NO_DOWNLINK) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false); + debug(state < RADIOLIB_ERR_NONE, F("Error in sendReceive"), state, false); - // Check if downlink was received - if(state != RADIOLIB_LORAWAN_NO_DOWNLINK) { + // Check if a downlink was received + // (state 0 = no downlink, state 1/2 = downlink in window Rx1/Rx2) + if(state > 0) { + Serial.println(F("Received a downlink")); // Did we get a downlink with data for us if(downlinkSize > 0) { Serial.println(F("Downlink data: ")); @@ -142,11 +145,6 @@ void loop() { Serial.print(radio.getSNR()); Serial.println(F(" dB")); - // print frequency error - Serial.print(F("[LoRaWAN] Frequency error:\t")); - Serial.print(radio.getFrequencyError()); - Serial.println(F(" Hz")); - // print extra information about the event Serial.println(F("[LoRaWAN] Event information:")); Serial.print(F("[LoRaWAN] Confirmed:\t")); @@ -158,13 +156,15 @@ void loop() { Serial.print(F("[LoRaWAN] Frequency:\t")); Serial.print(downlinkDetails.freq, 3); Serial.println(F(" MHz")); - Serial.print(F("[LoRaWAN] Output power:\t")); - Serial.print(downlinkDetails.power); - Serial.println(F(" dBm")); Serial.print(F("[LoRaWAN] Frame count:\t")); Serial.println(downlinkDetails.fCnt); Serial.print(F("[LoRaWAN] Port:\t\t")); Serial.println(downlinkDetails.fPort); + Serial.print(F("[LoRaWAN] Time-on-air: \t")); + Serial.print(node.getLastToA()); + Serial.println(F(" ms")); + Serial.print(F("[LoRaWAN] Rx window: \t")); + Serial.println(state); uint8_t margin = 0; uint8_t gwCnt = 0; @@ -184,6 +184,8 @@ void loop() { Serial.println(fracSecond); } + } else { + Serial.println(F("[LoRaWAN] No downlink received")); } // wait before sending another packet @@ -193,7 +195,7 @@ void loop() { Serial.print(F("[LoRaWAN] Next uplink in ")); Serial.print(delayMs/1000); - Serial.println(F("s")); + Serial.println(F(" seconds\n")); delay(delayMs); } diff --git a/examples/LoRaWAN/LoRaWAN_Reference/config.h b/examples/LoRaWAN/LoRaWAN_Reference/config.h index 65be0af9..21c79f8e 100644 --- a/examples/LoRaWAN/LoRaWAN_Reference/config.h +++ b/examples/LoRaWAN/LoRaWAN_Reference/config.h @@ -105,16 +105,16 @@ String stateDecode(const int16_t result) { return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED"; case RADIOLIB_ERR_CHECKSUM_MISMATCH: return "RADIOLIB_ERR_CHECKSUM_MISMATCH"; - case RADIOLIB_LORAWAN_NO_DOWNLINK: - return "RADIOLIB_LORAWAN_NO_DOWNLINK"; + case RADIOLIB_ERR_NO_JOIN_ACCEPT: + return "RADIOLIB_ERR_NO_JOIN_ACCEPT"; case RADIOLIB_LORAWAN_SESSION_RESTORED: return "RADIOLIB_LORAWAN_SESSION_RESTORED"; case RADIOLIB_LORAWAN_NEW_SESSION: return "RADIOLIB_LORAWAN_NEW_SESSION"; - case RADIOLIB_LORAWAN_NONCES_DISCARDED: - return "RADIOLIB_LORAWAN_NONCES_DISCARDED"; - case RADIOLIB_LORAWAN_SESSION_DISCARDED: - return "RADIOLIB_LORAWAN_SESSION_DISCARDED"; + case RADIOLIB_ERR_NONCES_DISCARDED: + return "RADIOLIB_ERR_NONCES_DISCARDED"; + case RADIOLIB_ERR_SESSION_DISCARDED: + return "RADIOLIB_ERR_SESSION_DISCARDED"; } return "See https://jgromes.github.io/RadioLib/group__status__codes.html"; } diff --git a/examples/LoRaWAN/LoRaWAN_Starter/LoRaWAN_Starter.ino b/examples/LoRaWAN/LoRaWAN_Starter/LoRaWAN_Starter.ino index 07ba7038..30b1e217 100644 --- a/examples/LoRaWAN/LoRaWAN_Starter/LoRaWAN_Starter.ino +++ b/examples/LoRaWAN/LoRaWAN_Starter/LoRaWAN_Starter.ino @@ -1,6 +1,8 @@ /* RadioLib LoRaWAN Starter Example + ! Please refer to the included notes to get started ! + This example joins a LoRaWAN network and will send uplink packets. Before you start, you will have to register your device at https://www.thethingsnetwork.org/ @@ -35,7 +37,8 @@ void setup() { debug(state != RADIOLIB_ERR_NONE, F("Initialise radio failed"), state, true); // Setup the OTAA session information - node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); + state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); + debug(state != RADIOLIB_ERR_NONE, F("Initialise node failed"), state, true); Serial.println(F("Join ('login') the LoRaWAN Network")); state = node.activateOTAA(); @@ -60,11 +63,19 @@ void loop() { // Perform an uplink int16_t state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload)); - debug((state != RADIOLIB_LORAWAN_NO_DOWNLINK) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false); + debug(state < RADIOLIB_ERR_NONE, F("Error in sendReceive"), state, false); - Serial.print(F("Uplink complete, next in ")); + // Check if a downlink was received + // (state 0 = no downlink, state 1/2 = downlink in window Rx1/Rx2) + if(state > 0) { + Serial.println(F("Received a downlink")); + } else { + Serial.println(F("No downlink received")); + } + + Serial.print(F("Next uplink in ")); Serial.print(uplinkIntervalSeconds); - Serial.println(F(" seconds")); + Serial.println(F(" seconds\n")); // Wait until next uplink - observing legal & TTN FUP constraints delay(uplinkIntervalSeconds * 1000UL); // delay needs milli-seconds diff --git a/examples/LoRaWAN/LoRaWAN_Starter/config.h b/examples/LoRaWAN/LoRaWAN_Starter/config.h index 65be0af9..21c79f8e 100644 --- a/examples/LoRaWAN/LoRaWAN_Starter/config.h +++ b/examples/LoRaWAN/LoRaWAN_Starter/config.h @@ -105,16 +105,16 @@ String stateDecode(const int16_t result) { return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED"; case RADIOLIB_ERR_CHECKSUM_MISMATCH: return "RADIOLIB_ERR_CHECKSUM_MISMATCH"; - case RADIOLIB_LORAWAN_NO_DOWNLINK: - return "RADIOLIB_LORAWAN_NO_DOWNLINK"; + case RADIOLIB_ERR_NO_JOIN_ACCEPT: + return "RADIOLIB_ERR_NO_JOIN_ACCEPT"; case RADIOLIB_LORAWAN_SESSION_RESTORED: return "RADIOLIB_LORAWAN_SESSION_RESTORED"; case RADIOLIB_LORAWAN_NEW_SESSION: return "RADIOLIB_LORAWAN_NEW_SESSION"; - case RADIOLIB_LORAWAN_NONCES_DISCARDED: - return "RADIOLIB_LORAWAN_NONCES_DISCARDED"; - case RADIOLIB_LORAWAN_SESSION_DISCARDED: - return "RADIOLIB_LORAWAN_SESSION_DISCARDED"; + case RADIOLIB_ERR_NONCES_DISCARDED: + return "RADIOLIB_ERR_NONCES_DISCARDED"; + case RADIOLIB_ERR_SESSION_DISCARDED: + return "RADIOLIB_ERR_SESSION_DISCARDED"; } return "See https://jgromes.github.io/RadioLib/group__status__codes.html"; } diff --git a/examples/LoRaWAN/README.md b/examples/LoRaWAN/README.md index 8a44860f..f4754c1b 100644 --- a/examples/LoRaWAN/README.md +++ b/examples/LoRaWAN/README.md @@ -1,16 +1,33 @@ # LoRaWAN examples -RadioLib LoRaWAN v1.1 examples. +RadioLib LoRaWAN examples. * [LoRaWAN_Starter](https://github.com/jgromes/RadioLib/tree/master/examples/LoRaWAN/LoRaWAN_Starter): this is the recommended entry point for new users. Please read the [`notes`](https://github.com/jgromes/RadioLib/blob/master/examples/LoRaWAN/LoRaWAN_Starter/notes.md) that come with this example to learn more about LoRaWAN and how to use it in RadioLib! * [LoRaWAN_Reference](https://github.com/jgromes/RadioLib/tree/master/examples/LoRaWAN/LoRaWAN_Reference): this sketch showcases most of the available API for LoRaWAN in RadioLib. Be frightened by the possibilities! It is recommended you have read all the [`notes`](https://github.com/jgromes/RadioLib/blob/master/examples/LoRaWAN/LoRaWAN_Starter/notes.md) for the Starter sketch first, as well as the [Learn section on The Things Network](https://www.thethingsnetwork.org/docs/lorawan/)! * [LoRaWAN_ABP](https://github.com/jgromes/RadioLib/tree/master/examples/LoRaWAN/LoRaWAN_ABP): if you wish to use ABP instead of OTAA (but why?), this example shows how you can do this using RadioLib. ---- +## LoRaWAN versions & regional parameters +RadioLib implements both LoRaWAN v1.1 and v1.0.4. Confusingly, v1.0.4 is newer than v1.1, but v1.1 includes more security checks and as such **LoRaWAN v1.1 is preferred**. +The catch is in the Regional Parameters: as v1.0.4 is newer, it is more up to date regarding local laws & regulations. Therefore, RadioLib implements 1.0.4 as baseline and 1.1 as fallback, but **Regional Parameters v1.0.4 is preferred**. +_Note: the CN500 band is implemented as specified in RP v1.1, as the RP v1.0.4 version is much too complex._ +To activate a LoRaWAN v1.1 session, supply all the required keys: +```cpp +node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); +node.beginABP(devAddr, fNwkSIntKey, sNwkSIntKey, nwkSEncKey, appSKey); +``` + +To activate a LoRaWAN v1.0.4 session, set the keys that are not available to `NULL`: +```cpp +node.beginOTAA(joinEUI, devEUI, NULL, appKey); +node.beginABP(devAddr, NULL, NULL, nwkSEncKey, appSKey); +``` + +The device doesn't need to know the Regional Parameters version - that is of importance on the console. + +## LoRaWAN persistence > [!WARNING] -> These examples do not fully comply with LoRaWAN v1.1: for that, persistent storage is necessary. As the implementation of persistent storage differs between different platforms, these are not given here, but in a separate repository, see below: +> These examples do not actually comply with LoRaWAN v1.0.4/v1.1: for that, persistent storage is necessary. As the implementation of persistent storage differs between different platforms, these are not given here, but in a separate repository, see below: -## RadioLib persistence In [this repository](https://github.com/radiolib-org/radiolib-persistence), examples are provided that do comply with the required persistence of certain parameters for LoRaWAN v1.1. Examples are (or will become) available for some of the most popular platforms. **These examples assume you have successfully used the Starter sketch and understood (most of) the accompanying notes!** Currently, examples are available for the following platforms: diff --git a/keywords.txt b/keywords.txt index 44d2487f..b22dbebd 100644 --- a/keywords.txt +++ b/keywords.txt @@ -320,38 +320,38 @@ checkDataRate KEYWORD2 setModem KEYWORD2 # LoRaWAN -clearSession KEYWORD2 getBufferNonces KEYWORD2 setBufferNonces KEYWORD2 +clearSession KEYWORD2 getBufferSession KEYWORD2 setBufferSession KEYWORD2 beginOTAA KEYWORD2 -activateOTAA KEYWORD2 beginABP KEYWORD2 +activateOTAA KEYWORD2 activateABP KEYWORD2 isActivated KEYWORD2 -setRx2Dr KEYWORD2 -sendMacCommandReq KEYWORD2 -uplink KEYWORD2 -downlink KEYWORD2 sendReceive KEYWORD2 +sendMacCommandReq KEYWORD2 +getMacLinkCheckAns KEYWORD2 +getMacDeviceTimeAns KEYWORD2 +setDatarate KEYWORD2 +setTxPower KEYWORD2 +setRx2Dr KEYWORD2 +setADR KEYWORD2 +setDutyCycle KEYWORD2 +setDwellTime KEYWORD2 +setCSMA KEYWORD2 setDeviceStatus KEYWORD2 +scheduleTransmission KEYWORD2 getFCntUp KEYWORD2 getNFCntDown KEYWORD2 getAFCntDown KEYWORD2 resetFCntDown KEYWORD2 -setDatarate KEYWORD2 -setADR KEYWORD2 -setDutyCycle KEYWORD2 -dutyCycleInterval KEYWORD2 -timeUntilUplink KEYWORD2 -setDwellTime KEYWORD2 -maxPayloadDwellTime KEYWORD2 -setTxPower KEYWORD2 -getMacLinkCheckAns KEYWORD2 -getMacDeviceTimeAns KEYWORD2 getDevAddr KEYWORD2 getLastToA KEYWORD2 +dutyCycleInterval KEYWORD2 +timeUntilUplink KEYWORD2 +getMaxPayloadLen KEYWORD2 ####################################### # Constants (LITERAL1) @@ -465,12 +465,12 @@ RADIOLIB_ERR_N_FCNT_DOWN_INVALID LITERAL1 RADIOLIB_ERR_A_FCNT_DOWN_INVALID LITERAL1 RADIOLIB_ERR_DWELL_TIME_EXCEEDED LITERAL1 RADIOLIB_ERR_CHECKSUM_MISMATCH LITERAL1 -RADIOLIB_LORAWAN_NO_DOWNLINK LITERAL1 +RADIOLIB_ERR_NO_JOIN_ACCEPT LITERAL1 RADIOLIB_LORAWAN_SESSION_RESTORED LITERAL1 RADIOLIB_LORAWAN_NEW_SESSION LITERAL1 -RADIOLIB_LORAWAN_NONCES_DISCARDED LITERAL1 -RADIOLIB_LORAWAN_SESSION_DISCARDED LITERAL1 -RADIOLIB_LORAWAN_INVALID_MODE LITERAL1 +RADIOLIB_ERR_NONCES_DISCARDED LITERAL1 +RADIOLIB_ERR_SESSION_DISCARDED LITERAL1 +RADIOLIB_ERR_INVALID_MODE LITERAL1 RADIOLIB_ERR_INVALID_WIFI_TYPE LITERAL1 diff --git a/src/TypeDef.h b/src/TypeDef.h index de6e06b6..7f4d2278 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -564,9 +564,9 @@ #define RADIOLIB_ERR_CHECKSUM_MISMATCH (-1115) /*! - \brief No downlink was received - most likely none was sent from the server. + \brief No JoinAccept was received - check your keys, or otherwise likely a range issue! */ -#define RADIOLIB_LORAWAN_NO_DOWNLINK (-1116) +#define RADIOLIB_ERR_NO_JOIN_ACCEPT (-1116) /*! \brief The LoRaWAN session was successfully re-activated. @@ -581,17 +581,17 @@ /*! \brief The supplied Nonces buffer is discarded as its activation information is invalid. */ -#define RADIOLIB_LORAWAN_NONCES_DISCARDED (-1119) +#define RADIOLIB_ERR_NONCES_DISCARDED (-1119) /*! \brief The supplied Session buffer is discarded as it doesn't match the Nonces. */ -#define RADIOLIB_LORAWAN_SESSION_DISCARDED (-1120) +#define RADIOLIB_ERR_SESSION_DISCARDED (-1120) /*! \brief The requested command is unavailable under the current LoRaWAN mode. */ -#define RADIOLIB_LORAWAN_INVALID_MODE (-1121) +#define RADIOLIB_ERR_INVALID_MODE (-1121) // LR11x0-specific status codes diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index a1f63a7d..d217347d 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -6,57 +6,218 @@ #if !RADIOLIB_EXCLUDE_LORAWAN -// flag to indicate whether there was some action during Rx mode (timeout or downlink) -static volatile bool downlinkAction = false; - -// interrupt service routine to handle downlinks automatically -#if defined(ESP8266) || defined(ESP32) - IRAM_ATTR -#endif -static void LoRaWANNodeOnDownlinkAction(void) { - downlinkAction = true; -} - -uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_t min, uint8_t max) { - int8_t dr = uplink - offset + base; - if(dr < min) { - dr = min; - } else if (dr > max) { - dr = max; - } - return(dr); -} - LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand) { this->phyLayer = phy; this->band = band; - this->rx2 = this->band->rx2; + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; this->txPowerMax = this->band->powerMax; this->subBand = subBand; - this->difsSlots = 2; - this->backoffMax = 6; - this->enableCSMA = false; this->dwellTimeEnabledUp = this->dwellTimeUp != 0; this->dwellTimeEnabledDn = this->dwellTimeDn != 0; - memset(this->availableChannels, 0, sizeof(this->availableChannels)); + memset(this->channelPlan, 0, sizeof(this->channelPlan)); } -void LoRaWANNode::setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA) { - this->backoffMax = backoffMax; - this->difsSlots = difsSlots; - this->enableCSMA = enableCSMA; +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + const char* dataUp = strUp.c_str(); + + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + state = this->sendReceive((uint8_t*)dataUp, strlen(dataUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown); + + if(state == RADIOLIB_ERR_NONE) { + // add null terminator + dataDown[lenDown] = '\0'; + + // initialize Arduino String class + strDown = String((char*)dataDown); + } + + return(state); +} +#endif + +int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + return(this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown)); } -void LoRaWANNode::createNonces() { - // set the device credentials - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], this->lwMode); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], this->lwClass); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], this->keyCheckSum); +int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + return(this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, lenDown, isConfirmed, eventUp, eventDown)); +} + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + return(this->sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown)); +} + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + if(!dataUp || !dataDown || !lenDown) { + return(RADIOLIB_ERR_NULL_POINTER); + } + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); + + // if after (at) ADR_ACK_LIMIT frames no RekeyConf was received, revert to Join state + if(this->fCntUp == (1UL << this->adrLimitExp)) { + state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_REKEY, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK); + if(state == RADIOLIB_ERR_NONE) { + this->clearSession(); + } + } + + // if not joined, don't do anything + if(!this->isActivated()) { + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + // check if the requested payload + fPort are allowed, also given dutycycle + uint8_t totalLen = lenUp + this->fOptsUpLen; + state = this->isValidUplink(&totalLen, fPort); + RADIOLIB_ASSERT(state); + + // in case of TS009, a payload that is too long may have gotten clipped, + // so recalculate the actual payload length + // (outside of TS009, a payload that is too long throws an error) + lenUp = totalLen - this->fOptsUpLen; + + // the first 16 bytes are reserved for MIC calculation blocks + size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(lenUp, this->fOptsUpLen); + #if RADIOLIB_STATIC_ONLY + uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen]; + #endif + + // build the encrypted uplink message + this->composeUplink(dataUp, lenUp, uplinkMsg, fPort, isConfirmed); + + // reset Time-on-Air as we are starting new uplink sequence + this->lastToA = 0; + + // repeat uplink+downlink up to 'nbTrans' times (ADR) + uint8_t trans = 0; + for(; trans < this->nbTrans; trans++) { + + // keep track of number of hopped channels + uint8_t numHops = this->maxChanges; + + // number of additional CAD tries + uint8_t numBackoff = 0; + if(this->backoffMax) { + numBackoff = this->phyLayer->random(1, this->backoffMax + 1); + } + + do { + // select a pair of Tx/Rx channels for uplink+downlink + this->selectChannels(); + + // generate and set uplink MIC (depends on selected channel) + this->micUplink(uplinkMsg, uplinkMsgLen); + + // if CSMA is enabled, repeat channel selection & encryption up to numHops times + } while(this->csmaEnabled && numHops-- > 0 && !this->csmaChannelClear(this->difsSlots, numBackoff)); + + // send it (without the MIC calculation blocks) + state = this->transmitUplink(&this->channels[RADIOLIB_LORAWAN_UPLINK], + &uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], + (uint8_t)(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS), + trans > 0); + if(state != RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] uplinkMsg; + #endif + return(state); + } + + // handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received + state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart); + + // RETRANSMIT_TIMEOUT is 2s +/- 1s (RP v1.0.4) + // must be present after any confirmed frame, so we force this here + if(isConfirmed) { + mod->hal->delay(this->phyLayer->random(1000, 3000)); + } + + // if an error occured or a downlink was received, stop retransmission + if(state != RADIOLIB_ERR_NONE) { + break; + } + // if no downlink was received, go on + + } // end of transmission & reception + + // note: if an error occured, it may still be the case that a transmission occured + // therefore, we act as if a transmission occured before throwing the actual error + // this feels to be the best way to comply to spec + + // increase frame counter by one for the next uplink + this->fCntUp += 1; + + // the downlink confirmation was acknowledged, so clear the counter value + this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + + // pass the uplink info if requested + if(eventUp) { + eventUp->dir = RADIOLIB_LORAWAN_UPLINK; + eventUp->confirmed = isConfirmed; + eventUp->confirming = (this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE); + eventUp->datarate = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + eventUp->freq = this->channels[RADIOLIB_LORAWAN_UPLINK].freq / 10000.0; + eventUp->power = this->txPowerMax - this->txPowerSteps * 2; + eventUp->fCnt = this->fCntUp; + eventUp->fPort = fPort; + eventUp->nbTrans = trans; + } + + // if a hardware error occurred, return + if(state < RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] uplinkMsg; + #endif + return(state); + } + + uint8_t rxWindow = state; + + // if no downlink was received, do an early exit + if(rxWindow == 0) { + // check if ADR backoff must occur + if(this->adrEnabled) { + this->adrBackoff(); + } + // remove only non-persistent MAC commands, the other commands should be re-sent until downlink is received + LoRaWANNode::clearMacCommands(this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + return(rxWindow); + } + + // a downlink was received, so we can clear the whole MAC uplink buffer + memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + this->fOptsUpLen = 0; + + state = this->parseDownlink(dataDown, lenDown, eventDown); + + // return an error code, if any, otherwise return Rx window (which is > 0) + RADIOLIB_ASSERT(state); + return(rxWindow); } void LoRaWANNode::clearNonces() { + // not a real version number, but helps debugging + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("This line was updated on 2024-09-08 at 00:01+2"); // clear & set all the device credentials memset(this->bufferNonces, 0, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); this->keyCheckSum = 0; @@ -66,15 +227,14 @@ void LoRaWANNode::clearNonces() { this->rev = 0; } -void LoRaWANNode::clearSession() { - memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); - memset(&(this->commandsUp), 0, sizeof(LoRaWANMacCommandQueue_t)); - memset(&(this->commandsDown), 0, sizeof(LoRaWANMacCommandQueue_t)); - this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; - this->isActive = false; -} - uint8_t* LoRaWANNode::getBufferNonces() { + // set the device credentials + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], this->lwMode); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], this->lwClass); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], this->keyCheckSum); + // 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); @@ -102,7 +262,7 @@ int16_t LoRaWANNode::setBufferNonces(uint8_t* persistentBuffer) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Configuration mismatch (keys: %d, mode: %d, class: %d, plan: %d)", isSameKeys, isSameMode, isSameClass, isSamePlan); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Discarding the Nonces buffer:"); RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); - return(RADIOLIB_LORAWAN_NONCES_DISCARDED); + return(RADIOLIB_ERR_NONCES_DISCARDED); } // copy the whole buffer over @@ -118,6 +278,161 @@ int16_t LoRaWANNode::setBufferNonces(uint8_t* persistentBuffer) { return(state); } +void LoRaWANNode::clearSession() { + memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; + this->isActive = false; + + // reset all frame counters + this->fCntUp = 0; + this->aFCntDown = 0; + this->nFCntDown = 0; + this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; + this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + this->adrFCnt = 0; + + // reset number of retransmissions from ADR + this->nbTrans = 1; + + // clear CSMA settings + this->csmaEnabled = false; + this->maxChanges = 0; + this->difsSlots = 0; + this->backoffMax = 0; +} + +void LoRaWANNode::createSession(uint16_t lwMode, uint8_t initialDr) { + this->clearSession(); + + // setup JoinRequest uplink/downlink frequencies and datarates + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(true); + } else { + this->selectChannelPlanFix(); + } + + uint8_t drUp = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + + // on fixed bands, the first OTAA uplink (JoinRequest) is sent on fixed datarate + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && lwMode == RADIOLIB_LORAWAN_MODE_OTAA) { + // 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 + if(rand <= 8) { + drUp = this->band->txSpans[0].drJoinRequest; // if one of the first 8 channels, select datarate of span 0 + } else { + drUp = this->band->txSpans[1].drJoinRequest; // if ninth channel, select datarate of span 1 + } + } else { + // on dynamic bands, the first OTAA uplink (JoinRequest) can be any available datarate + // this is also true for ABP on both dynamic and fixed bands, as there is no JoinRequest + if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + uint8_t i = 0; + for(; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + if(initialDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin + && initialDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) { + drUp = initialDr; + break; + } + } + } + // if there is no channel that allowed the user-specified datarate, revert to default datarate + if(i == RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); + initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + } + } + + // if there is no (channel that allowed the) user-specified datarate, use a default datarate + // we use the floor of the average datarate of the first enabled channel + if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + uint8_t drMin = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin; + uint8_t drMax = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax; + drUp = (drMin + drMax) / 2; + } + } + } + } + + uint8_t cOcts[5]; // 5 = maximum downlink payload length + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = (drUp << 4); // set uplink datarate + cOcts[0] |= 0; // default to max Tx Power + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; + this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + uint8_t maxDCyclePower = 0; + switch(this->band->dutyCycle) { + case(3600): + maxDCyclePower = 10; + break; + case(36000): + maxDCyclePower = 7; + break; + } + cOcts[0] = maxDCyclePower; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET << 4); + cOcts[0] |= this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr; // may be set by user, otherwise band's default upon initialization + LoRaWANNode::hton(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (this->band->dwellTimeDn > 0 ? 1 : 0) << 5; + cOcts[0] |= (this->band->dwellTimeUp > 0 ? 1 : 0) << 4; + uint8_t maxEIRPRaw; + switch(this->band->powerMax) { + case(12): + maxEIRPRaw = 2; + break; + case(14): + maxEIRPRaw = 4; + break; + case(16): + maxEIRPRaw = 5; + break; + case(19): // this option does not exist for the TxParamSetupReq but will be caught during execution + maxEIRPRaw = 7; + break; + case(30): + maxEIRPRaw = 13; + break; + default: + maxEIRPRaw = 2; + break; + } + cOcts[0] |= maxEIRPRaw; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4); + cOcts[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N << 4); + cOcts[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N; + (void)execMacCommand(cid, cOcts, cLen); +} + uint8_t* LoRaWANNode::getBufferSession() { // store all frame counters LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN], this->aFCntDown); @@ -127,8 +442,13 @@ uint8_t* LoRaWANNode::getBufferSession() { LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT], this->adrFCnt); LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP], this->fCntUp); + uint16_t chMask = 0x0000; + (void)this->getAvailableChannels(&chMask); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS], chMask); + // save the current uplink MAC command queue - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE], this->fOptsUp, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN], &this->fOptsUpLen, 1); // 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); @@ -150,8 +470,9 @@ int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { 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_LORAWAN_SESSION_DISCARDED); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The Session buffer (%04x) does not match the Nonces buffer (%04x)", + signatureInSession, signatureNonces); + return(RADIOLIB_ERR_SESSION_DISCARDED); } // copy the whole buffer over @@ -183,109 +504,76 @@ int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { // restore the complete MAC state - // all-zero buffer used for checking if MAC commands are set - uint8_t bufferZeroes[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; + uint8_t cOcts[14] = { 0 }; // TODO explain + uint8_t cid; + uint8_t cLen = 0; - LoRaWANMacCommand_t cmd = { - .cid = 0, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - - // for dynamic bands, first restore the defined channels before restoring ADR - // this is because the ADR command acts as a mask for the enabled channels + // setup the default channels if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // setup the default channels - state = this->setupChannelsDyn(); - RADIOLIB_ASSERT(state); + this->selectChannelPlanDyn(); + } else { // type == RADIOLIB_LORAWAN_BAND_FIXED) + this->selectChannelPlanFix(); + } + + // for dynamic bands, the additional channels must be restored per-channel + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // all-zero buffer used for checking if MAC commands are set + uint8_t bufferZeroes[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; // restore the session channels uint8_t *startChannelsUp = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; - cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - 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); + memcpy(cOcts, startChannelsUp + (i * cLen), cLen); + if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes + (void)execMacCommand(cid, cOcts, cLen); } } uint8_t *startChannelsDown = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS]; - cmd.cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; + cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn; - 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); + memcpy(cOcts, startChannelsDown + (i * cLen), cLen); + if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes + (void)execMacCommand(cid, cOcts, cLen); } } } - cmd.cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn, - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cmd.len); - (void)execMacCommand(&cmd); + // restore the MAC state - ADR needs special care, the rest is straight default + cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cLen = 14; // special internal ADR command + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], cLen); + (void)execMacCommand(cid, cOcts, cLen); - 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) { - // setup the default channels - state = this->setupChannelsFix(this->subBand); - RADIOLIB_ASSERT(state); - - // restore the session channels - uint8_t *startMACpayload = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; - - // there are at most 8 channel masks present - cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; - for(int i = 0; i < 8; i++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - 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) { - break; - } - cmd.repeat = (i+1); - (void)execMacCommand(&cmd); - } + uint8_t cids[6] = { + RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, + RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, + RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP + }; + uint16_t locs[6] = { + RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE, RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP, + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP, RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP, + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP, RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + }; + for(uint8_t i = 0; i < 6; i++) { + (void)this->getMacLen(cids[i], &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[locs[i]], cLen); + (void)execMacCommand(cids[i], cOcts, cLen); } - cmd.cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn; - 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; - 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; - 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; - 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; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], cmd.len); - (void)execMacCommand(&cmd); + // set the available channels + uint16_t chMask = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS]); + this->setAvailableChannels(chMask); // copy uplink MAC command queue back in place - memcpy(&this->commandsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], sizeof(LoRaWANMacCommandQueue_t)); + memcpy(this->fOptsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE], RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memcpy(&this->fOptsUpLen, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN], 1); // as both the Nonces and session are restored, revert to active session this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; @@ -293,168 +581,10 @@ int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { 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; - } +int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey) { + if(!appKey) { + return(RADIOLIB_ERR_NULL_POINTER); } - 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); -} - -void LoRaWANNode::activateCommon(uint8_t initialDr) { - uint8_t drUp = 0; - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // if join datarate is user-specified and valid, select that value - if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - if(initialDr >= this->band->txFreqs[0].drMin && initialDr <= this->band->txFreqs[0].drMax) { - drUp = initialDr; - } else { - // if there is no channel that allowed the user-specified datarate, revert to default datarate - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); - initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - } - - // if there is no (channel that allowed the) user-specified datarate, use a default datarate - // we use the floor of the average datarate of the first default channel - if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - drUp = (this->band->txFreqs[0].drMin + this->band->txFreqs[0].drMax) / 2; - } - - } else { - // if the user specified a certain datarate, check if any of the configured channels allows it - if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - uint8_t i = 0; - for(; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - if(initialDr >= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin - && initialDr <= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax) { - break; - } - } - } - // if there is no channel that allowed the user-specified datarate, revert to default datarate - if(i == RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); - initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - } - - // if there is no (channel that allowed the) user-specified datarate, use a default datarate - // we use the join-request datarate for one of the available channels - if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - // 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 - if(rand <= 8) { - drUp = this->band->txSpans[0].joinRequestDataRate; // if one of the first 8 channels, select datarate of span 0 - } else { - drUp = this->band->txSpans[1].joinRequestDataRate; // if ninth channel, select datarate of span 1 - } - } - - } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; - cmd.payload[0] = (drUp << 4); // set uplink datarate - cmd.payload[0] |= 0; // default to max Tx Power - cmd.payload[3] = (1 << 7); // set the RFU bit, which means that the channel mask gets ignored - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn; - uint8_t maxDCyclePower; - switch(this->band->dutyCycle) { - case(0): - maxDCyclePower = 0; - break; - case(3600): - maxDCyclePower = 10; - break; - case(36000): - maxDCyclePower = 7; - break; - default: - maxDCyclePower = 0; - break; - } - cmd.payload[0] = maxDCyclePower; - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET << 4); - cmd.payload[0] |= this->rx2.drMax; // may be set by user, otherwise band's default upon initialization - uint32_t rx2Freq = uint32_t(this->rx2.freq * 10000); - LoRaWANNode::hton(&cmd.payload[1], rx2Freq, 3); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn; - cmd.payload[0] = (this->band->dwellTimeDn > 0 ? 1 : 0) << 5; - cmd.payload[0] |= (this->band->dwellTimeUp > 0 ? 1 : 0) << 4; - uint8_t maxEIRPRaw; - switch(this->band->powerMax) { - case(12): - maxEIRPRaw = 2; - break; - case(14): - maxEIRPRaw = 4; - break; - case(16): - maxEIRPRaw = 5; - break; - case(19): // this option does not exist for the TxParamSetupReq but will be caught during execution - maxEIRPRaw = 7; - break; - case(30): - maxEIRPRaw = 13; - break; - default: - maxEIRPRaw = 2; - break; - } - cmd.payload[0] |= maxEIRPRaw; - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4); - cmd.payload[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N << 4); - cmd.payload[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N; - (void)execMacCommand(&cmd); -} - -void LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey) { // clear all the device credentials in case there were any this->clearNonces(); @@ -467,118 +597,73 @@ void LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, } // generate activation key checksum - this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&joinEUI), 8); - this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&devEUI), 8); - this->keyCheckSum ^= LoRaWANNode::checkSum16(appKey, 16); + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&joinEUI), sizeof(uint64_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&devEUI), sizeof(uint64_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(appKey, RADIOLIB_AES128_KEY_SIZE); if(nwkKey) { - this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkKey, 16); + this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkKey, RADIOLIB_AES128_KEY_SIZE); } this->lwMode = RADIOLIB_LORAWAN_MODE_OTAA; this->lwClass = RADIOLIB_LORAWAN_CLASS_A; - // set the device credentials - this->createNonces(); + return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) { - // check if there is an active session - if(this->isActivated()) { - // already activated, don't do anything - return(RADIOLIB_ERR_NONE); - } - if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) { - // session restored but not yet activated - do so now - this->isActive = true; - return(RADIOLIB_LORAWAN_SESSION_RESTORED); +int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey) { + if(!nwkSEncKey || !appSKey) { + return(RADIOLIB_ERR_NULL_POINTER); } + // clear all the device credentials in case there were any + this->clearNonces(); - int16_t state = RADIOLIB_ERR_UNKNOWN; - - // either no valid session was found or user forced a new session, so clear all activity - this->clearSession(); - - // starting a new session, so make sure to update event fields already - if(joinEvent) { - joinEvent->newSession = true; - joinEvent->devNonce = this->devNonce; - joinEvent->joinNonce = this->joinNonce; - } - - // setup join-request uplink/downlink frequencies and datarates - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - state = this->setupChannelsDyn(true); + this->devAddr = addr; + memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + if(fNwkSIntKey && sNwkSIntKey) { + this->rev = 1; + memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } else { - state = this->setupChannelsFix(this->subBand); - } - RADIOLIB_ASSERT(state); - - // on fixed bands, the join-datarate is specified per specification - // therefore, we ignore the value that was specified by the user - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { - joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - // setup all MAC properties to default values - this->activateCommon(joinDr); - - // select a random pair of Tx/Rx channels - state = this->selectChannels(); - RADIOLIB_ASSERT(state); - - // set the physical layer configuration for uplink - state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); - RADIOLIB_ASSERT(state); - - if(this->dwellTimeEnabledUp) { - // calculate JoinRequest time-on-air in milliseconds - RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000; - if(toa > this->dwellTimeUp) { - return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); - } + memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->sNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); } + // generate activation key checksum + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&addr), sizeof(uint32_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + this->keyCheckSum ^= LoRaWANNode::checkSum16(appSKey, RADIOLIB_AES128_KEY_SIZE); + if(fNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } + if(sNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } + + this->lwMode = RADIOLIB_LORAWAN_MODE_ABP; + this->lwClass = RADIOLIB_LORAWAN_CLASS_A; + + return(RADIOLIB_ERR_NONE); +} + +void LoRaWANNode::composeJoinRequest(uint8_t* out) { // copy devNonce currently in use uint16_t devNonceUsed = this->devNonce; - - // build the join-request message - uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; // set the packet fields - joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], this->joinEUI); - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], this->devEUI); - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonceUsed); + out[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], this->joinEUI); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], this->devEUI); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonceUsed); // add the authentication code uint32_t mic = 0; if(this->rev == 1) { - mic =this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->nwkKey); + mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->nwkKey); } else { - mic =this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->appKey); + mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->appKey); } - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic); +} - // send it - Module* mod = this->phyLayer->getMod(); - state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); - this->rxDelayStart = mod->hal->millis(); - RADIOLIB_ASSERT(state); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce); - - // join-request successfully sent, so increase & save devNonce - this->devNonce += 1; - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce); - - // configure Rx delay for join-accept message - these are re-configured once a valid join-request is received - this->rxDelays[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS; - this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS; - - // make sure the Rx2 settings are back to this band's default - this->rx2 = this->band->rx2; - - // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received - state = downlinkCommon(); - RADIOLIB_ASSERT(state); +int16_t LoRaWANNode::processJoinAccept(LoRaWANJoinEvent_t *joinEvent) { + int16_t state = RADIOLIB_ERR_UNKNOWN; // build the buffer for the reply data uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; @@ -656,7 +741,7 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE; LoRaWANNode::hton(&micBuff[1], this->joinEUI); - LoRaWANNode::hton(&micBuff[9], devNonceUsed); + LoRaWANNode::hton(&micBuff[9], this->devNonce - 1); memcpy(&micBuff[11], joinAcceptMsg, lenRx); if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) { @@ -670,46 +755,39 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) } } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn, - .repeat = 0, - }; - cmd.payload[0] = dlSettings & 0x7F; - uint32_t rx2Freq = uint32_t(this->rx2.freq * 10000); // default Rx2 frequency - LoRaWANNode::hton(&cmd.payload[1], rx2Freq, 3); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; - memset(cmd.payload, 0, RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN); - cmd.payload[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn; - (void)execMacCommand(&cmd); - // in case of dynamic band, setup the default channels first + // in case of dynamic band, reset the channels to clear JoinRequest-specific channels if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannelsDyn(false); + this->selectChannelPlanDyn(false); } + uint8_t cOcts[5]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + uint8_t cLen = 0; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = dlSettings & 0x7F; + LoRaWANNode::hton(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]; + (void)execMacCommand(cid, cOcts, cLen); + // process CFlist if present (and if CFListType matches used band type) if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN && joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS] == this->band->bandType) { - uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 }; - memcpy(&cfList[0], &joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS], RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN); - this->processCFList(cfList); + this->processCFList(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS]); } // if no (valid) CFList was received, default or subband are already setup so don't need to do anything else - // prepare buffer for key derivation uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_NONCE_POS], this->joinNonce, 3); // check protocol version (1.0 vs 1.1) if(this->rev == 1) { // 1.1 version, derive the keys - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], this->joinEUI); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonceUsed); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS], this->joinEUI); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS], this->devNonce - 1); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(this->appKey); @@ -727,19 +805,10 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) RadioLibAES128Instance.init(this->nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey); - // enqueue the RekeyInd MAC command to be sent in the next uplink - cmd.cid = RADIOLIB_LORAWAN_MAC_REKEY; - memset(cmd.payload, 0, RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN); - cmd.payload[0] = this->rev; - cmd.len = sizeof(uint8_t); - cmd.repeat = 0x01 << RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP; - state = pushMacCommand(&cmd, &this->commandsUp); - RADIOLIB_ASSERT(state); - } else { // 1.0 version, just derive the keys LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], this->homeNetId, 3); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonceUsed); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], this->devNonce - 1); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(this->appKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); @@ -753,15 +822,16 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) } - // reset all frame counters - this->fCntUp = 0; - this->aFCntDown = 0; - this->nFCntDown = 0; - this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; - this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; - this->adrFCnt = 0; + // for LW v1.1, send the RekeyInd MAC command + if(this->rev == 1) { + // enqueue the RekeyInd MAC command to be sent in the next uplink + cid = RADIOLIB_LORAWAN_MAC_REKEY; + this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_UPLINK); + cOcts[0] = this->rev; + state = LoRaWANNode::pushMacCommand(cid, cOcts, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + RADIOLIB_ASSERT(state); + } - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], this->joinNonce, 3); this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; @@ -772,10 +842,10 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) // store DevAddr and all keys 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); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); // set the signature of the Nonces buffer in the Session buffer LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], signature); @@ -786,42 +856,99 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) this->isActive = true; - // received join-accept, so update JoinNonce value in event + // received JoinAccept, so update JoinNonce value in event if(joinEvent) { joinEvent->joinNonce = this->joinNonce; } - return(RADIOLIB_LORAWAN_NEW_SESSION); + return(state); } -void LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey) { - // clear all the device credentials in case there were any - this->clearNonces(); - - this->devAddr = addr; - memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); - if(fNwkSIntKey && sNwkSIntKey) { - this->rev = 1; - memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); - } else { - memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->sNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); +int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) { + // check if there is an active session + if(this->isActivated()) { + // already activated, don't do anything + return(RADIOLIB_ERR_NONE); + } + if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) { + // session restored but not yet activated - do so now + this->isActive = true; + return(RADIOLIB_LORAWAN_SESSION_RESTORED); } - // generate activation key checksum - this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&addr), 4); - this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkSEncKey, 16); - this->keyCheckSum ^= LoRaWANNode::checkSum16(appSKey, 16); - if(fNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, 16); } - if(sNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, 16); } + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); - this->lwMode = RADIOLIB_LORAWAN_MODE_ABP; - this->lwClass = RADIOLIB_LORAWAN_CLASS_A; + // starting a new session, so make sure to update event fields already + if(joinEvent) { + joinEvent->newSession = true; + joinEvent->devNonce = this->devNonce; + joinEvent->joinNonce = this->joinNonce; + } - // set the device credentials - this->createNonces(); + // setup all MAC properties to default values + this->createSession(RADIOLIB_LORAWAN_MODE_OTAA, joinDr); + + // build the JoinRequest message + uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; + this->composeJoinRequest(joinRequestMsg); + + // select a random pair of Tx/Rx channels + state = this->selectChannels(); + RADIOLIB_ASSERT(state); + + // set the physical layer configuration for uplink + state = this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK], + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + + // calculate JoinRequest time-on-air in milliseconds + if(this->dwellTimeEnabledUp) { + RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000; + if(toa > this->dwellTimeUp) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Dwell time exceeded: ToA = %lu, max = %d", (unsigned long)toa, this->dwellTimeUp); + return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); + } + } + + // if requested, delay until transmitting JoinRequest + RadioLibTime_t tNow = mod->hal->millis(); + if(this->tUplink > tNow) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow)); + if(this->tUplink > mod->hal->millis()) { + mod->hal->delay(this->tUplink - mod->hal->millis()); + } + } + + // send it + state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + + // JoinRequest successfully sent, so increase & save devNonce + this->devNonce += 1; + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce); + + // configure Rx1 and Rx2 delay for JoinAccept message - these are re-configured once a valid JoinAccept is received + this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS; + this->rxDelays[2] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS; + + // handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received + state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart); + if(state < RADIOLIB_ERR_NONE) { + return(state); + } else if (state == RADIOLIB_ERR_NONE) { + return(RADIOLIB_ERR_NO_JOIN_ACCEPT); + } + + // process JoinAccept message + state = this->processJoinAccept(joinEvent); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_LORAWAN_NEW_SESSION); } int16_t LoRaWANNode::activateABP(uint8_t initialDr) { @@ -836,26 +963,8 @@ int16_t LoRaWANNode::activateABP(uint8_t initialDr) { return(RADIOLIB_LORAWAN_SESSION_RESTORED); } - // either no valid session was found or user forced a new session, so clear all activity - this->clearSession(); - - // setup the uplink/downlink channels and initial datarate - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannelsDyn(); - } else { - this->setupChannelsFix(this->subBand); - } - // setup all MAC properties to default values - this->activateCommon(initialDr); - - // reset all frame counters - this->fCntUp = 0; - this->aFCntDown = 0; - this->nFCntDown = 0; - this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; - this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; - this->adrFCnt = 0; + this->createSession(RADIOLIB_LORAWAN_MODE_ABP, initialDr); // new session all good, so set active-bit to true this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; @@ -883,28 +992,248 @@ int16_t LoRaWANNode::activateABP(uint8_t initialDr) { return(RADIOLIB_LORAWAN_NEW_SESSION); } +void LoRaWANNode::processCFList(uint8_t* cfList) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processing CFList"); + + uint8_t cOcts[14] = { 0 }; // TODO explain + uint8_t cid; + uint8_t cLen = 0; + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // retrieve number of existing (default) channels + size_t num = 0; + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(!this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + break; + } + num++; + } + + uint8_t freqZero[3] = { 0 }; + + // datarate range for all new channels is equal to the default channels + cOcts[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin; + for(uint8_t i = 0; i < 5; i++, num++) { + // if the frequency fields are all zero, there are no more channels in the CFList + if(memcmp(&cfList[i*3], freqZero, 3) == 0) { + break; + } + cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = num; + memcpy(&cOcts[1], &cfList[i*3], 3); + (void)execMacCommand(cid, cOcts, cLen); + } + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // complete channel mask received, so clear all existing channels + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + + // copy channel mask straight over to LinkAdr MAC command + cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cLen = 14; // special internal ADR length + cOcts[0] = 0xFF; // same datarate and cOcts + memcpy(&cOcts[1], cfList, 12); // copy mask + cOcts[13] = 0; // set NbTrans = 0 -> keep the same + (void)execMacCommand(cid, cOcts, cLen); + } + +} + bool LoRaWANNode::isActivated() { return(this->isActive); } -#if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::uplink(String& str, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* event) { - return(this->uplink(str.c_str(), fPort, isConfirmed, event)); -} -#endif +int16_t LoRaWANNode::isValidUplink(uint8_t* len, uint8_t fPort) { + // check destination fPort + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: { + // MAC FPort only good if internally overruled + if (!this->isMACPayload) { + return(RADIOLIB_ERR_INVALID_PORT); + } + // if this is MAC only payload, continue and reset for next uplink + this->isMACPayload = false; + } break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: { + // all good + } break; + case RADIOLIB_LORAWAN_FPORT_TS009: { + // TS009 FPort only good if overruled during verification testing + if(!this->TS009) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort); + return(RADIOLIB_ERR_INVALID_PORT); + } + } break; + case RADIOLIB_LORAWAN_FPORT_TS011: { + // TS011 FPort only good if overruled during relay exchange + if(!this->TS011) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort); + return(RADIOLIB_ERR_INVALID_PORT); + } + } break; + default: { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort); + } break; + } -int16_t LoRaWANNode::uplink(const char* str, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* event) { - return(this->uplink((uint8_t*)str, strlen(str), fPort, isConfirmed, event)); + // check maximum payload len as defined in band + uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + if(this->TS011) { + maxPayLen = RADIOLIB_MIN(maxPayLen, 230); // payload length is limited to 230 if under repeater + } + if(*len > maxPayLen) { + // normally, throw an error if the packet is too long + if(this->TS009 == false) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + // if testing with TS009 Specification Verification Protocol, don't throw error but clip the message + *len = maxPayLen; + } + + return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* event) { - // if not joined, don't do anything - if(!this->isActivated()) { - return(RADIOLIB_ERR_NETWORK_NOT_JOINED); +void LoRaWANNode::adrBackoff() { + // check if we need to do ADR stuff + uint32_t adrLimit = 0x01 << this->adrLimitExp; + uint32_t adrDelay = 0x01 << this->adrDelayExp; + if((this->fCntUp - this->adrFCnt) >= adrLimit) { + this->adrAckReq = true; + } else { + this->adrAckReq = false; + } + + if ((this->fCntUp - this->adrFCnt) < (adrLimit + adrDelay)) { + return; + } + + // if we hit the Limit + Delay, try one of three, in order: + // set TxPower to max, set DR to min, enable all default channels + + // as we try to do something to improve the range, increase the ADR frame counter by 'ADR delay' + this->adrFCnt += adrDelay; + + // if the TxPower field has some offset, remove it and switch to maximum power + if(this->txPowerSteps > 0) { + // set the maximum power supported by both the module and the band + if(this->setTxPower(this->txPowerMax) == RADIOLIB_ERR_NONE) { + return; + } + } + + // try to decrease the datarate + if(this->channels[RADIOLIB_LORAWAN_UPLINK].dr > this->channels[RADIOLIB_LORAWAN_UPLINK].drMin) { + if(this->setDatarate(this->channels[RADIOLIB_LORAWAN_UPLINK].dr - 1) == RADIOLIB_ERR_NONE) { + return; + } + } + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(false); // revert to default frequencies + } else { + this->selectChannelPlanFix(); // go back to default selected subband + } + this->nbTrans = 1; + return; // nothing else to do +} + +void LoRaWANNode::composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t fPort, bool isConfirmed) { + // set the packet fields + if(isConfirmed) { + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; + this->confFCntUp = this->fCntUp; + } else { + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; + } + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); + + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; + if(this->adrEnabled) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; + if(adrAckReq) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; + } } - Module* mod = this->phyLayer->getMod(); + // check if we have some MAC commands to append + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= this->fOptsUpLen; + + // if the saved confirm-fCnt is set, set the ACK bit + if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; + } + + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fCntUp); + + if(this->fOptsUpLen > 0) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload:"); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(this->fOptsUp, this->fOptsUpLen); + + if(this->rev == 1) { + // in LoRaWAN v1.1, the FOpts are encrypted using the NwkSEncKey + processAES(this->fOptsUp, this->fOptsUpLen, this->nwkSEncKey, &out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x01, true); + } else { + // in LoRaWAN v1.0.x, the FOpts are unencrypted + memcpy(&out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fOptsUp, this->fOptsUpLen); + } + + } + + // set the fPort + out[RADIOLIB_LORAWAN_FHDR_FPORT_POS(this->fOptsUpLen)] = fPort; + + // select encryption key based on the target fPort + uint8_t* encKey = this->appSKey; + if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) { + encKey = this->nwkSEncKey; + } + + // encrypt the frame payload + processAES(in, lenIn, encKey, &out[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(this->fOptsUpLen)], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x00, true); +} + +void LoRaWANNode::micUplink(uint8_t* inOut, uint8_t lenInOut) { + // create blocks for MIC calculation + uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_UPLINK; + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fCntUp); + block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = lenInOut - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); + + uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); + if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + LoRaWANNode::hton(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntDown); + } + block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].idx; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink (FCntUp = %lu) decoded:", (unsigned long)this->fCntUp); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(inOut, lenInOut); + + // calculate authentication codes + memcpy(inOut, block1, RADIOLIB_AES128_BLOCK_SIZE); + uint32_t micS = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->sNwkSIntKey); + memcpy(inOut, block0, RADIOLIB_AES128_BLOCK_SIZE); + uint32_t micF = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->fNwkSIntKey); + + // check LoRaWAN revision + if(this->rev == 1) { + uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0); + LoRaWANNode::hton(&inOut[lenInOut - sizeof(uint32_t)], mic); + } else { + LoRaWANNode::hton(&inOut[lenInOut - sizeof(uint32_t)], micF); + } +} + +int16_t LoRaWANNode::transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len, bool retrans) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); + // check if the Rx windows were closed after sending the previous uplink // this FORCES a user to call downlink() after an uplink() if(this->rxDelayEnd < this->rxDelayStart) { @@ -912,304 +1241,102 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t fPort, bool isCon return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); } - // if adhering to dutyCycle and the time since last uplink + interval has not elapsed, return an error - if(this->dutyCycleEnabled && this->rxDelayStart + (RadioLibTime_t)dutyCycleInterval(this->dutyCycle, this->lastToA) > mod->hal->millis()) { - return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); + RadioLibTime_t tNow = mod->hal->millis(); + // if scheduled uplink time is in the past, reschedule to now + if(this->tUplink < tNow) { + this->tUplink = tNow; } - // check destination fPort - if(fPort > RADIOLIB_LORAWAN_FPORT_RESERVED) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is RFU.", fPort); - return(RADIOLIB_ERR_INVALID_PORT); - } - if(fPort == RADIOLIB_LORAWAN_FPORT_TS009 && this->TS009 == false) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! TS009 was not enabled.", fPort); - } - // fPort 0 is only allowed for MAC-only payloads - if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - if (!this->isMACPayload) { - return(RADIOLIB_ERR_INVALID_PORT); - } - // if this is MAC only payload, continue and reset for next uplink - this->isMACPayload = false; - } - - 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; - if(this->commandsUp.numCommands > 0 && fPort != RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - // there are, assume the maximum possible FOpts len for buffer allocation - fOptsLen = this->commandsUp.len; - } - - // check maximum payload len as defined in phy - if(len > this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]) { - // normally, throw an error if the packet is too long - if(this->TS009 == false) { - return(RADIOLIB_ERR_PACKET_TOO_LONG); - } - // if testing with TS009 Specification Verification Protocol, don't throw error but clip the message - len = this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]; - } - - bool adrAckReq = false; - if(this->adrEnabled) { - // check if we need to do ADR stuff - uint32_t adrLimit = 0x01 << this->adrLimitExp; - uint32_t adrDelay = 0x01 << this->adrDelayExp; - if((this->fCntUp - this->adrFCnt) >= adrLimit) { - adrAckReq = true; - } - // if we hit the Limit + Delay, try one of three, in order: - // set TxPower to max, set DR to min, enable all default channels - if ((this->fCntUp - this->adrFCnt) == (adrLimit + adrDelay)) { - uint8_t adrStage = 1; - while(adrStage != 0) { - switch(adrStage) { - case(1): { - // if the TxPower field has some offset, remove it and switch to maximum power - if(this->txPowerSteps > 0) { - // set the maximum power supported by both the module and the band - state = this->setTxPower(this->txPowerMax); - if(state == RADIOLIB_ERR_NONE) { - this->txPowerSteps = 0; - adrStage = 0; // successfully did some ADR stuff - } - } - if(adrStage == 1) { // if nothing succeeded, proceed to stage 2 - adrStage = 2; - } - } - break; - case(2): { - // try to decrease the datarate - if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > 0) { - if(this->setDatarate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] - 1) == RADIOLIB_ERR_NONE) { - adrStage = 0; // successfully did some ADR stuff - } - } - if(adrStage == 2) { // if nothing succeeded, proceed to stage 3 - adrStage = 3; - } - } - break; - case(3): { - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannelsDyn(false); // revert to default frequencies - } else { - // go back to default selected subband - // hopefully it'll help something, but probably not; at least we tried.. - this->setupChannelsFix(this->subBand); - } - adrStage = 0; // nothing else to do, so end the cycle - } - break; - } - } - - // we tried something to improve the range, so increase the ADR frame counter by 'ADR delay' - this->adrFCnt += adrDelay; + // if dutycycle is enabled and the time since last uplink + interval has not elapsed, return an error + // but: don't check this for retransmissions + if(!retrans && this->dutyCycleEnabled) { + if(this->rxDelayStart + (RadioLibTime_t)dutyCycleInterval(this->dutyCycle, this->lastToA) > this->tUplink) { + return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); } } // set the physical layer configuration for uplink - this->selectChannels(); - state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); + state = this->setPhyProperties(chnl, + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); RADIOLIB_ASSERT(state); - - // if dwell time is imposed, calculated expected time on air and cancel if exceeds - if(this->dwellTimeEnabledUp && this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_FRAME_LEN(len, fOptsLen) - 16)/1000 > this->dwellTimeUp) { - return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); - } - - // build the uplink message - // the first 16 bytes are reserved for MIC calculation blocks - size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, fOptsLen); - #if RADIOLIB_STATIC_ONLY - uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen]; - #endif - // set the packet fields - if(isConfirmed) { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; - this->confFCntUp = this->fCntUp; - } else { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; - } - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); - - // length of fOpts will be added later - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; - if(this->adrEnabled) { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; - if(adrAckReq) { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; + // if requested, wait until transmitting uplink + tNow = mod->hal->millis(); + if(this->tUplink > tNow) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow)); + if(this->tUplink > mod->hal->millis()) { + mod->hal->delay(this->tUplink - mod->hal->millis()); } } - // if the saved confirm-fCnt is set, set the ACK bit - bool isConfirmingDown = false; - if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { - isConfirmingDown = true; - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; - } - - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fCntUp); - - // check if we have some MAC commands to append - if(fOptsLen > 0) { - // assume maximum possible buffer size - uint8_t fOptsBuff[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN]; - - this->macQueueToBuff(&(this->commandsUp), fOptsBuff); - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsBuff, fOptsLen); - - uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, fOptsLen); - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= fOptsLen; - - if(this->rev == 1) { - // in LoRaWAN v1.1, the FOpts are encrypted using the NwkSEncKey - processAES(fOptsBuff, fOptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fCntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); - } else { - // in LoRaWAN v1.0.x, the FOpts are unencrypted - memcpy(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fOptsBuff, fOptsLen); - } - - } - - // set the fPort - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsLen)] = fPort; - - // select encryption key based on the target fPort - uint8_t* encKey = this->appSKey; - if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - encKey = this->nwkSEncKey; - } - - // encrypt the frame payload - processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsLen)], this->fCntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); - - // perform CSMA if enabled (do it now already, because MIC calculation depends on this) - if (enableCSMA) { - performCSMA(); - } - - // create blocks for MIC calculation - uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; - block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fCntUp); - block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = uplinkMsgLen - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); - - uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); - if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { - LoRaWANNode::hton(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntDown); - } - 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("Uplink (FCntUp = %lu) decoded:", (unsigned long)this->fCntUp); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(uplinkMsg, uplinkMsgLen); - - // calculate authentication codes - memcpy(uplinkMsg, block1, RADIOLIB_AES128_BLOCK_SIZE); - uint32_t micS = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->sNwkSIntKey); - memcpy(uplinkMsg, block0, RADIOLIB_AES128_BLOCK_SIZE); - uint32_t micF = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->fNwkSIntKey); - - // check LoRaWAN revision - if(this->rev == 1) { - uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0); - LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic); - } else { - LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF); - } - - // send it (without the MIC calculation blocks) - state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS); + state = this->phyLayer->transmit(in, len); // set the timestamp so that we can measure when to start receiving this->rxDelayStart = mod->hal->millis(); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink sent <-- Rx Delay start"); - // calculate Time on Air of this uplink in milliseconds - this->lastToA = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000; + // increase Time on Air of the uplink sequence + this->lastToA += this->phyLayer->getTimeOnAir(len) / 1000; - #if !RADIOLIB_STATIC_ONLY - delete[] uplinkMsg; - #endif - RADIOLIB_ASSERT(state); - - // the downlink confirmation was acknowledged, so clear the counter value - this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; - - // pass the extra info if requested - if(event) { - event->dir = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; - event->confirmed = isConfirmed; - event->confirming = isConfirmingDown; - event->datarate = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; - event->freq = currentChannels[event->dir].freq; - event->power = this->txPowerMax - this->txPowerSteps * 2; - event->fCnt = this->fCntUp; - event->fPort = fPort; - } - - // increase frame counter by one for the next uplink - this->fCntUp += 1; - - return(RADIOLIB_ERR_NONE); + return(state); } -int16_t LoRaWANNode::downlinkCommon() { +// flag to indicate whether there was some action during Rx mode (timeout or downlink) +static volatile bool downlinkAction = false; + +// interrupt service routine to handle downlinks automatically +#if defined(ESP8266) || defined(ESP32) + IRAM_ATTR +#endif +static void LoRaWANNodeOnDownlinkAction(void) { + downlinkAction = true; +} + +int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChannels, const RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference) { Module* mod = this->phyLayer->getMod(); + int16_t state = RADIOLIB_ERR_UNKNOWN; + // check if there are any upcoming Rx windows // if the Rx1 window has already started, you're too late, because most downlinks happen in Rx1 RadioLibTime_t now = mod->hal->millis(); // fix the current timestamp to prevent negative delays - if(now > this->rxDelayStart + this->rxDelays[0] - this->scanGuard) { - // if between start of Rx1 and end of Rx2, wait until Rx2 closes - if(now < this->rxDelayStart + this->rxDelays[1]) { - mod->hal->delay(this->rxDelays[1] + this->rxDelayStart - now); + if(now > tReference + dlDelays[1] - this->scanGuard) { + // if function was called while Rx windows are in progress, + // wait until last window closes to prevent very bad stuff + if(now < tReference + dlDelays[numWindows]) { + mod->hal->delay(dlDelays[numWindows] + tReference - now); } // update the end timestamp in case user got stuck between uplink and downlink this->rxDelayEnd = mod->hal->millis(); return(RADIOLIB_ERR_NO_RX_WINDOW); } - // set the physical layer configuration for downlink - int16_t state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); - RADIOLIB_ASSERT(state); - - // create the masks that are required for receiving downlinks - RadioLibIrqFlags_t irqFlags = (1UL << RADIOLIB_IRQ_RX_DONE) | (1UL << RADIOLIB_IRQ_TIMEOUT); - RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK; - + // setup interrupt this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction); + RadioLibTime_t tOpen = 0; int16_t timedOut = 0; - // perform listening in the two Rx windows - for(uint8_t i = 0; i < 2; i++) { + // listen during the specified windows + uint8_t window = 1; + for(; window <= numWindows; window++) { downlinkAction = false; + // set the physical layer configuration for downlink + this->phyLayer->standby(); + state = this->setPhyProperties(&dlChannels[window], dir, this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + // calculate the Rx timeout RadioLibTime_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*this->scanGuard*1000; RadioLibTime_t timeoutMod = this->phyLayer->calculateRxTimeout(timeoutHost); // wait for the start of the Rx window - RadioLibTime_t waitLen = this->rxDelayStart + this->rxDelays[i] - mod->hal->millis(); + RadioLibTime_t waitLen = tReference + dlDelays[window] - mod->hal->millis(); // make sure that no underflow occured; if so, clip the delay (although this will likely miss any downlink) - if(waitLen > this->rxDelays[i]) { - waitLen = this->rxDelays[i]; + if(waitLen > dlDelays[window]) { + waitLen = dlDelays[window]; } // the waiting duration is shortened a bit to cover any possible timing errors if(waitLen > this->scanGuard) { @@ -1218,120 +1345,87 @@ int16_t LoRaWANNode::downlinkCommon() { mod->hal->delay(waitLen); // open Rx window by starting receive with specified timeout - state = this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0); + // TODO remove default arguments + state = this->phyLayer->startReceive(timeoutMod, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + tOpen = mod->hal->millis(); RADIOLIB_ASSERT(state); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", i+1, (int)(timeoutHost / 1000 + scanGuard / 2)); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", window, (int)(timeoutHost / 1000 + scanGuard / 2)); // wait for the timeout to complete (and a small additional delay) mod->hal->delay(timeoutHost / 1000 + this->scanGuard / 2); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", i+1); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", window); - // check if the IRQ bit for Rx Timeout is set + // if the IRQ bit for Rx Timeout is not set, something is received, so stop the windows timedOut = this->phyLayer->checkIrq(RADIOLIB_IRQ_TIMEOUT); if(timedOut == RADIOLIB_ERR_UNSUPPORTED) { return(timedOut); } if(!timedOut) { break; - - } else if(i == 0) { - // nothing in the first window, configure for the second - this->phyLayer->standby(); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %6.3f MHz", 'D', this->rx2.freq); - state = this->phyLayer->setFrequency(this->rx2.freq); - RADIOLIB_ASSERT(state); - - DataRate_t dataRate; - state = findDataRate(this->rx2.drMax, &dataRate); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setDataRate(dataRate); - RADIOLIB_ASSERT(state); } - } // Rx windows are now closed this->rxDelayEnd = mod->hal->millis(); // if we got here due to a timeout, stop ongoing activities if(timedOut) { - this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { - this->phyLayer->invertIQ(false); - } - - return(RADIOLIB_LORAWAN_NO_DOWNLINK); + this->phyLayer->standby(); + return(RADIOLIB_ERR_NONE); } - // wait for the DIO to fire indicating a downlink is received - now = mod->hal->millis(); + // get the maximum allowed Time-on-Air of a packet given the current datarate + uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + if(this->TS011) { + maxPayLen = RADIOLIB_MIN(maxPayLen, 230); // payload length is limited to 230 if under repeater + } + RadioLibTime_t tMax = this->phyLayer->getTimeOnAir(maxPayLen); bool downlinkComplete = true; + + // wait for the DIO to fire indicating a downlink is received while(!downlinkAction) { mod->hal->yield(); - // this should never happen, but if it does this would be an infinite loop - // TODO scale this value using expected ToA of maximum allowed payload length - if(mod->hal->millis() - now > 3000UL) { + // stay in Rx mode for the maximum allowed Time-on-Air plus small grace period + if(mod->hal->millis() - tOpen > tMax + scanGuard) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink missing!"); downlinkComplete = false; break; } } - // we have a message, clear actions, go to standby and reset the IQ inversion - this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? + // update time of downlink reception + if(downlinkComplete) { + this->tDownlink = mod->hal->millis(); + } + + // we have a message, clear actions, go to standby this->phyLayer->clearPacketReceivedAction(); - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { - state = this->phyLayer->invertIQ(false); - RADIOLIB_ASSERT(state); - } + this->phyLayer->standby(); + // if all windows passed without receiving anything, return 0 if(!downlinkComplete) { - state = RADIOLIB_LORAWAN_NO_DOWNLINK; + state = 0; + + // if we received something during a window, return the window number + } else { + state = window; + } + + // Any frame received by an end-device containing a MACPayload greater than + // the specified maximum length M over the data rate used to receive the frame + // SHALL be silently discarded. + if(this->phyLayer->getPacketLength() > maxPayLen) { + return(0); // act as if no downlink was received } return(state); } -#if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::downlink(String& str, LoRaWANEvent_t* event) { +int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) { int16_t state = RADIOLIB_ERR_UNKNOWN; - - // build a temporary buffer - // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL - size_t length = 0; - uint8_t data[251]; - - // wait for downlink - state = this->downlink(data, &length, event); - if(state == RADIOLIB_ERR_NONE) { - // add null terminator - data[length] = '\0'; - - // initialize Arduino String class - str = String((char*)data); - } - - return(state); -} -#endif - -int16_t LoRaWANNode::downlink(LoRaWANEvent_t* event) { - int16_t state = RADIOLIB_ERR_UNKNOWN; - - // build a temporary buffer - // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL - size_t length = 0; - uint8_t data[251]; - - // wait for downlink - state = this->downlink(data, &length, event); - - return(state); -} - -int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) { - // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received - int16_t state = downlinkCommon(); - RADIOLIB_ASSERT(state); + + // set user-data length to 0 to prevent undefined behaviour in case of bad use + // if there is user-data, this will be handled at the appropriate place + *len = 0; // get the packet length size_t downlinkMsgLen = this->phyLayer->getPacketLength(); @@ -1376,44 +1470,15 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } - // calculate length of FOpts and payload - uint8_t fOptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; + // calculate length of piggy-backed FOpts + uint8_t fOptsPbLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; - // check if the ACK bit is set, indicating this frame acknowledges the previous uplink - bool isConfirmingUp = false; - if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK)) { - isConfirmingUp = true; - } - - // total - MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOpts - MIC(4) - // potentially also an FPort, but we'll find out soon enough - uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsLen - 4; - - // get the frame counter - uint16_t fCnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); - - // set the MIC calculation blocks - memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); - downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; - // if this downlink is confirming an uplink, the MIC was generated with the least-significant 16 bits of that fCntUp - if(isConfirmingUp && (this->rev == 1)) { - LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntUp); - } - downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK; - LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt16); - downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t); - - // check the MIC - if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - return(RADIOLIB_ERR_CRC_MISMATCH); - } + // MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOptsPb - Payload - MIC(4) + // potentially also an FPort, will find out next + uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsPbLen - 4; // in LoRaWAN v1.1, a frame is a Network frame if there is no Application payload - // i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload (FPort = 0) + // i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload uint8_t fPort = RADIOLIB_LORAWAN_FPORT_MAC_COMMAND; bool isAppDownlink = false; if(this->rev == 0) { @@ -1421,32 +1486,52 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) } if(payLen > 0) { payLen -= 1; // subtract one as fPort is set - fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsLen)]; + fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsPbLen)]; + // check if fPort value is actually allowed - if(fPort > RADIOLIB_LORAWAN_FPORT_RESERVED) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Received downlink at FPort %d - rejected! This FPort is RFU!", fPort); - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - return(RADIOLIB_ERR_INVALID_PORT); - } - if(fPort == RADIOLIB_LORAWAN_FPORT_TS009 && this->TS009 == false) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Received downlink at FPort %d - rejected! TS009 was not enabled.", fPort); - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - return(RADIOLIB_ERR_INVALID_PORT); + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: { + // payload consists of all MAC commands (or is empty) + } break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: { + // payload is user-defined (or empty) - may carry piggybacked MAC commands + isAppDownlink = true; + } break; + case RADIOLIB_LORAWAN_FPORT_TS009: { + // TS009 FPort only good if overruled during verification testing + if(!this->TS009) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + isAppDownlink = true; + } break; + case RADIOLIB_LORAWAN_FPORT_TS011: { + // TS011 FPort only good if overruled during relay exchange + if(!this->TS011) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + isAppDownlink = true; + } break; + default: { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is reserved.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } break; } - if(fPort > RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - isAppDownlink = true; - } else { - fOptsLen = payLen; - } } - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %d) encoded:", isAppDownlink ? "A" : "N", fCnt16); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); + + // get the frame counter + uint16_t fCnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); // check the fCntDown value (Network or Application) uint32_t fCntDownPrev = 0; @@ -1475,6 +1560,33 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) fCnt32 |= ((uint32_t)msb << 16); // add back the MSB part } } + + // check if the ACK bit is set, indicating this frame acknowledges the previous uplink + bool isConfirmingUp = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK)) { + isConfirmingUp = true; + } + + // set the MIC calculation blocks + memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); + downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + // if this downlink is confirming an uplink, the MIC was generated with the least-significant 16 bits of that fCntUp + // (LoRaWAN v1.1 only) + if(isConfirmingUp && (this->rev == 1)) { + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntUp); + } + downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_DOWNLINK; + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt32); + downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t); + + // check the MIC + if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_CRC_MISMATCH); + } // save current fCnt to respective frame counter if (isAppDownlink) { @@ -1483,196 +1595,1073 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) this->nFCntDown = fCnt32; } + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %lu) encoded:", + isAppDownlink ? "A" : "N", + (unsigned long)(isAppDownlink ? this->aFCntDown : this->nFCntDown)); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); + // if this is a confirmed frame, save the downlink number (only app frames can be confirmed) bool isConfirmedDown = false; if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { this->confFCntDown = this->aFCntDown; isConfirmedDown = true; } + + // a downlink was received, so reset the ADR counter to the last uplink's fCnt + this->adrFCnt = this->getFCntUp(); + // if this downlink is on FPort 0, the FOptsLen is the length of the payload + // in any other case, the payload (length) is user accessible + uint8_t fOptsLen = fOptsPbLen; + if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && payLen > 0) { + fOptsLen = payLen; + } else { + *len = payLen; + } + + #if !RADIOLIB_STATIC_ONLY + uint8_t* fOpts = new uint8_t[fOptsLen]; + #else + uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // figure out if the payload should end up in user data or internal FOpts buffer + uint8_t* dest; + if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { + dest = fOpts; + } else { + dest = data; + } + + // figure out which key to use to decrypt the payload + uint8_t* encKey = this->appSKey; + if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) { + encKey = this->nwkSEncKey; + } + + // decrypt the frame payload + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsPbLen)], payLen, encKey, dest, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true); + + // decrypt any piggy-backed FOpts + if(fOptsPbLen > 0) { + // the decryption depends on the LoRaWAN version + if(this->rev == 1) { + // in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey + uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, ctrId, true); + } else { + // in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted + memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen); + } + } + + // clear the previous MAC commands, if any + memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + + // process FOpts (if there are any) + uint8_t cid; + uint8_t fLen = 1; + uint8_t* mPtr = fOpts; + uint8_t procLen = 0; + + #if !RADIOLIB_STATIC_ONLY + uint8_t* fOptsRe = new uint8_t[250]; + #else + uint8_t fOptsRe[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + uint8_t fOptsReLen = 0; + + // indication whether LinkAdr MAC command has been processed + bool mAdr = false; + + while(procLen < fOptsLen) { + cid = *mPtr; // MAC id is the first byte + state = this->getMacLen(cid, &fLen, RADIOLIB_LORAWAN_DOWNLINK, true); + RADIOLIB_ASSERT(state); + uint8_t fLenRe = 0; + state = this->getMacLen(cid, &fLenRe, RADIOLIB_LORAWAN_UPLINK, true); + RADIOLIB_ASSERT(state); + + if(procLen + fLen > fOptsLen) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Incomplete MAC command %02x (%d bytes, expected %d)", cid, fOptsLen, fLen); + return(RADIOLIB_ERR_INVALID_CID); + } + + bool reply = false; + + // if this is a LinkAdr MAC command, pre-process contiguous commands into one atomic block + if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) { + // if there was any LinkAdr command before, set NACK and continue without processing + if(mAdr) { + reply = true; + fOptsRe[fOptsReLen + 1] = 0x00; + + // if this is the first LinkAdr command, do some special treatment: + } else { + mAdr = true; + uint8_t fAdrLen = 5; + uint8_t mAdrOpt[14] = { 0 }; + + // retrieve all contiguous LinkAdr commands + while(procLen + fLen + fAdrLen < fOptsLen + 1 && *(mPtr + fLen) == RADIOLIB_LORAWAN_MAC_LINK_ADR) { + fLen += 5; // ADR command is 5 bytes + fLenRe += 2; // ADR response is 2 bytes + } + + // pre-process them into a single complete channel mask (stored in mAdrOpt) + LoRaWANNode::preprocessMacLinkAdr(mPtr, fLen, mAdrOpt); + + // execute like a normal MAC command (but pointing to mAdrOpt instead) + reply = this->execMacCommand(cid, mAdrOpt, 14, &fOptsRe[fOptsReLen + 1]); + + // in LoRaWAN v1.0.x, all ACK bytes should have equal status - fix in post-processing + if(this->rev == 0) { + LoRaWANNode::postprocessMacLinkAdr(&fOptsRe[fOptsReLen], fLen); + + // in LoRaWAN v1.1, just provide one ACK, so no post-processing but cut off reply length + } else { + fLenRe = 2; + } + } + + // MAC command other than LinkAdr, just process the payload + } else { + reply = this->execMacCommand(cid, mPtr + 1, fLen - 1, &fOptsRe[fOptsReLen + 1]); + } + + if(reply) { + fOptsRe[fOptsReLen] = cid; + fOptsReLen += fLenRe; + } + + procLen += fLen; + mPtr += fLen; + } + + // remove all MAC commands except those whose payload can be requested by the user + // (which are LinkCheck and DeviceTime) + if(fOptsLen > 0) { + LoRaWANNode::clearMacCommands(fOpts, &fOptsLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(this->fOptsDown, fOpts, fOptsLen); + } + this->fOptsDownLen = fOptsLen; + + // if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink + if(fOptsReLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC-only payload (%d bytes):", fOptsReLen); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsRe, fOptsReLen); + + this->isMACPayload = true; + // temporarily lift dutyCycle restrictions to allow immediate MAC response + bool prevDC = this->dutyCycleEnabled; + this->dutyCycleEnabled = false; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. "); + + this->sendReceive(fOptsRe, fOptsReLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); + + this->dutyCycleEnabled = prevDC; + + } else { // fOptsReLen <= 15 + memcpy(this->fOptsUp, fOptsRe, fOptsReLen); + this->fOptsUpLen = fOptsReLen; + } + // pass the extra info if requested if(event) { - event->dir = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK; + event->dir = RADIOLIB_LORAWAN_DOWNLINK; event->confirmed = isConfirmedDown; event->confirming = isConfirmingUp; - event->datarate = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]; - event->freq = currentChannels[event->dir].freq; + event->datarate = this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr; + event->freq = channels[event->dir].freq / 10000.0; event->power = this->txPowerMax - this->txPowerSteps * 2; event->fCnt = isAppDownlink ? this->aFCntDown : this->nFCntDown; event->fPort = fPort; } - // clear the previous MAC commands, if any - memset(&(this->commandsDown), 0, sizeof(LoRaWANMacCommandQueue_t)); - - // process FOpts (if there are any) - if(fOptsLen > 0) { - // there are some Fopts, decrypt them - #if !RADIOLIB_STATIC_ONLY - uint8_t* fOpts = new uint8_t[RADIOLIB_MAX(RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK, (int)fOptsLen)]; - #else - uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE]; - #endif - - // it COULD be the case that the assumed FCnt rollover is incorrect, in which case the following MAC decryption fails... - - if(payLen > 0 && fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - // if the MAC payload is in the payload, process AES is if it were a normal payload but using the NwkSEncKey instead - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], (size_t)fOptsLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); - } else { - // if the MAC payload is piggybacked in the FHDR fields, the decryption depends on the LoRaWAN version. - if(this->rev == 1) { - // in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey - uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, ctrId, true); - } else { - // in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted - memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsLen); - } - } - - // parse the fOpts into the MAC queue - this->macBufftoQueue(&(this->commandsDown), fOpts, fOptsLen); - - // process the MAC queue commands (any response is modified in-place) - int16_t i = 0; - for(; i < this->commandsDown.numCommands; i++) { - bool sendUp = execMacCommand(&(this->commandsDown.commands[i])); - if(sendUp) { - pushMacCommand(&(this->commandsDown.commands[i]), &(this->commandsUp)); - } - } - - // pop the commands from back to front - for (; i >= 0; i--) { - if(this->commandsDown.commands[i].repeat > 0) { - this->commandsDown.commands[i].repeat--; - } else { - deleteMacCommand(this->commandsDown.commands[i].cid, &(this->commandsDown)); - } - } - - #if !RADIOLIB_STATIC_ONLY - delete[] fOpts; - #endif - - // if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink - if(this->commandsUp.len > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { - size_t fOptsBufSize = this->commandsUp.len; - #if RADIOLIB_STATIC_ONLY - uint8_t fOptsBuff[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* fOptsBuff = new uint8_t[fOptsBufSize]; - #endif - - this->macQueueToBuff(&(this->commandsUp), fOptsBuff); - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsBuff, fOptsBufSize); - - this->isMACPayload = true; - // temporarily lift dutyCycle restrictions to allow immediate MAC response - bool prevDC = this->dutyCycleEnabled; - this->dutyCycleEnabled = false; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. "); - state = this->uplink(fOptsBuff, fOptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN(" .. state: %d", state); - this->dutyCycleEnabled = prevDC; - #if !RADIOLIB_STATIC_ONLY - delete[] fOptsBuff; - #endif - RADIOLIB_ASSERT(state); - - #if RADIOLIB_STATIC_ONLY - uint8_t strDown[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* strDown = new uint8_t[this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]]; - #endif - size_t lenDown = 0; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Receiving after MAC-only uplink .. "); - state = this->downlink(strDown, &lenDown); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN(" .. state: %d", state); - #if !RADIOLIB_STATIC_ONLY - delete[] strDown; - #endif - RADIOLIB_ASSERT(state); - } - - } - - // a downlink was received, so reset the ADR counter to the last uplink's fCnt - this->adrFCnt = this->getFCntUp(); - - // if MAC-only payload, return now - if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - // no payload - *len = 0; - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - - return(RADIOLIB_ERR_NONE); - } - - // process Application payload - *len = payLen; - - // TODO it COULD be the case that the assumed rollover is incorrect, then figure out a way to catch this and retry with just fCnt16 - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsLen)], payLen, this->appSKey, data, fCnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); - #if !RADIOLIB_STATIC_ONLY + delete[] fOpts; + delete[] fOptsRe; delete[] downlinkMsg; #endif return(RADIOLIB_ERR_NONE); } -#if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(strUp, fPort, isConfirmed, eventUp); - RADIOLIB_ASSERT(state); - - // wait for the downlink - state = this->downlink(strDown, eventDown); - return(state); +bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn) { + uint8_t buff[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN]; + return(this->execMacCommand(cid, optIn, lenIn, buff)); } -#endif -int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(dataUp, lenUp, fPort, isConfirmed, eventUp); - RADIOLIB_ASSERT(state); +bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[MAC] 0x%02x", cid); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(optIn, lenIn); - // wait for the downlink - state = this->downlink(eventDown); + if(cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) { + // TODO call user-provided callback for proprietary MAC commands? + return(false); + } + + switch(cid) { + case(RADIOLIB_LORAWAN_MAC_RESET): { + // get the server version + uint8_t srvVersion = optIn[0]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ResetConf: server version 1.%d", srvVersion); + if(srvVersion == this->rev) { + // valid server version, stop sending the ResetInd MAC command + LoRaWANNode::deleteMacCommand(RADIOLIB_LORAWAN_MAC_RESET, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + } + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]"); + + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_LINK_ADR): { + // get the ADR configuration + uint8_t macDrUp = (optIn[0] & 0xF0) >> 4; + uint8_t macTxSteps = optIn[0] & 0x0F; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrReq: dataRate = %d, txSteps = %d, nbTrans = %d", macDrUp, macTxSteps, lenIn > 1 ? optIn[13] : 0); + + uint8_t chMaskAck = 0; + uint8_t drAck = 0; + uint8_t pwrAck = 0; + + // first, get current configuration + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + uint16_t chMaskActive = 0; + (void)this->getAvailableChannels(&chMaskActive); + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + + // only apply channel mask if present (internal Dr/Tx commands do not set channel mask) + if(lenIn > 1) { + uint64_t macChMaskGrp0123 = LoRaWANNode::ntoh(&optIn[1]); + uint32_t macChMaskGrp45 = LoRaWANNode::ntoh(&optIn[9]); + // apply requested channel mask and enable all of them for testing datarate + chMaskAck = this->applyChannelMask(macChMaskGrp0123, macChMaskGrp45); + } else { + chMaskAck = true; + } + + this->setAvailableChannels(0xFFFF); + + int16_t state; + + // try to apply the datarate configuration + // if value is set to 'keep current values', retrieve current value + if(macDrUp == 0x0F) { + macDrUp = currentDr; + } + + if (this->band->dataRates[macDrUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + // check if the module supports this data rate + DataRate_t dr; + state = this->findDataRate(macDrUp, &dr); + + // if datarate in hardware all good, set datarate for now + // and check if there are any available Tx channels for this datarate + if(state == RADIOLIB_ERR_NONE) { + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = macDrUp; + + // only if we have available Tx channels, we set an Ack + if(this->getAvailableChannels(NULL) > 0) { + drAck = 1; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: no channels available for datarate %d", macDrUp); + } + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: hardware failure configurating datarate %d, code %d", macDrUp, state); + } + + } + + // try to apply the power configuration + // if value is set to 'keep current values', retrieve current value + if(macTxSteps == 0x0F) { + macTxSteps = this->txPowerSteps; + } + + int8_t power = this->txPowerMax - 2*macTxSteps; + int8_t powerActual = 0; + state = this->phyLayer->checkOutputPower(power, &powerActual); + // only acknowledge if the radio is able to operate at or below the requested power level + if(state == RADIOLIB_ERR_NONE || (state == RADIOLIB_ERR_INVALID_OUTPUT_POWER && powerActual < power)) { + pwrAck = 1; + this->txPowerSteps = macTxSteps; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure Tx power %d, code %d!", power, state); + } + + // set ACK bits + optOut[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0); + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrAns: %02x", optOut[0]); + + // if ACK not completely successful, revert and stop + if(optOut[0] != 0x07) { + this->applyChannelMask(chMaskGrp0123, chMaskGrp45); + this->setAvailableChannels(chMaskActive); + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr; + // Tx power was not modified + return(true); + } + + // ACK successful, so apply and save + this->txPowerSteps = macTxSteps; + if(lenIn > 1) { + uint8_t macNbTrans = optIn[13] & 0x0F; + if(macNbTrans) { // if there is a value for NbTrans, set this value + this->nbTrans = macNbTrans; + } + } + + // restore original active channels + this->setAvailableChannels(chMaskActive); + + // save to the ADR MAC location + // but first re-set the Dr/Tx/NbTrans field to make sure they're not set to 0xF + optIn[0] = (this->channels[RADIOLIB_LORAWAN_UPLINK].dr) << 4; + optIn[0] |= this->txPowerSteps; + if(lenIn > 1) { + optIn[13] = this->nbTrans; + } + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DUTY_CYCLE): { + uint8_t maxDutyCycle = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle); + if(maxDutyCycle == 0) { + this->dutyCycle = this->band->dutyCycle; + } else { + this->dutyCycle = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000 / (RadioLibTime_t)(1UL << maxDutyCycle); + } + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP): { + // get the configuration + uint8_t macRx1DrOffset = (optIn[0] & 0x70) >> 4; + uint8_t macRx2Dr = optIn[0] & 0x0F; + uint32_t macRx2Freq = LoRaWANNode::ntoh(&optIn[1], 3); + + uint8_t rx1DrOsAck = 0; + uint8_t rx2DrAck = 0; + uint8_t rx2FreqAck = 0; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupReq: Rx1DrOffset = %d, rx2DataRate = %d, freq = %7.3f", + macRx1DrOffset, macRx2Dr, macRx2Freq / 10000.0); + + // check the requested configuration + uint8_t uplinkDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + DataRate_t dr; + if(this->band->rx1DrTable[uplinkDr][macRx1DrOffset] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + if(this->findDataRate(this->band->rx1DrTable[uplinkDr][macRx1DrOffset], &dr) == RADIOLIB_ERR_NONE) { + rx1DrOsAck = 1; + } + } + if(macRx2Dr >= this->band->rx2.drMin && macRx2Dr <= this->band->rx2.drMax) { + if(this->band->dataRates[macRx2Dr] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + if(this->findDataRate(macRx2Dr, &dr) == RADIOLIB_ERR_NONE) { + rx2DrAck = 1; + } + } + } + if(macRx2Freq >= this->band->freqMin && macRx2Freq <= this->band->freqMax) { + if(this->phyLayer->setFrequency(macRx2Freq / 10000.0) == RADIOLIB_ERR_NONE) { + rx2FreqAck = 1; + } + } + optOut[0] = (rx1DrOsAck << 2) | (rx2DrAck << 1) | (rx2FreqAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x07) { + return(true); + } + + // passed ACK, so apply configuration + this->rx1DrOffset = macRx1DrOffset; + this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = macRx2Dr; + this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq = macRx2Freq; + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DEV_STATUS): { + // set the uplink reply + optOut[0] = this->battLevel; + int8_t snr = this->phyLayer->getSNR(); + optOut[1] = snr & 0x3F; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", optOut[0], optOut[1]); + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_NEW_CHANNEL): { + // only implemented on dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(false); + } + + // get the configuration + uint8_t macChIndex = optIn[0]; + uint32_t macFreq = LoRaWANNode::ntoh(&optIn[1], 3); + uint8_t macDrMax = (optIn[4] & 0xF0) >> 4; + uint8_t macDrMin = optIn[4] & 0x0F; + + uint8_t newChAck = 0; + uint8_t freqAck = 0; + + // on LoRaWAN v1.1, the default channels may be modified - not on v1.0.x. + // in that case, only allow non-default channels to be modified + // there are at most three default channels, so either check for >2 or else if index is used + if(this->rev == 1 || macChIndex > 2 || this->band->txFreqs[macChIndex].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + newChAck = 1; + } + + // check if the frequency is allowed and possible + if(macFreq >= this->band->freqMin && macFreq <= this->band->freqMax) { + if(this->phyLayer->setFrequency((float)macFreq / 10000.0) == RADIOLIB_ERR_NONE) { + freqAck = 1; + } + // otherwise, if frequency is 0, disable the channel which is also a valid option + } else if(macFreq == 0) { + freqAck = 1; + } + + // set ACK bits + optOut[0] = (newChAck << 1) | (freqAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x03) { + return(true); + } + + // ACK successful, so apply and save + if(macFreq > 0) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled = true; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx = macChIndex; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq = macFreq; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin = macDrMin; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax = macDrMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].available = true; + // downlink channel is identical to uplink channel + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex]; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE; + + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax, + + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].idx, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].enabled, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMin, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMax + ); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + macChIndex * lenIn, optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DL_CHANNEL): { + // only implemented on dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(false); + } + + // get the configuration + uint8_t macChIndex = optIn[0]; + uint32_t macFreq = LoRaWANNode::ntoh(&optIn[1], 3); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelReq: index = %d, freq = %7.3f MHz", macChIndex, macFreq / 10000.0); + uint8_t freqDlAck = 0; + uint8_t freqUlAck = 0; + + // check if the frequency is allowed possible + if(macFreq >= this->band->freqMin && macFreq <= this->band->freqMax) { + if(this->phyLayer->setFrequency(macFreq / 10000.0) == RADIOLIB_ERR_NONE) { + freqDlAck = 1; + } + } + + // check if the corresponding uplink frequency is actually set + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq > 0) { + freqUlAck = 1; + } + + // set ACK bits + optOut[0] = (freqUlAck << 1) | (freqDlAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x03) { + return(true); + } + + // ACK successful, so apply and save + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq = macFreq; + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS] + macChIndex * lenIn, optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): { + // get the configuration + uint8_t delay = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay); + + // apply the configuration + if(delay == 0) { + delay = 1; + } + this->rxDelays[1] = delay * 1000; // Rx1 delay + this->rxDelays[2] = this->rxDelays[1] + 1000; // Rx2 delay + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP): { + // TxParamSetupReq is only supported on a subset of bands + // in other bands, silently ignore without response + if(!this->band->txParamSupported) { + return(false); + } + uint8_t dlDwell = (optIn[0] & 0x20) >> 5; + uint8_t ulDwell = (optIn[0] & 0x10) >> 4; + uint8_t maxEirpRaw = optIn[0] & 0x0F; + + // 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("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; + + this->dwellTimeEnabledDn = dlDwell ? true : false; + this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_REKEY): { + // get the server version + uint8_t srvVersion = optIn[0]; + 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 + LoRaWANNode::deleteMacCommand(RADIOLIB_LORAWAN_MAC_REKEY, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + } + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): { + this->adrLimitExp = (optIn[0] & 0xF0) >> 4; + this->adrDelayExp = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DEVICE_TIME): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DeviceTimeAns: [user]"); + + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): { + // TODO implement this + uint16_t rejoinReq = LoRaWANNode::ntoh(optIn); + 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("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); + (void)period; + (void)maxRetries; + (void)rejoinType; + (void)dr; + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP): { + // TODO implement this + uint8_t maxTime = (optIn[0] & 0xF0) >> 4; + uint8_t maxCount = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], optIn, lenIn); + + lenIn = 0; + optIn[0] = (1 << 1) | 1; + + (void)maxTime; + (void)maxCount; + return(true); + } break; + + default: { + // derived classes may implement additional MAC commands + return(derivedMacHandler(cid, optIn, lenIn, optOut)); + } + } + + return(false); +} + +bool LoRaWANNode::derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) { + (void)cid; + (void)optIn; + (void)lenIn; + (void)optOut; + return(false); +} + +void LoRaWANNode::preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt) { + uint8_t fLen = 5; // single ADR command is 5 bytes + uint8_t numOpts = cLen / fLen; + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + + // set Dr/Tx field from last MAC command + mAdrOpt[0] = mPtr[cLen - fLen + 1]; + + // set NbTrans partial field from last MAC command + mAdrOpt[13] = mPtr[cLen - fLen + 4] & 0x0F; + + uint8_t opt = 0; + while(opt < numOpts) { + uint8_t chMaskCntl = (mPtr[opt * fLen + 4] & 0x70) >> 4; + uint16_t chMask = LoRaWANNode::ntoh(&mPtr[opt * fLen + 2]); + switch(chMaskCntl) { + case 0 ... 3: + chMaskGrp0123 |= (uint64_t)chMask << (16 * chMaskCntl); + break; + case 4: + chMaskGrp45 |= (uint32_t)chMask; + break; + case 5: + // for CN500, this is just a normal channel mask + // for all other bands, the first 10 bits enable banks of 8 125kHz channels + if(this->band->bandNum == BandCN500) { + chMaskGrp45 |= (uint32_t)chMask << 16; + } else { + int bank = 0; + for(; bank < 8; bank++) { + if(chMask & ((uint16_t)1 << bank)) { + chMaskGrp0123 |= (0xFF << (8 * bank)); + } + } + for(; bank < 10; bank++) { + if(chMask & ((uint16_t)1 << bank)) { + chMaskGrp45 |= (0xFF << (8 * (bank - 8))); + } + } + } + break; + case 6: + // for dynamic bands: all channels ON (currently defined) + // for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4 + // except for CN500: all 125kHz channels ON + + // for dynamic bands: retrieve all defined channels + // for fixed bands: cannot store all defined channels, so select a random one from each bank + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) { + chMaskGrp45 |= (uint32_t)chMask; + } + break; + case 7: + // for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4 + // except for CN500: RFU + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) { + chMaskGrp0123 = 0; + chMaskGrp45 |= (uint32_t)chMask; + } + break; + } + opt++; + } + LoRaWANNode::hton(&mAdrOpt[1], chMaskGrp0123); + LoRaWANNode::hton(&mAdrOpt[9], chMaskGrp45); +} + +void LoRaWANNode::postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen) { + uint8_t fLen = 5; // single ADR command is 5 bytes + uint8_t numOpts = cLen / fLen; + + // duplicate the ACK bits of the atomic block response 'numOpts' times + // skip one, as the first response is already there + for(int opt = 1; opt < numOpts; opt++) { + ack[opt*2 + 0] = RADIOLIB_LORAWAN_MAC_LINK_ADR; + ack[opt*2 + 1] = ack[1]; + } +} + +int16_t LoRaWANNode::getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd) { + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_MAC_COMMANDS; i++) { + if(MacTable[i].cid == cid) { + memcpy(cmd, &MacTable[i], sizeof(LoRaWANMacCommand_t)); + return(RADIOLIB_ERR_NONE); + } + } + // didn't find this CID, check if derived class can help (if any) + int16_t state = this->derivedMacFinder(cid, cmd); return(state); } -int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(strUp, fPort, isConfirmed, eventUp); - RADIOLIB_ASSERT(state); +int16_t LoRaWANNode::derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd) { + (void)cid; + (void)cmd; + return(RADIOLIB_ERR_INVALID_CID); +} - // wait for the downlink - state = this->downlink(dataDown, lenDown, eventDown); +int16_t LoRaWANNode::sendMacCommandReq(uint8_t cid) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = this->getMacCommand(cid, &cmd); + RADIOLIB_ASSERT(state); + if(!cmd.user) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("You are not allowed to request this MAC command"); + return(RADIOLIB_ERR_INVALID_CID); + } + + // if there are already 15 MAC bytes in the uplink queue, we can't add a new one + if(fOptsUpLen >= RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The maximum size of FOpts payload was reached"); + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + // if this MAC command is already in the queue, silently stop + if(this->getMacPayload(cid, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK) == RADIOLIB_ERR_NONE) { + return(RADIOLIB_ERR_NONE); + } + + state = LoRaWANNode::pushMacCommand(cid, NULL, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); return(state); } -int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(dataUp, lenUp, fPort, isConfirmed, eventUp); +int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) { + uint8_t payload[2] = { 0 }; + int16_t state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_LINK_CHECK, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK); RADIOLIB_ASSERT(state); - // wait for the downlink - state = this->downlink(dataDown, lenDown, eventDown); + if(margin) { *margin = payload[0]; } + if(gwCnt) { *gwCnt = payload[1]; } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) { + uint8_t payload[5] = { 0 }; + int16_t state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK); + RADIOLIB_ASSERT(state); + + if(gpsEpoch) { + *gpsEpoch = LoRaWANNode::ntoh(&payload[0]); + if(returnUnix) { + uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980) + *gpsEpoch += unixOffset; + } + } + if(fraction) { *fraction = payload[4]; } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = this->getMacCommand(cid, &cmd); + RADIOLIB_ASSERT(state); + if(dir == RADIOLIB_LORAWAN_UPLINK) { + *len = cmd.lenUp; + } else { + *len = cmd.lenDn; + } + if(inclusive) { + *len += 1; // add one byte for CID + } + return(RADIOLIB_ERR_NONE); +} + +bool LoRaWANNode::isPersistentMacCommand(uint8_t cid, uint8_t dir) { + // if this MAC command doesn't exist, it wouldn't even get into the queue, so don't care about outcome + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + (void)this->getMacCommand(cid, &cmd); + + // in the uplink direction, MAC payload should persist per spec + if(dir == RADIOLIB_LORAWAN_UPLINK) { + return(cmd.persist); + + // in the downlink direction, MAC payload should persist if it is user-accessible + // which is the case for LinkCheck and DeviceTime + } else { + return(cmd.user); + } + return(false); +} + +int16_t LoRaWANNode::pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir) { + uint8_t fLen = 0; + int16_t state = this->getMacLen(cid, &fLen, dir, true); + RADIOLIB_ASSERT(state); + + // check if we can even append the MAC command into the buffer + if(*lenOut + fLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + out[*lenOut] = cid; // add MAC id + memcpy(&out[*lenOut + 1], cOcts, fLen - 1); // copy payload into buffer + *lenOut += fLen; // payload + command ID + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir) { + size_t i = 0; + + while(i < lenIn) { + uint8_t id = in[i]; + uint8_t fLen = 0; + int16_t state = this->getMacLen(id, &fLen, dir, true); + RADIOLIB_ASSERT(state); + if(lenIn < i + fLen) { + return(RADIOLIB_ERR_INVALID_CID); + } + + // if this is the requested MAC id, copy the payload over + if(id == cid) { + // only copy payload if destination is supplied + if(out) { + memcpy(out, &in[i + 1], fLen - 1); + } + return(RADIOLIB_ERR_NONE); + } + + // move on to next MAC command + i += fLen; + } + + return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); +} + +int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) { + size_t i = 0; + while(i < *lenInOut) { + uint8_t id = inOut[i]; + uint8_t fLen = 0; + int16_t state = this->getMacLen(id, &fLen, dir); + RADIOLIB_ASSERT(state); + if(*lenInOut < i + fLen) { + return(RADIOLIB_ERR_INVALID_CID); + } + + // if this is the requested MAC id, + if(id == cid) { + // remove it by moving the rest of the payload forward + memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen); + + // set the remainder of the queue to 0 + memset(&inOut[i + fLen], 0, *lenInOut - i - fLen); + + *lenInOut -= fLen; + return(RADIOLIB_ERR_NONE); + } + + // move on to next MAC command + i += fLen; + } + + return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); +} + +void LoRaWANNode::clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) { + size_t i = 0; + uint8_t numDeleted = 0; + while(i < *lenInOut) { + uint8_t id = inOut[i]; + uint8_t fLen = 1; // if there is an incorrect MAC command, we should at least move forward by one byte + (void)this->getMacLen(id, &fLen, dir, true); + + // only clear MAC command if it should not persist until a downlink is received + if(!this->isPersistentMacCommand(id, dir)) { + // remove it by moving the rest of the payload forward + memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen); + + // set the remainder of the queue to 0 + memset(&inOut[i + fLen], 0, *lenInOut - i - fLen); + + numDeleted += fLen; + } + + // move on to next MAC command + i += fLen; + } + *lenInOut -= numDeleted; +} + +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++) { + LoRaWANChannel_t *chnl = &(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i]); + if(chnl->enabled) { + if(drUp >= chnl->drMin && drUp <= chnl->drMax) { + isValidDR = true; + break; + } + } + } + if(!isValidDR) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No defined channel allows datarate %d", drUp); + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + uint8_t cOcts[1]; + uint8_t cAck[1]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = (drUp << 4); // set requested datarate + cOcts[0] |= 0x0F; // keep Tx Power the same + (void)execMacCommand(cid, cOcts, cLen, cAck); + + // check if ACK is set for Datarate + if(!(cAck[0] & 0x02)) { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + return(RADIOLIB_ERR_NONE); +} + +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); + } + // Tx Power is set in steps of two + // the selected value is rounded down to nearest multiple of two away from txPowerMax + // e.g. on EU868, max is 16; if 13 is selected then we set to 12 + uint8_t numSteps = (this->txPowerMax - txPower + 1) / (-RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM); + + uint8_t cOcts[1]; + uint8_t cAck[1]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = 0xF0; // keep datarate the same + cOcts[0] |= numSteps; // set requested Tx Power + (void)execMacCommand(cid, cOcts, cLen, cAck); + + // check if ACK is set for Tx Power + if(!(cAck[0] & 0x04)) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::setRx2Dr(uint8_t dr) { + // this can only be configured in ABP mode + if(this->lwMode != RADIOLIB_LORAWAN_MODE_ABP) { + return(RADIOLIB_ERR_INVALID_MODE); + } + + // can only configure different datarate for dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); + } + + // check if datarate is available in the selected band + if(this->band->dataRates[dr] == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + // find and check if the datarate is available for this radio module + DataRate_t dataRate; + int16_t state = findDataRate(dr, &dataRate); + RADIOLIB_ASSERT(state); + + // passed all checks, so configure the datarate + this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = dr; + return(state); } +void LoRaWANNode::setADR(bool enable) { + this->adrEnabled = enable; +} + +void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) { + this->dutyCycleEnabled = enable; + if(!enable) { + this->dutyCycle = 0; + } + if(msPerHour == 0) { + this->dutyCycle = this->band->dutyCycle; + } else { + this->dutyCycle = msPerHour; + } +} + +void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) { + this->dwellTimeEnabledUp = enable; + if(msPerUplink == 0) { + this->dwellTimeUp = this->band->dwellTimeUp; + } else { + this->dwellTimeUp = msPerUplink; + } +} + +// A user may enable CSMA to provide frames an additional layer of protection from interference. +// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma +void LoRaWANNode::setCSMA(bool csmaEnabled, uint8_t maxChanges, uint8_t backoffMax, uint8_t difsSlots) { + this->csmaEnabled = csmaEnabled; + if(csmaEnabled) { + this->maxChanges = maxChanges; + this->difsSlots = difsSlots; + this->backoffMax = backoffMax; + } else { + // disable all values + this->maxChanges = 0; + this->difsSlots = 0; + this->backoffMax = 0; + } +} + void LoRaWANNode::setDeviceStatus(uint8_t battLevel) { this->battLevel = battLevel; } +void LoRaWANNode::scheduleTransmission(RadioLibTime_t tUplink) { + this->tUplink = tUplink; +} + // return fCnt of last uplink; also return 0 if no uplink occured yet uint32_t LoRaWANNode::getFCntUp() { if(this->fCntUp == 0) { @@ -1694,76 +2683,73 @@ void LoRaWANNode::resetFCntDown() { this->aFCntDown = 0; } -uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { - if((msg == NULL) || (len == 0)) { - return(0); - } - - RadioLibAES128Instance.init(key); - uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE]; - RadioLibAES128Instance.generateCMAC(msg, len, cmac); - return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24); +uint32_t LoRaWANNode::getDevAddr() { + return(this->devAddr); } -bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { - if((msg == NULL) || (len < sizeof(uint32_t))) { - return(0); - } - - // extract MIC from the message - uint32_t micReceived = LoRaWANNode::ntoh(&msg[len - sizeof(uint32_t)]); - - // calculate the expected value and compare - uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key); - if(micCalculated != micReceived) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MIC mismatch, expected %08x, got %08x", micCalculated, micReceived); - return(false); - } - - return(true); +RadioLibTime_t LoRaWANNode::getLastToA() { + return(this->lastToA); } -int16_t LoRaWANNode::setPhyProperties(uint8_t dir) { +int16_t LoRaWANNode::setPhyProperties(const LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre) { // set the physical layer configuration - RADIOLIB_DEBUG_PROTOCOL_PRINTLN(""); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %6.3f MHz", dir ? 'D' : 'U', this->currentChannels[dir].freq); - int16_t state = this->phyLayer->setFrequency(this->currentChannels[dir].freq); - RADIOLIB_ASSERT(state); - - // if this channel is an FSK channel, toggle the FSK switch - if(this->band->dataRates[this->dataRates[dir]] == RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { - this->modulation = RADIOLIB_LORAWAN_MODULATION_GFSK; + int16_t state = this->phyLayer->standby(); + if(state != RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?"); + return(state); } - int8_t pwr = this->txPowerMax - this->txPowerSteps * 2; + // TODO implement PhysicalLayer::setModem() + // set modem-dependent functions + switch(this->band->dataRates[chnl->dr] & RADIOLIB_LORAWAN_DATA_RATE_MODEM) { + case(RADIOLIB_LORAWAN_DATA_RATE_LORA): + this->modulation = RADIOLIB_LORAWAN_MODULATION_LORA; + // downlink messages are sent with inverted IQ + if(dir == RADIOLIB_LORAWAN_DOWNLINK) { + state = this->phyLayer->invertIQ(true); + } else { + state = this->phyLayer->invertIQ(false); + } + RADIOLIB_ASSERT(state); + break; + case(RADIOLIB_LORAWAN_DATA_RATE_FSK): + this->modulation = RADIOLIB_LORAWAN_MODULATION_GFSK; + state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); + RADIOLIB_ASSERT(state); + break; + case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS): + this->modulation = RADIOLIB_LORAWAN_MODULATION_LR_FHSS; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN(""); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %7.3f MHz", dir ? 'D' : 'U', chnl->freq / 10000.0); + state = this->phyLayer->setFrequency(chnl->freq / 10000.0); + RADIOLIB_ASSERT(state); // at this point, assume that Tx power value is already checked, so ignore the return value + // this call is only used to clip a value that is higher than the module supports (void)this->phyLayer->checkOutputPower(pwr, &pwr); state = this->phyLayer->setOutputPower(pwr); RADIOLIB_ASSERT(state); DataRate_t dr; - state = findDataRate(this->dataRates[dir], &dr); + state = findDataRate(chnl->dr, &dr); RADIOLIB_ASSERT(state); state = this->phyLayer->setDataRate(dr); RADIOLIB_ASSERT(state); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: SF = %d, TX = %d dBm, BW = %6.3f kHz, CR = 4/%d", - dr.lora.spreadingFactor, pwr, dr.lora.bandwidth, dr.lora.codingRate); - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_GFSK) { - state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); - RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FSK: BR = %4.1f, TX = %d dBm, FD = %4.1f kHz", + dr.fsk.bitRate, pwr, dr.fsk.freqDev); } - - // downlink messages are sent with inverted IQ - if(dir == RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK) { - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } + if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRa: SF = %d, TX = %d dBm, BW = %5.1f kHz, CR = 4/%d", + dr.lora.spreadingFactor, pwr, dr.lora.bandwidth, dr.lora.codingRate); } // this only needs to be done once-ish @@ -1800,238 +2786,360 @@ int16_t LoRaWANNode::setPhyProperties(uint8_t dir) { state = this->phyLayer->setSyncWord(syncWord, syncWordLen); RADIOLIB_ASSERT(state); + // if a preamble length is supplied, overrule the 'calculated' preamble length + if(pre) { + preLen = pre; + } if(this->modulation != RADIOLIB_LORAWAN_MODULATION_LR_FHSS) { state = this->phyLayer->setPreambleLength(preLen); } return(state); } -int16_t LoRaWANNode::setupChannelsDyn(bool joinRequest) { +// The following function implements LMAC, a CSMA scheme for LoRa as specified +// in the LoRa Alliance Technical Recommendation #13. +bool LoRaWANNode::csmaChannelClear(uint8_t difs, uint8_t numBackoff) { + // DIFS phase: perform #DIFS CAD operations + uint8_t numCads = 0; + for (; numCads < difs; numCads++) { + if (!this->cadChannelClear()) { + return(false); + } + } + + // BO phase: perform #numBackoff additional CAD operations + for (; numCads < difs + numBackoff; numCads++) { + if (!this->cadChannelClear()) { + return(false); + } + } + + // none of the CADs showed activity, so all clear + return(true); +} + +bool LoRaWANNode::cadChannelClear() { + int16_t state = this->phyLayer->scanChannel(); + // if activity was detected, channel is not clear + if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { + return(false); + } + return(true); +} + +void LoRaWANNode::getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45) { + // clear masks in case anything was set + *chMaskGrp0123 = 0; + *chMaskGrp45 = 0; + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx; + if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + if(idx < 64) { + *chMaskGrp0123 |= ((uint64_t)1 << idx); + } else { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + } + } else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED + // if a subband is set, we can set the channel indices straight from subband + if(this->subBand > 0 && this->subBand <= 8) { + // for sub band 1-8, set bank of 8 125kHz + single 500kHz channel + *chMaskGrp0123 |= 0xFF << ((this->subBand - 1) * 8); + *chMaskGrp45 |= 0x01 << ((this->subBand - 1) * 8); + } else if(this->subBand > 8 && this->subBand <= 12) { + // CN500 only: for sub band 9-12, set bank of 8 125kHz channels + *chMaskGrp45 |= 0xFF << ((this->subBand - 9) * 8); + } else { + // if subband is set to 0, all 125kHz channels are enabled + // however, we can 'only' store 16 channels, so we do not actually store these + // therefore, we select a random channel from each bank of 8 channels + uint8_t num125kHz = this->band->txSpans[0].numChannels; + uint8_t numBanks = num125kHz / 8; + for(uint8_t bank = 0; bank < numBanks; bank++) { + uint8_t bankIdx = this->phyLayer->random(8); + uint8_t idx = bank * 8 + bankIdx; + if(idx < 64) { + *chMaskGrp0123 |= ((uint64_t)1 << idx); + } else { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + // the 500 kHz channels are in the usual channel plan however + // these are the channel indices 64-71 for bands other than CN500 + if(this->band->bandNum != BandCN500) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx; + if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE && idx >= 64) { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + } + } + } +} + +void LoRaWANNode::selectChannelPlanDyn(bool joinRequest) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up dynamic channels"); size_t num = 0; // copy the default defined channels into the first slots (where Tx = Rx) for(; num < 3 && this->band->txFreqs[num].enabled; num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = this->band->txFreqs[num]; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num]; } - // if we're about to send a join-request, copy the join-request channels to the next slots + // if we're about to send a JoinRequest, copy the JoinRequest channels to the next slots if(joinRequest) { size_t numJR = 0; for(; numJR < 3 && this->band->txJoinReq[num].enabled; numJR++, num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = this->band->txFreqs[num]; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num]; } } // clear all remaining channels for(; num < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; } + // make sure the Rx2 settings are back to this band's default + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; + + // make all enabled channels available for uplink selection + this->setAvailableChannels(0xFFFF); + #if RADIOLIB_DEBUG_PROTOCOL this->printChannels(); #endif - - return(RADIOLIB_ERR_NONE); } -// setup a subband and its corresponding join-request datarate +// setup a subband and its corresponding JoinRequest datarate // 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); +void LoRaWANNode::selectChannelPlanFix() { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up fixed channels (subband %d)", this->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; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; } - // if no subband is selected by user, cycle through banks of 8 using devNonce value - if(subBand == 0) { - uint8_t numBanks8 = this->band->txSpans[0].numChannels / 8; - subBand = this->devNonce % numBanks8; - } + // get channel masks for this subband + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); - uint8_t chMaskCntl = 0; - uint16_t chMask = 0; + // apply channel mask + this->applyChannelMask(chMaskGrp0123, chMaskGrp45); + + // make sure the Rx2 settings are back to this band's default + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; - // if there are two channel spans, first set the channel from second span - if(this->band->numTxSpans == 2) { - chMaskCntl = 7; - chMask = (1 << (subBand - 1)); // set channel mask - this->applyChannelMaskFix(chMaskCntl, chMask); - } + // make all enabled channels available for uplink selection + this->setAvailableChannels(0xFFFF); - // 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 - if(subBand % 2 == 0) { // even subbands - chMask = 0xFF00; - } else { - chMask = 0x00FF; // odd subbands - } - this->applyChannelMaskFix(chMaskCntl, chMask); - - return(RADIOLIB_ERR_NONE); + #if RADIOLIB_DEBUG_PROTOCOL + this->printChannels(); + #endif } -int16_t LoRaWANNode::processCFList(uint8_t* cfList) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processing CFList"); - - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // retrieve number of existing (default) channels - size_t num = 0; - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(!this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - break; +uint8_t LoRaWANNode::getAvailableChannels(uint16_t* chMask) { + uint8_t num = 0; + uint16_t mask = 0; + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // if channel is available and usable for current datarate, set corresponding bit + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available) { + if(currentDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin && + currentDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) { + num++; + mask |= (0x0001 << i); } - num++; - } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - - uint8_t freqZero[3] = { 0 }; - - // datarate range for all new channels is equal to the default channels - cmd.payload[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin; - for(uint8_t i = 0; i < 5; i++, num++) { - // if the frequency fields are all zero, there are no more channels in the CFList - if(memcmp(&cfList[i*3], freqZero, 3) == 0) { - break; - } - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - cmd.payload[0] = num; - memcpy(&cmd.payload[1], &cfList[i*3], 3); - (void)execMacCommand(&cmd); - } - } else { // RADIOLIB_LORAWAN_BAND_FIXED - // complete channel mask received, so clear all existing channels - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; - } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - - // in case of mask-type bands, copy those frequencies that are masked true into the available TX channels - size_t numChMasks = 3 + this->band->numTxSpans; // 4 masks for bands with 2 spans, 5 spans for bands with 1 span - for(size_t chMaskCntl = 0; chMaskCntl < numChMasks; chMaskCntl++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - cmd.payload[0] = 0xFF; // same datarate and payload - memcpy(&cmd.payload[1], &cfList[chMaskCntl*2], 2); // copy mask - cmd.payload[3] = chMaskCntl << 4; // set chMaskCntl, set NbTrans = 0 -> keep the same - cmd.repeat = (chMaskCntl + 1); - (void)execMacCommand(&cmd); } } + if(chMask) { + *chMask = mask; + } + return(num); +} - return(RADIOLIB_ERR_NONE); +void LoRaWANNode::setAvailableChannels(uint16_t mask) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // if channel is enabled, set to available + if(mask & (0x0001 << i) && this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = true; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = false; + } + } } int16_t LoRaWANNode::selectChannels() { - // figure out which channel IDs are enabled (chMask may have disabled some) and are valid for the current datarate - uint8_t numChannels = 0; - uint8_t channelsEnabled[RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; - for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] >= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin - && this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] <= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax) { - channelsEnabled[numChannels] = i; - numChannels++; - } + uint16_t chMask = 0x0000; + uint8_t numChannels = this->getAvailableChannels(&chMask); + + // if there are no available channels, try resetting them all to available + if(numChannels == 0) { + this->setAvailableChannels(0xFFFF); + numChannels = this->getAvailableChannels(&chMask); + + // if there are still no channels available, give up + if(numChannels == 0) { + return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); } } - if(numChannels == 0) { - return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); + + // select a random value within the number of possible channels + int chRand = this->phyLayer->random(numChannels); + + // retrieve the index of this channel by looping through the channel mask + int chIdx = -1; + while(chRand >= 0) { + chIdx++; + if(chMask & 0x0001) { + chRand--; + } + chMask >>= 1; } - // select a random ID & channel from the list of enabled and possible channels - uint8_t channelID = channelsEnabled[this->phyLayer->random(numChannels)]; - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][channelID]; + + // as we are now going to use this channel, mark unavailable for next uplink + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx].available = false; + + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + this->channels[RADIOLIB_LORAWAN_UPLINK] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx]; + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr; if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { // for dynamic bands, the downlink channel is the one matched to the uplink channel - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][channelID]; + this->channels[RADIOLIB_LORAWAN_DOWNLINK] = this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][chIdx]; } else { // RADIOLIB_LORAWAN_BAND_FIXED // for fixed bands, the downlink channel is the uplink channel ID `modulo` number of downlink channels - LoRaWANChannel_t channelDn; + LoRaWANChannel_t channelDn = RADIOLIB_LORAWAN_CHANNEL_NONE; channelDn.enabled = true; - channelDn.idx = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx % this->band->rx1Span.numChannels; + channelDn.idx = this->channels[RADIOLIB_LORAWAN_UPLINK].idx % this->band->rx1Span.numChannels; channelDn.freq = this->band->rx1Span.freqStart + channelDn.idx*this->band->rx1Span.freqStep; channelDn.drMin = this->band->rx1Span.drMin; channelDn.drMax = this->band->rx1Span.drMax; - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDn; + this->channels[RADIOLIB_LORAWAN_DOWNLINK] = channelDn; } - uint8_t drDown = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], this->rx1DrOffset, this->band->rx1DataRateBase, - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin, this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax); - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown; + uint8_t rx1Dr = this->band->rx1DrTable[currentDr][this->rx1DrOffset]; + + // if downlink dwelltime is enabled, datarate < 2 cannot be used, so clip to 2 + // only in use on AS923_x bands + if(this->dwellTimeEnabledDn && rx1Dr < 2) { + rx1Dr = 2; + } + this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr = rx1Dr; return(RADIOLIB_ERR_NONE); } -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++) { - LoRaWANChannel_t *chnl = &(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]); - if(chnl->enabled) { - if(drUp >= chnl->drMin && drUp <= chnl->drMax) { - isValidDR = true; - break; +bool LoRaWANNode::applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45) { + int num = 0; + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(chMaskGrp0123 & ((uint64_t)1 << i)) { + // if it should be enabled but is not currently defined, stop immediately + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + return(false); + } + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = true; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = false; + } + } + } else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED + LoRaWANChannel_t chnl = RADIOLIB_LORAWAN_CHANNEL_NONE; + uint8_t spanNum = 0; + int chNum = 0; + int chOfs = 0; + for(; chNum < 64; chNum++) { + if(chMaskGrp0123 & ((uint64_t)1 << chNum)) { + chnl.enabled = true; + chnl.idx = chNum; + chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep; + chnl.drMin = this->band->txSpans[spanNum].drMin; + chnl.drMax = this->band->txSpans[spanNum].drMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl; + } + } + if(this->band->numTxSpans > 1) { + spanNum += 1; + chNum = 0; + chOfs = 64; + } + for(; chNum < this->band->txSpans[spanNum].numChannels; chNum++) { + if(chMaskGrp45 & ((uint32_t)1 << chNum)) { + chnl.enabled = true; + chnl.idx = chNum + chOfs; + chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep; + chnl.drMin = this->band->txSpans[spanNum].drMin; + chnl.drMax = this->band->txSpans[spanNum].drMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl; } } } - if(!isValidDR) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No defined channel allows datarate %d", drUp); - return(RADIOLIB_ERR_INVALID_DATA_RATE); - } - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; - cmd.payload[0] = (drUp << 4); - cmd.payload[0] |= 0x0F; // keep Tx Power the same - cmd.payload[3] = (1 << 7); // set the RFU bit, which means that the channel mask gets ignored - cmd.payload[3] |= 0; // keep NbTrans the same - (void)execMacCommand(&cmd); +#if RADIOLIB_DEBUG_PROTOCOL + this->printChannels(); +#endif - // check if ACK is set for Tx Power - if((cmd.payload[0] >> 1) != 1) { - return(RADIOLIB_ERR_INVALID_DATA_RATE); - } - - return(RADIOLIB_ERR_NONE); + return(true); } -void LoRaWANNode::setADR(bool enable) { - this->adrEnabled = enable; +#if RADIOLIB_DEBUG_PROTOCOL +void LoRaWANNode::printChannels() { + for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax, + + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].idx, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].enabled, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMin, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMax + ); + } + } +} +#endif + +uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { + if((msg == NULL) || (len == 0)) { + return(0); + } + + RadioLibAES128Instance.init(key); + uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE]; + RadioLibAES128Instance.generateCMAC(msg, len, cmac); + return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24); } -void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) { - this->dutyCycleEnabled = enable; - if(!enable) { - this->dutyCycle = 0; +bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { + if((msg == NULL) || (len < sizeof(uint32_t))) { + return(0); } - if(msPerHour == 0) { - this->dutyCycle = this->band->dutyCycle; - } else { - this->dutyCycle = msPerHour; + + // extract MIC from the message + uint32_t micReceived = LoRaWANNode::ntoh(&msg[len - sizeof(uint32_t)]); + + // calculate the expected value and compare + uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key); + if(micCalculated != micReceived) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MIC mismatch, expected %08x, got %08x", micCalculated, micReceived); + return(false); } + + return(true); } // given an airtime in milliseconds, calculate the minimum uplink interval @@ -2055,93 +3163,80 @@ RadioLibTime_t LoRaWANNode::timeUntilUplink() { return(nextUplink - mod->hal->millis() + 1); } -void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) { - this->dwellTimeEnabledUp = enable; - if(msPerUplink == 0) { - this->dwellTimeUp = this->band->dwellTimeUp; - } else { - this->dwellTimeUp = msPerUplink; - } -} +uint8_t LoRaWANNode::getMaxPayloadLen() { + // configure the uplink channel properties + this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK], + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); -uint8_t LoRaWANNode::maxPayloadDwellTime() { - // configure current datarate - DataRate_t dr; - // TODO this may fail horribly? - (void)findDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], &dr); - (void)this->phyLayer->setDataRate(dr); uint8_t minPayLen = 0; - uint8_t maxPayLen = 255; - uint8_t payLen = (minPayLen + maxPayLen) / 2; + uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + if(this->TS011) { + maxPayLen = RADIOLIB_MIN(maxPayLen, 230); // payload length is limited to 230 if under repeater + } + + // if not limited by dwell-time, just return maximum + if(!this->dwellTimeEnabledUp) { + // subtract FHDR (13 bytes) as well as any FOpts + return(maxPayLen - 13 - this->fOptsUpLen); + } + + // fast exit in case upper limit is already good + if(this->phyLayer->getTimeOnAir(maxPayLen) / 1000 <= this->dwellTimeUp) { + // subtract FHDR (13 bytes) as well as any FOpts + return(maxPayLen - 13 - this->fOptsUpLen); + } + // do some binary search to find maximum allowed payload length + uint8_t payLen = (minPayLen + maxPayLen) / 2; while(payLen != minPayLen && payLen != maxPayLen) { - if(this->phyLayer->getTimeOnAir(payLen) > this->dwellTimeUp) { + if(this->phyLayer->getTimeOnAir(payLen) / 1000 > this->dwellTimeUp) { maxPayLen = payLen; } else { minPayLen = payLen; } payLen = (minPayLen + maxPayLen) / 2; } - return(payLen - 13); // fixed 13-byte header -} - -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); - } - // Tx Power is set in steps of two - // the selected value is rounded down to nearest multiple of two away from txPowerMax - // e.g. on EU868, max is 16; if 13 is selected then we set to 12 - uint8_t numSteps = (this->txPowerMax - txPower + 1) / (-RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM); - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; - cmd.payload[0] = 0xF0; // keep datarate the same - 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); - - // check if ACK is set for Tx Power - if((cmd.payload[0] >> 2) != 1) { - return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } - - return(RADIOLIB_ERR_NONE); + // subtract FHDR (13 bytes) as well as any FOpts + return(payLen - 13 - this->fOptsUpLen); } int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { - int16_t state = RADIOLIB_ERR_UNKNOWN; + int16_t state = this->phyLayer->standby(); + if(state != RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?"); + return(state); + } uint8_t dataRateBand = this->band->dataRates[dr]; - if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { - dataRate->fsk.bitRate = 50; - dataRate->fsk.freqDev = 25; - - } else { - uint8_t bw = dataRateBand & 0x0C; - switch(bw) { - case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ): - dataRate->lora.bandwidth = 125.0; - break; - case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ): - dataRate->lora.bandwidth = 250.0; - break; - case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ): - dataRate->lora.bandwidth = 500.0; - break; - default: - dataRate->lora.bandwidth = 125.0; - } - - dataRate->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6; - dataRate->lora.codingRate = (dataRateBand & 0x03) + 5; + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_MODEM) { + case(RADIOLIB_LORAWAN_DATA_RATE_LORA): + dataRate->lora.spreadingFactor = ((dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_SF) >> 3) + 7; + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_BW) { + case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ): + dataRate->lora.bandwidth = 125.0; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ): + dataRate->lora.bandwidth = 250.0; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ): + dataRate->lora.bandwidth = 500.0; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + dataRate->lora.codingRate = 5; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_FSK): + dataRate->fsk.bitRate = 50; + dataRate->fsk.freqDev = 25; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS): + // not yet supported by DataRate_t + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); } state = this->phyLayer->checkDataRate(*dataRate); @@ -2149,852 +3244,6 @@ int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { return(state); } -int16_t LoRaWANNode::setRx2Dr(uint8_t dr) { - // this can only be configured in ABP mode - if(this->lwMode != RADIOLIB_LORAWAN_MODE_ABP) { - return(RADIOLIB_LORAWAN_INVALID_MODE); - } - - // can only configure different datarate for dynamic bands - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { - return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); - } - - // check if datarate is available in the selected band - if(this->band->dataRates[dr] == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - return(RADIOLIB_ERR_INVALID_DATA_RATE); - } - - // find and check if the datarate is available for this radio module - DataRate_t dataRate; - int16_t state = findDataRate(dr, &dataRate); - RADIOLIB_ASSERT(state); - - // passed all checks, so configure the datarate - this->rx2.drMax = dr; - - return(state); -} - -int16_t LoRaWANNode::sendMacCommandReq(uint8_t cid) { - bool valid = false; - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_MAC_COMMANDS; i++) { - if(MacTable[i].cid == cid) { - valid = MacTable[i].user; - } - } - if(!valid) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("You are not allowed to request this MAC command"); - return(RADIOLIB_ERR_INVALID_CID); - } - - // if there are already 15 MAC bytes in the uplink queue, we can't add a new one - if(this->commandsUp.len + 1 > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The maximum number of FOpts payload was reached"); - return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); - } - if(this->commandsUp.numCommands > RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The RadioLib internal MAC command queue was full"); - return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); - } - - // delete any prior requests for this MAC command, in case this is requested more than once - (void)deleteMacCommand(cid, &this->commandsUp); - - LoRaWANMacCommand_t cmd = { - .cid = cid, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - pushMacCommand(&cmd, &this->commandsUp); - return(true); -} - -void LoRaWANNode::macQueueToBuff(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer) { - // append all MAC replies into fOpts buffer - uint8_t* fOptsPtr = buffer; - int16_t i = 0; - for (; i < queue->numCommands; i++) { - LoRaWANMacCommand_t cmd = queue->commands[i]; - memcpy(fOptsPtr, &cmd, 1 + cmd.len); - fOptsPtr += cmd.len + 1; - } - - // pop the commands from back to front - for (; i >= 0; i--) { - if(queue->commands[i].repeat > 0) { - queue->commands[i].repeat--; - } else { - deleteMacCommand(queue->commands[i].cid, queue); - } - } - -} - -void LoRaWANNode::macBufftoQueue(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer, uint8_t len) { - bool hasADR = false; - uint8_t numADR = 0; - uint8_t lastCID = 0; - - // process the MAC command(s) - int8_t remLen = len; - uint8_t* fOptsPtr = buffer; - while(remLen > 0) { - uint8_t cid = *fOptsPtr; - uint8_t macLen = getMacPayloadLength(cid); - if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) { - // if there was an earlier ADR command but it was not the last, ignore it - if(hasADR && lastCID != RADIOLIB_LORAWAN_MAC_LINK_ADR) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Encountered non-consecutive block of ADR commands - skipping"); - remLen -= (macLen + 1); - fOptsPtr += (macLen + 1); - lastCID = cid; - continue; - } - // otherwise, set ADR flag to true and increase counter - hasADR = true; - numADR++; - } - if(macLen + 1 > remLen) - break; - LoRaWANMacCommand_t cmd = { - .cid = cid, - .payload = { 0 }, - .len = macLen, - .repeat = (cid == RADIOLIB_LORAWAN_MAC_LINK_ADR ? numADR : (uint8_t)0), - }; - memcpy(cmd.payload, fOptsPtr + 1, macLen); - pushMacCommand(&cmd, queue); - - // move in the buffer to the next command - remLen -= (macLen + 1); - fOptsPtr += (macLen + 1); - lastCID = cid; - } - -} - -int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue) { - if(queue->numCommands >= RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) { - return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); - } - - memcpy(&queue->commands[queue->numCommands], cmd, sizeof(LoRaWANMacCommand_t)); - queue->numCommands++; - queue->len += 1 + cmd->len; // 1 byte for command ID, len bytes for payload - - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue, uint8_t* payload) { - for(size_t index = 0; index < queue->numCommands; index++) { - if(queue->commands[index].cid == cid) { - // if a pointer to a payload is supplied, copy the command's payload over - if(payload) { - memcpy(payload, queue->commands[index].payload, queue->commands[index].len); - } - queue->len -= (1 + queue->commands[index].len); // 1 byte for command ID, len for payload - // move all subsequent commands one forward in the queue - if(index < RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1) { - memmove(&queue->commands[index], &queue->commands[index + 1], (RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - index - 1) * sizeof(LoRaWANMacCommand_t)); - } - // set the latest element to all 0 - memset(&queue->commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1], 0x00, sizeof(LoRaWANMacCommand_t)); - queue->numCommands--; - return(RADIOLIB_ERR_NONE); - } - } - - return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); -} - -bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[MAC] 0x%02x", cmd->cid); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(cmd->payload, cmd->len); - - if(cmd->cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) { - // TODO call user-provided callback for proprietary MAC commands? - return(false); - } - - switch(cmd->cid) { - case(RADIOLIB_LORAWAN_MAC_RESET): { - // get the server version - uint8_t srvVersion = cmd->payload[0]; - 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); - } - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]"); - cmd->repeat = 255; // prevent the command from being deleted by setting repeat to maximum - - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_LINK_ADR): { - // get the ADR configuration - uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4; - uint8_t txSteps = 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 nbTransMac = cmd->payload[3] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRReq: dataRate = %d, txSteps = %d, chMask = 0x%04x, chMaskCntl = %d, nbTrans = %d", drUp, txSteps, chMask, chMaskCntl, nbTransMac); - - // try to apply the datarate configuration - int16_t state; - uint8_t drAck = 0; - if(drUp == 0x0F) { // keep the same - drAck = 1; - - } else if (this->band->dataRates[drUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - // check if the module supports this data rate - DataRate_t dr; - state = findDataRate(drUp, &dr); - if(state == RADIOLIB_ERR_NONE) { - uint8_t drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin, - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax); - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown; - drAck = 1; - } else { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure dataRate %d, code %d!", drUp, state); - drUp = 0x0F; // set value to 'keep the same' - } - - } - - // try to apply the power configuration - uint8_t pwrAck = 0; - if(txSteps == 0x0F) { - pwrAck = 1; - - } else { - int8_t power = this->txPowerMax - 2*txSteps; - int8_t powerActual = 0; - state = this->phyLayer->checkOutputPower(power, &powerActual); - // only acknowledge if the radio is able to operate at or below the requested power level - if(state == RADIOLIB_ERR_NONE || (state == RADIOLIB_ERR_INVALID_OUTPUT_POWER && powerActual < power)) { - pwrAck = 1; - this->txPowerSteps = txSteps; - } else { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure Tx power %d, code %d!", power, state); - txSteps = 0x0F; // set value to 'keep the same' - } - } - - uint8_t chMaskAck = 1; - // only apply channel mask when the RFU bit is not set - // (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); - - } else { // RADIOLIB_LORAWAN_BAND_FIXED - 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 - 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); - - } - } - - if(nbTransMac) { // if there is a value for NbTrans, set this value - this->nbTrans = nbTransMac; - } - - // replace 'placeholder' or failed values with the current values for saving - // per spec, all these configuration should only be set if all ACKs are set, otherwise retain previous state - // but we don't bother and try to set each individual command - if(drUp == 0x0F || !drAck) { - cmd->payload[0] = (cmd->payload[0] & 0x0F) | (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4); - } - if(txSteps == 0x0F || !pwrAck) { - cmd->payload[0] = (cmd->payload[0] & 0xF0) | this->txPowerSteps; - } - if(nbTransMac == 0) { - cmd->payload[3] = (cmd->payload[3] & 0xF0) | this->nbTrans; - } - - 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(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 - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], &(cmd->payload[0]), cmd->len); - - } else { // RADIOLIB_LORAWAN_BAND_FIXED - - // save Tx/Dr to the Link ADR position in the session buffer - uint8_t bufTxDr[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - bufTxDr[0] = cmd->payload[0]; - bufTxDr[3] = 1 << 7; - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], bufTxDr, cmd->len); - - // 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); - } - - } - - // 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("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("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle); - if(maxDutyCycle == 0) { - this->dutyCycle = this->band->dutyCycle; - } else { - this->dutyCycle = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000 / (RadioLibTime_t)(1UL << maxDutyCycle); - } - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], cmd->payload, cmd->len); - - cmd->len = 0; - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP): { - // get the configuration - this->rx1DrOffset = (cmd->payload[0] & 0x70) >> 4; - uint8_t rx1OffsAck = 1; - this->rx2.drMax = cmd->payload[0] & 0x0F; - uint8_t rx2Ack = 1; - uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); - this->rx2.freq = (float)freqRaw/10000.0; - 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; - if(this->phyLayer->setFrequency(this->rx2.freq) == RADIOLIB_ERR_NONE) { - chanAck = 1; - this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); - } - - 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("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[0] = this->battLevel; - int8_t snr = this->phyLayer->getSNR(); - cmd->payload[1] = snr & 0x3F; - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", cmd->payload[0], cmd->payload[1]); - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_NEW_CHANNEL): { - // get the configuration - uint8_t chIndex = cmd->payload[0]; - uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); - float freq = (float)freqRaw/10000.0; - uint8_t maxDr = (cmd->payload[4] & 0xF0) >> 4; - uint8_t minDr = cmd->payload[4] & 0x0F; - - uint8_t newChAck = 0; - uint8_t freqAck = 0; - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].enabled = true; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].idx = chIndex; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].freq = freq; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMin = minDr; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMax = maxDr; - - // downlink channel is identical to uplink channel - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex]; - newChAck = 1; - - // check if the frequency is possible - if(this->phyLayer->setFrequency(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].freq) == RADIOLIB_ERR_NONE) { - freqAck = 1; - this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); - } - - 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, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMax, - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].drMax - ); - - 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; - - case(RADIOLIB_LORAWAN_MAC_DL_CHANNEL): { - // get the configuration - 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("DlChannelReq: index = %d, freq = %f MHz", chIndex, freq); - uint8_t freqDlAck = 0; - uint8_t freqUlAck = 0; - - // check if the frequency is possible - if(this->phyLayer->setFrequency(freq) == RADIOLIB_ERR_NONE) { - freqDlAck = 1; - this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); - } - - // update the downlink frequency - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx == chIndex) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq = freq; - // check if the corresponding uplink frequency is actually set - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq > 0) { - freqUlAck = 1; - } - } - } - - 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; - - case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): { - // get the configuration - uint8_t delay = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay); - - // apply the configuration - if(delay == 0) { - delay = 1; - } - this->rxDelays[0] = delay * 1000; - this->rxDelays[1] = this->rxDelays[0] + 1000; - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], cmd->payload, cmd->len); - - // send the reply - cmd->len = 0; - - // TODO send this repeatedly until a downlink is received - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP): { - uint8_t dlDwell = (cmd->payload[0] & 0x20) >> 5; - uint8_t ulDwell = (cmd->payload[0] & 0x10) >> 4; - uint8_t maxEirpRaw = cmd->payload[0] & 0x0F; - - // 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("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; - - this->dwellTimeEnabledDn = dlDwell ? true : false; - this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cmd->payload, cmd->len); - - cmd->len = 0; - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_REKEY): { - // get the server version - uint8_t srvVersion = cmd->payload[0]; - 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); - } - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): { - this->adrLimitExp = (cmd->payload[0] & 0xF0) >> 4; - this->adrDelayExp = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); - - 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]"); - cmd->repeat = 255; // prevent the command from being deleted by setting repeat to maximum - - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): { - // TODO implement this - 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("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); - (void)period; - (void)maxRetries; - (void)rejoinType; - (void)dr; - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP): { - // TODO implement this - uint8_t maxTime = (cmd->payload[0] & 0xF0) >> 4; - uint8_t maxCount = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount); - - 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; - return(true); - } break; - } - - return(false); -} - -void LoRaWANNode::printChannels() { - for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - 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 - ); - } - } -} - -bool LoRaWANNode::applyChannelMaskDyn(uint8_t chMaskCntl, uint16_t chMask) { - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(chMaskCntl == 0) { - // apply the mask by looking at each channel bit - if(chMask & (1UL << i)) { - // if it should be enabled but is not currently defined, stop immediately - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { - return(false); - } - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; - } else { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = false; - } - - } else if(chMaskCntl == 6) { - // enable all defined channels - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; - } - } - - } - - #if RADIOLIB_DEBUG_PROTOCOL - this->printChannels(); - #endif - - return(true); -} - -bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask); - - // 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++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq > 0) { - idx++; - } - } - - LoRaWANChannel_t chnl; - if((this->band->numTxSpans == 1 && chMaskCntl <= 5) || (this->band->numTxSpans == 2 && chMaskCntl <= 3)) { - // select channels from first span - for(uint8_t i = 0; i < 16; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - uint8_t chNum = chMaskCntl * 16 + i; // 0 through 63 or 95 - this->subBand = chNum / 8 + 1; // save configured subband in case we must reset the channels (1-based) - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[0].freqStart + chNum*this->band->txSpans[0].freqStep; - chnl.drMin = this->band->txSpans[0].drMin; - chnl.drMax = this->band->txSpans[0].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 1 && chMaskCntl == 6) { - // all channels on (but we revert to user-selected subband) - this->setupChannelsFix(this->subBand); - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 4) { - // select channels from second span - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - uint8_t chNum = chMaskCntl * 16 + i; // 64 through 71 - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 5) { - // a '1' enables a bank of 8 + 1 channels from 1st and 2nd span respectively - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - // enable bank of 8 channels from first span - for(uint8_t j = 0; j < 8; j++) { - uint8_t chNum = i * 8 + j; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[0].freqStart + chNum*this->band->txSpans[0].freqStep; - chnl.drMin = this->band->txSpans[0].drMin; - chnl.drMax = this->band->txSpans[0].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - // enable single channel from second span - uint8_t chNum = 64 + i; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 6) { - // all channels on (but we revert to selected subband) - this->setupChannelsFix(this->subBand); - - // a '1' enables a single channel from second span - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - // enable single channel from second span - uint8_t chNum = 64 + i; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 7) { - // all channels off (clear all channels) - chnl = RADIOLIB_LORAWAN_CHANNEL_NONE; - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = chnl; - // downlink channels are not defined so don't need to reset - } - idx = 0; - // a '1' enables a single channel from second span - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - // enable single channel from second span - uint8_t chNum = 64 + i; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - - #if RADIOLIB_DEBUG_PROTOCOL - this->printChannels(); - #endif - - return(true); -} - -uint8_t LoRaWANNode::getMacPayloadLength(uint8_t cid) { - for (LoRaWANMacSpec_t entry : MacTable) { - // cppcheck warns here we should use std::find_if, but some platforms may not have that - if (entry.cid == cid) { // cppcheck-suppress useStlAlgorithm - return entry.lenDn; - } - } - // no idea about the length - return 0; -} - -int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) { - uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_LINK_CHECK_REQ, &this->commandsDown, payload); - RADIOLIB_ASSERT(state); - - if(margin) { *margin = payload[0]; } - if(gwCnt) { *gwCnt = payload[1]; } - - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) { - uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, &this->commandsDown, payload); - RADIOLIB_ASSERT(state); - - if(gpsEpoch) { - *gpsEpoch = LoRaWANNode::ntoh(&payload[0]); - if(returnUnix) { - uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980) - *gpsEpoch += unixOffset; - } - } - if(fraction) { *fraction = payload[4]; } - - return(RADIOLIB_ERR_NONE); -} - -uint32_t LoRaWANNode::getDevAddr() { - return(this->devAddr); -} - -RadioLibTime_t LoRaWANNode::getLastToA() { - return(this->lastToA); -} - -// The following function enables LMAC, a CSMA scheme for LoRa as specified -// in the LoRa Alliance Technical Recommendation #13. -// A user may enable CSMA to provide frames an additional layer of protection from interference. -// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma -void LoRaWANNode::performCSMA() { - - // Compute initial random back-off. - // When BO is reduced to zero, the function returns and the frame is transmitted. - uint32_t BO = this->phyLayer->random(1, this->backoffMax + 1); - while (BO > 0) { - // DIFS: Check channel for DIFS_slots - bool channelFreeDuringDIFS = true; - for (uint8_t i = 0; i < this->difsSlots; i++) { - if (performCAD()) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Occupied channel during DIFS"); - channelFreeDuringDIFS = false; - // Channel is occupied during DIFS, hop to another. - this->selectChannels(); - break; - } - } - // Start reducing BO counter if DIFS slot was free. - if (channelFreeDuringDIFS) { - // Continue decrementing BO with per each CAD reporting free channel. - while (BO > 0) { - if (performCAD()) { - 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. - } - BO--; // Decrement BO by one if channel is free - } - } - } -} -bool LoRaWANNode::performCAD() { - int16_t state = this->phyLayer->scanChannel(); - if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { - return true; // Channel is busy - } - return false; // Channel is free -} - void LoRaWANNode::processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter) { // figure out how many encryption blocks are there size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE; @@ -3036,14 +3285,36 @@ void LoRaWANNode::processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_ } } -uint16_t LoRaWANNode::checkSum16(uint8_t *key, uint16_t keyLen) { +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); +} + +uint16_t LoRaWANNode::checkSum16(const uint8_t *key, uint16_t keyLen) { uint16_t checkSum = 0; 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; + uint16_t word = (key[i] << 8); + if(i + 1 < keyLen) { + word |= key[i + 1]; + } + checkSum ^= word; } return(checkSum); } diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 5c2bbee9..9b3dc6ed 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -47,42 +47,39 @@ // fPort field #define RADIOLIB_LORAWAN_FPORT_MAC_COMMAND (0x00 << 0) // 7 0 payload contains MAC commands only +#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN (0x01 << 0) // 7 0 start of user-allowed fPort range +#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX (0xDF << 0) // 7 0 end of user-allowed fPort range #define RADIOLIB_LORAWAN_FPORT_TS009 (0xE0 << 0) // 7 0 fPort used for TS009 testing +#define RADIOLIB_LORAWAN_FPORT_TS011 (0xE2 << 0) // 7 0 fPort used for TS011 Forwarding #define RADIOLIB_LORAWAN_FPORT_RESERVED (0xE0 << 0) // 7 0 fPort values equal to and larger than this are reserved -// MAC commands - only those sent from end-device to gateway -#define RADIOLIB_LORAWAN_LINK_CHECK_REQ (0x02 << 0) // 7 0 MAC command: request to check connectivity to network -#define RADIOLIB_LORAWAN_LINK_ADR_ANS (0x03 << 0) // 7 0 answer to ADR change -#define RADIOLIB_LORAWAN_DUTY_CYCLE_ANS (0x04 << 0) // 7 0 answer to duty cycle change -#define RADIOLIB_LORAWAN_RX_PARAM_SETUP_ANS (0x05 << 0) // 7 0 answer to reception slot setup request -#define RADIOLIB_LORAWAN_DEV_STATUS_ANS (0x06 << 0) // 7 0 device status information -#define RADIOLIB_LORAWAN_NEW_CHANNEL_ANS (0x07 << 0) // 7 0 acknowledges change of a radio channel -#define RADIOLIB_LORAWAN_RX_TIMING_SETUP_ANS (0x08 << 0) // 7 0 acknowledges change of a reception slots timing - -#define RADIOLIB_LORAWAN_NOPTS_LEN (8) - // data rate encoding -#define RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K (0x01 << 7) // 7 7 FSK @ 50 kbps -#define RADIOLIB_LORAWAN_DATA_RATE_SF_12 (0x06 << 4) // 6 4 LoRa spreading factor: SF12 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_11 (0x05 << 4) // 6 4 SF11 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_10 (0x04 << 4) // 6 4 SF10 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_9 (0x03 << 4) // 6 4 SF9 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_8 (0x02 << 4) // 6 4 SF8 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_7 (0x01 << 4) // 6 4 SF7 -#define RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ (0x00 << 2) // 3 2 LoRa bandwidth: 500 kHz -#define RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ (0x01 << 2) // 3 2 250 kHz -#define RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ (0x02 << 2) // 3 2 125 kHz -#define RADIOLIB_LORAWAN_DATA_RATE_BW_RESERVED (0x03 << 2) // 3 2 reserved value -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_5 (0x00 << 0) // 1 0 LoRa coding rate: 4/5 -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_6 (0x01 << 0) // 1 0 4/6 -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_7 (0x02 << 0) // 1 0 4/7 -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_8 (0x03 << 0) // 1 0 4/8 +#define RADIOLIB_LORAWAN_DATA_RATE_MODEM (0x03 << 6) // 7 6 modem mask +#define RADIOLIB_LORAWAN_DATA_RATE_LORA (0x00 << 6) // 7 6 use LoRa modem +#define RADIOLIB_LORAWAN_DATA_RATE_FSK (0x01 << 6) // 7 6 use FSK modem +#define RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS (0x02 << 6) // 7 6 use LR-FHSS modem +#define RADIOLIB_LORAWAN_DATA_RATE_SF (0x07 << 3) // 5 3 LoRa spreading factor mask +#define RADIOLIB_LORAWAN_DATA_RATE_SF_12 (0x05 << 3) // 5 3 LoRa spreading factor: SF12 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_11 (0x04 << 3) // 5 3 SF11 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_10 (0x03 << 3) // 5 3 SF10 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_9 (0x02 << 3) // 5 3 SF9 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_8 (0x01 << 3) // 5 3 SF8 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_7 (0x00 << 3) // 5 3 SF7 +#define RADIOLIB_LORAWAN_DATA_RATE_BW (0x03 << 1) // 2 1 bandwith mask +#define RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ (0x00 << 1) // 2 1 125 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ (0x01 << 1) // 2 1 250 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ (0x02 << 1) // 2 1 LoRa bandwidth: 500 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ (0x00 << 1) // 2 1 137 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ (0x01 << 1) // 2 1 336 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ (0x02 << 1) // 2 1 LR-FHSS bandwidth: 1523 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 (0x00 << 0) // 0 0 LR-FHSS coding rate: 1/3 +#define RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 (0x01 << 0) // 0 0 2/3 #define RADIOLIB_LORAWAN_DATA_RATE_UNUSED (0xFF << 0) // 7 0 unused data rate -#define RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK (0x00 << 0) -#define RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK (0x01 << 0) -#define RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH (0x02 << 0) -#define RADIOLIB_LORAWAN_CHANNEL_DIR_NONE (0x03 << 0) +// channels and channel plans +#define RADIOLIB_LORAWAN_UPLINK (0x00 << 0) +#define RADIOLIB_LORAWAN_DOWNLINK (0x01 << 0) +#define RADIOLIB_LORAWAN_DIR_RX2 (0x02 << 0) #define RADIOLIB_LORAWAN_BAND_DYNAMIC (0) #define RADIOLIB_LORAWAN_BAND_FIXED (1) #define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (15) @@ -118,14 +115,18 @@ #define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS (7) -#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS (4) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS (11) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS (12) -#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS (12) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS (13) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN (16) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS (RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN - 1) +// join accept key derivation layout +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_NONCE_POS (1) // regular keys +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS (4) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS (12) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_ADDR_POS (1) // relay keys + // join accept message variables #define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_0 (0x00 << 7) // 7 7 LoRaWAN revision: 1.0 #define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1 (0x01 << 7) // 7 7 1.1 @@ -135,6 +136,9 @@ #define RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY (0x04) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_ENC_KEY (0x05) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY (0x06) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_ROOT_WOR_S_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_WOR_S_INT_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_WOR_S_ENC_KEY (0x02) // frame header layout #define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16) @@ -172,8 +176,13 @@ // unused frame counter value #define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) +// TR013 CSMA recommended values +#define RADIOLIB_LORAWAN_DIFS_DEFAULT (2) +#define RADIOLIB_LORAWAN_BACKOFF_MAX_DEFAULT (6) +#define RADIOLIB_LORAWAN_MAX_CHANGES_DEFAULT (4) + // MAC commands -#define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (16) +#define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (23) #define RADIOLIB_LORAWAN_MAC_RESET (0x01) #define RADIOLIB_LORAWAN_MAC_LINK_CHECK (0x02) @@ -204,10 +213,10 @@ #define RADIOLIB_LORAWAN_MAX_NUM_ADR_COMMANDS (8) /*! - \struct LoRaWANMacSpec_t + \struct LoRaWANMacCommand_t \brief MAC command specification structure. */ -struct LoRaWANMacSpec_t { +struct LoRaWANMacCommand_t { /*! \brief Command ID */ const uint8_t cid; @@ -216,87 +225,58 @@ struct LoRaWANMacSpec_t { /*! \brief Downlink message length */ const uint8_t lenUp; + + /*! \brief Some commands must be resent until Class A downlink received */ + const bool persist; /*! \brief Whether this MAC command can be issued by the user or not */ const bool user; }; -constexpr LoRaWANMacSpec_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS + 1] = { - { 0x00, 0, 0, false }, // not an actual MAC command, exists for index offsetting - { RADIOLIB_LORAWAN_MAC_RESET, 1, 1, false }, - { RADIOLIB_LORAWAN_MAC_LINK_CHECK, 2, 0, true }, - { RADIOLIB_LORAWAN_MAC_LINK_ADR, 4, 1, false }, - { RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, 4, 1, false }, - { RADIOLIB_LORAWAN_MAC_DEV_STATUS, 0, 2, false }, - { RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, 5, 1, false }, - { RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_DL_CHANNEL, 4, 1, false }, - { RADIOLIB_LORAWAN_MAC_REKEY, 1, 1, false }, - { RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_DEVICE_TIME, 5, 0, true }, - { RADIOLIB_LORAWAN_MAC_FORCE_REJOIN, 2, 0, false }, - { RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP, 1, 1, false }, - { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, true } -}; +#define RADIOLIB_LORAWAN_MAC_COMMAND_NONE { .cid = 0, .lenDn = 0, .lenUp = 0, .persist = false, .user = false } -/*! - \struct LoRaWANMacCommand_t - \brief Structure to save information about MAC command -*/ -struct LoRaWANMacCommand_t { - /*! \brief The command ID */ - uint8_t cid; - - /*! \brief Payload buffer (5 bytes is the longest possible) */ - uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN]; - - /*! \brief Length of the payload */ - uint8_t len; - - /*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */ - uint8_t repeat; -}; - -/*! - \struct LoRaWANMacCommandQueue_t - \brief Structure to hold information about a queue of MAC commands -*/ -struct LoRaWANMacCommandQueue_t { - /*! \brief Number of commands in the queue */ - uint8_t numCommands; - - /*! \brief Total length of the queue */ - uint8_t len; - - /*! \brief MAC command buffer */ - LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE]; +constexpr LoRaWANMacCommand_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS] = { + { RADIOLIB_LORAWAN_MAC_RESET, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_LINK_CHECK, 2, 0, false, true }, + { RADIOLIB_LORAWAN_MAC_LINK_ADR, 4, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, 1, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, 4, 1, true, false }, + { RADIOLIB_LORAWAN_MAC_DEV_STATUS, 0, 2, false, false }, + { RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, 5, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, 1, 0, true, false }, + { RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, 1, 0, true, false }, + { RADIOLIB_LORAWAN_MAC_DL_CHANNEL, 4, 1, true, false }, + { RADIOLIB_LORAWAN_MAC_REKEY, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, 1, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_DEVICE_TIME, 5, 0, false, true }, + { RADIOLIB_LORAWAN_MAC_FORCE_REJOIN, 2, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, false, true }, }; #define RADIOLIB_LORAWAN_NONCES_VERSION_VAL (0x0001) enum LoRaWANSchemeBase_t { RADIOLIB_LORAWAN_NONCES_START = 0x00, - RADIOLIB_LORAWAN_NONCES_VERSION = RADIOLIB_LORAWAN_NONCES_START, // 2 bytes - RADIOLIB_LORAWAN_NONCES_MODE = RADIOLIB_LORAWAN_NONCES_VERSION + sizeof(uint16_t), // 2 bytes - RADIOLIB_LORAWAN_NONCES_CLASS = RADIOLIB_LORAWAN_NONCES_MODE + sizeof(uint16_t), // 1 byte - RADIOLIB_LORAWAN_NONCES_PLAN = RADIOLIB_LORAWAN_NONCES_CLASS + sizeof(uint8_t), // 1 byte - RADIOLIB_LORAWAN_NONCES_CHECKSUM = RADIOLIB_LORAWAN_NONCES_PLAN + sizeof(uint8_t), // 2 bytes - RADIOLIB_LORAWAN_NONCES_DEV_NONCE = RADIOLIB_LORAWAN_NONCES_CHECKSUM + sizeof(uint16_t), // 2 bytes - RADIOLIB_LORAWAN_NONCES_JOIN_NONCE = RADIOLIB_LORAWAN_NONCES_DEV_NONCE + sizeof(uint16_t), // 3 bytes - RADIOLIB_LORAWAN_NONCES_ACTIVE = RADIOLIB_LORAWAN_NONCES_JOIN_NONCE + 3, // 1 byte - RADIOLIB_LORAWAN_NONCES_SIGNATURE = RADIOLIB_LORAWAN_NONCES_ACTIVE + sizeof(uint8_t), // 2 bytes - RADIOLIB_LORAWAN_NONCES_BUF_SIZE = RADIOLIB_LORAWAN_NONCES_SIGNATURE + sizeof(uint16_t) // Nonces buffer size + RADIOLIB_LORAWAN_NONCES_VERSION = RADIOLIB_LORAWAN_NONCES_START, // 2 bytes + RADIOLIB_LORAWAN_NONCES_MODE = RADIOLIB_LORAWAN_NONCES_VERSION + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_CLASS = RADIOLIB_LORAWAN_NONCES_MODE + sizeof(uint16_t), // 1 byte + RADIOLIB_LORAWAN_NONCES_PLAN = RADIOLIB_LORAWAN_NONCES_CLASS + sizeof(uint8_t), // 1 byte + RADIOLIB_LORAWAN_NONCES_CHECKSUM = RADIOLIB_LORAWAN_NONCES_PLAN + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_DEV_NONCE = RADIOLIB_LORAWAN_NONCES_CHECKSUM + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_JOIN_NONCE = RADIOLIB_LORAWAN_NONCES_DEV_NONCE + sizeof(uint16_t), // 3 bytes + RADIOLIB_LORAWAN_NONCES_ACTIVE = RADIOLIB_LORAWAN_NONCES_JOIN_NONCE + 3, // 1 byte + RADIOLIB_LORAWAN_NONCES_SIGNATURE = RADIOLIB_LORAWAN_NONCES_ACTIVE + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_BUF_SIZE = RADIOLIB_LORAWAN_NONCES_SIGNATURE + sizeof(uint16_t) // Nonces buffer size }; enum LoRaWANSchemeSession_t { RADIOLIB_LORAWAN_SESSION_START = 0x00, - RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY = RADIOLIB_LORAWAN_SESSION_START, // 16 bytes - RADIOLIB_LORAWAN_SESSION_APP_SKEY = RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY + RADIOLIB_AES128_BLOCK_SIZE, // 16 bytes - RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_APP_SKEY + RADIOLIB_AES128_BLOCK_SIZE, // 16 bytes - RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY + RADIOLIB_AES128_BLOCK_SIZE, // 16 bytes - RADIOLIB_LORAWAN_SESSION_DEV_ADDR = RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY + RADIOLIB_AES128_BLOCK_SIZE, // 4 bytes + RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY = RADIOLIB_LORAWAN_SESSION_START, // 16 bytes + RADIOLIB_LORAWAN_SESSION_APP_SKEY = RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_APP_SKEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_DEV_ADDR = RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY + RADIOLIB_AES128_KEY_SIZE, // 4 bytes RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE = RADIOLIB_LORAWAN_SESSION_DEV_ADDR + sizeof(uint32_t), // 2 bytes RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE + sizeof(uint16_t), // 4 bytes RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP = RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN + sizeof(uint32_t), // 4 bytes @@ -306,22 +286,24 @@ enum LoRaWANSchemeSession_t { RADIOLIB_LORAWAN_SESSION_HOMENET_ID = RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 + sizeof(uint16_t), // 4 bytes RADIOLIB_LORAWAN_SESSION_VERSION = RADIOLIB_LORAWAN_SESSION_HOMENET_ID + sizeof(uint32_t), // 1 byte RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE = RADIOLIB_LORAWAN_SESSION_VERSION + sizeof(uint8_t), // 1 byte - RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE + MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn, // 4 bytes - RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn, // 1 byte - RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn, // 1 byte - RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn, // 1 byte - RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP].lenDn, // 1 byte - RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP].lenDn, // 3 bytes - RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL = RADIOLIB_LORAWAN_SESSION_BEACON_FREQ + 3, // 4 bytes - RADIOLIB_LORAWAN_SESSION_PERIODICITY = RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL + 4, // 1 byte - RADIOLIB_LORAWAN_SESSION_LAST_TIME = RADIOLIB_LORAWAN_SESSION_PERIODICITY + 1, // 4 bytes - RADIOLIB_LORAWAN_SESSION_UL_CHANNELS = RADIOLIB_LORAWAN_SESSION_LAST_TIME + 4, // 16*5 bytes - RADIOLIB_LORAWAN_SESSION_DL_CHANNELS = RADIOLIB_LORAWAN_SESSION_UL_CHANNELS + 16*MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn, // 16*4 bytes - RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL = RADIOLIB_LORAWAN_SESSION_DL_CHANNELS + 16*MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn, // 9*8+2 bytes - RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL + sizeof(LoRaWANMacCommandQueue_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE + 1, // 4 bytes + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP + 4, // 1 byte + RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + 1, // 3 bytes + RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL = RADIOLIB_LORAWAN_SESSION_BEACON_FREQ + 3, // 4 bytes + RADIOLIB_LORAWAN_SESSION_PERIODICITY = RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL + 4, // 1 byte + RADIOLIB_LORAWAN_SESSION_LAST_TIME = RADIOLIB_LORAWAN_SESSION_PERIODICITY + 1, // 4 bytes + RADIOLIB_LORAWAN_SESSION_UL_CHANNELS = RADIOLIB_LORAWAN_SESSION_LAST_TIME + 4, // 16*5 bytes + RADIOLIB_LORAWAN_SESSION_DL_CHANNELS = RADIOLIB_LORAWAN_SESSION_UL_CHANNELS + RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS*5, // 16*4 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE = RADIOLIB_LORAWAN_SESSION_DL_CHANNELS + RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS*4, // 15 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN + RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN, // 4 bytes RADIOLIB_LORAWAN_SESSION_ADR_FCNT = RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN + sizeof(uint32_t), // 4 bytes - RADIOLIB_LORAWAN_SESSION_LINK_ADR = RADIOLIB_LORAWAN_SESSION_ADR_FCNT + sizeof(uint32_t), // 4 bytes - RADIOLIB_LORAWAN_SESSION_FCNT_UP = RADIOLIB_LORAWAN_SESSION_LINK_ADR + MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, // 4 bytes + RADIOLIB_LORAWAN_SESSION_LINK_ADR = RADIOLIB_LORAWAN_SESSION_ADR_FCNT + sizeof(uint32_t), // 14 bytes + RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS = RADIOLIB_LORAWAN_SESSION_LINK_ADR + 14, // 2 bytes + RADIOLIB_LORAWAN_SESSION_FCNT_UP = RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS + 2, // 4 bytes RADIOLIB_LORAWAN_SESSION_SIGNATURE = RADIOLIB_LORAWAN_SESSION_FCNT_UP + sizeof(uint32_t), // 2 bytes RADIOLIB_LORAWAN_SESSION_BUF_SIZE = RADIOLIB_LORAWAN_SESSION_SIGNATURE + sizeof(uint16_t) // Session buffer size }; @@ -338,18 +320,25 @@ struct LoRaWANChannel_t { /*! \brief The channel number, as specified by defaults or the network */ uint8_t idx; - /*! \brief The channel frequency */ - float freq; + /*! \brief The channel frequency (coded in 100 Hz steps) */ + uint32_t freq; /*! \brief Minimum allowed datarate for this channel */ uint8_t drMin; /*! \brief Maximum allowed datarate for this channel (inclusive) */ uint8_t drMax; + + /*! \brief Datarate currently in use on this channel */ + uint8_t dr; + + /*! \brief Whether this channel is available for channel selection */ + bool available; }; // alias for unused channel -#define RADIOLIB_LORAWAN_CHANNEL_NONE { .enabled = false, .idx = RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE, .freq = 0, .drMin = 0, .drMax = 0 } +#define RADIOLIB_LORAWAN_CHANNEL_NONE { .enabled = false, .idx = RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE, .freq = 0, \ + .drMin = 0, .drMax = 0, .dr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, .available = false } /*! \struct LoRaWANChannelSpan_t @@ -360,11 +349,11 @@ struct LoRaWANChannelSpan_t { /*! \brief Total number of channels in the span */ uint8_t numChannels; - /*! \brief Center frequency of the first channel in span */ - float freqStart; + /*! \brief Center frequency of the first channel in span (coded in 100 Hz steps) */ + uint32_t freqStart; - /*! \brief Frequency step between adjacent channels */ - float freqStep; + /*! \brief Frequency step between adjacent channels (coded in 100 Hz steps) */ + uint32_t freqStep; /*! \brief Minimum allowed datarate for all channels in this span */ uint8_t drMin; @@ -373,11 +362,11 @@ struct LoRaWANChannelSpan_t { uint8_t drMax; /*! \brief Allowed data rates for a join request message */ - uint8_t joinRequestDataRate; + uint8_t drJoinRequest; }; // alias for unused channel span -#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .numChannels = 0, .freqStart = 0, .freqStep = 0, .drMin = 0, .drMax = 0, .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED } +#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .numChannels = 0, .freqStart = 0, .freqStep = 0, .drMin = 0, .drMax = 0, .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED } /*! \struct LoRaWANBand_t @@ -390,7 +379,13 @@ struct LoRaWANBand_t { /*! \brief Whether the channels are fixed per specification, or dynamically allocated through the network (plus defaults) */ uint8_t bandType; - /*! \brief Array of allowed maximum payload lengths for each data rate */ + /*! \brief Minimum allowed frequency (coded in 100 Hz steps) */ + uint32_t freqMin; + + /*! \brief Maximum allowed frequency (coded in 100 Hz steps) */ + uint32_t freqMax; + + /*! \brief Array of allowed maximum payload lengths for each data rate (global maximum) */ uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; /*! \brief Maximum allowed output power in this band in dBm */ @@ -408,26 +403,34 @@ struct LoRaWANBand_t { /*! \brief Maximum dwell time per downlink message in milliseconds */ RadioLibTime_t dwellTimeDn; - /*! \brief A set of default uplink (TX) channels for frequency-type bands */ + /*! \brief Whether this band implements the MAC command TxParamSetupReq */ + bool txParamSupported; + + /*! \brief A set of default uplink (TX) channels for dynamic bands */ LoRaWANChannel_t txFreqs[3]; - /*! \brief A set of possible extra channels for the Join-Request message for frequency-type bands */ + /*! \brief A set of possible extra channels for the Join-Request message for dynamic bands */ LoRaWANChannel_t txJoinReq[3]; - /*! \brief The number of TX channel spans for mask-type bands */ + /*! \brief The number of TX channel spans for fixed bands */ uint8_t numTxSpans; - /*! \brief Default uplink (TX) channel spans for mask-type bands, including Join-Request parameters */ + /*! \brief Default uplink (TX) channel spans for fixed bands, including Join-Request parameters */ LoRaWANChannelSpan_t txSpans[2]; - /*! \brief Default downlink (RX1) channel span for mask-type bands */ + /*! \brief Default downlink (RX1) channel span for fixed bands */ LoRaWANChannelSpan_t rx1Span; - /*! \brief The base downlink data rate. Used to calculate data rate changes for adaptive data rate */ - uint8_t rx1DataRateBase; + uint8_t rx1DrTable[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES][8]; /*! \brief Backup channel for downlink (RX2) window */ LoRaWANChannel_t rx2; + + /*! \brief Relay channels for WoR uplink */ + LoRaWANChannel_t txWoR[2]; + + /*! \brief Relay channels for ACK downlink */ + LoRaWANChannel_t txAck[2]; /*! \brief The corresponding datarates, bandwidths and coding rates for DR index */ uint8_t dataRates[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; @@ -515,6 +518,9 @@ struct LoRaWANEvent_t { /*! \brief Port number */ uint8_t fPort; + + /*! \brief Number of times this uplink was transmitted (ADR)*/ + uint8_t nbTrans; }; /*! @@ -532,11 +538,6 @@ class LoRaWANNode { */ LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand = 0); - /*! - \brief Clear an active session, so that the device will have to rejoin the network. - */ - void clearSession(); - /*! \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 @@ -550,6 +551,11 @@ class LoRaWANNode { */ int16_t setBufferNonces(uint8_t* persistentBuffer); + /*! + \brief Clear an active session, so that the device will have to rejoin the network. + */ + void clearSession(); + /*! \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 @@ -569,16 +575,9 @@ 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. - */ - void beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey); - - /*! - \brief Join network by restoring OTAA session or performing over-the-air activation. By this procedure, - the device will perform an exchange with the network server and set all necessary configuration. - \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 activateOTAA(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, LoRaWANJoinEvent_t *joinEvent = NULL); + int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey); /*! \brief Set the device credentials and activation configuration @@ -588,8 +587,17 @@ class LoRaWANNode { \param nwkSEncKey Pointer to the MAC command network session key [NwkSEncKey] (LoRaWAN 1.1) or network session AES-128 key [NwkSKey] (LoRaWAN 1.0). \param appSKey Pointer to the application session AES-128 key. + \returns \ref status_codes */ - void beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey); + int16_t beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey); + + /*! + \brief Join network by restoring OTAA session or performing over-the-air activation. By this procedure, + the device will perform an exchange with the network server and set all necessary configuration. + \param joinDr The datarate at which to send the join-request and any subsequent uplinks (unless ADR is enabled) + \returns \ref status_codes + */ + virtual int16_t activateOTAA(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, LoRaWANJoinEvent_t *joinEvent = NULL); /*! \brief Join network by restoring ABP session or performing over-the-air activation. @@ -602,88 +610,6 @@ class LoRaWANNode { /*! \brief Whether there is an ongoing session active */ bool isActivated(); - /*! - \brief Configure the Rx2 datarate for ABP mode. - This should not be needed for LoRaWAN 1.1 as it is configured through the first downlink. - \param dr The datarate to be used for listening for downlinks in Rx2. - \returns \ref status_codes - */ - int16_t setRx2Dr(uint8_t dr); - - /*! - \brief Add a MAC command to the uplink queue. - Only LinkCheck and DeviceTime are available to the user. - Other commands are ignored; duplicate MAC commands are discarded. - \param cid ID of the MAC command - \returns \ref status_codes - */ - int16_t sendMacCommandReq(uint8_t cid); - - #if defined(RADIOLIB_BUILD_ARDUINO) - /*! - \brief Send a message to the server. - \param str Address of Arduino String that will be transmitted. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t uplink(String& str, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* event = NULL); - #endif - - /*! - \brief Send a message to the server. - \param str C-string that will be transmitted. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t uplink(const char* str, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* event = NULL); - - /*! - \brief Send a message to the server. - \param data Data to send. - \param len Length of the data. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t uplink(uint8_t* data, size_t len, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* event = NULL); - - #if defined(RADIOLIB_BUILD_ARDUINO) - /*! - \brief Wait for downlink from the server in either RX1 or RX2 window. - \param str Address of Arduino String to save the received data. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t downlink(String& str, LoRaWANEvent_t* event = NULL); - #endif - - /*! - \brief Wait for downlink from the server in either RX1 or RX2 window. - \param data Buffer to save received data into. - \param len Pointer to variable that will be used to save the number of received bytes. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event = NULL); - - /*! - \brief Wait for downlink, simplified to allow for simpler sendReceive - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t downlink(LoRaWANEvent_t* event = NULL); - #if defined(RADIOLIB_BUILD_ARDUINO) /*! \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. @@ -695,11 +621,24 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \param eventDown Pointer to a structure to store extra information about the downlink event (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes */ - int16_t sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); #endif + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param strUp C-string that will be transmitted. + \param fPort Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(const char* strUp, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + /*! \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. \param strUp C-string that will be transmitted. @@ -711,9 +650,23 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \param eventDown Pointer to a structure to store extra information about the downlink event (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes */ - int16_t sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + + /*! + \brief Send a message to the server and wait for a downlink but don't bother the user with downlink contents + \param dataUp Data to send. + \param lenUp Length of the data. + \param fPort Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort = 1, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); /*! \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. @@ -727,23 +680,92 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \param eventDown Pointer to a structure to store extra information about the downlink event (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes */ - int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); /*! - \brief Send a message to the server and wait for a downlink but don't bother the user with downlink contents - \param dataUp Data to send. - \param lenUp Length of the data. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param eventUp Pointer to a structure to store extra information about the uplink event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \param eventDown Pointer to a structure to store extra information about the downlink event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \brief Add a MAC command to the uplink queue. + Only LinkCheck and DeviceTime are available to the user. + Other commands are ignored; duplicate MAC commands are discarded. + \param cid ID of the MAC command \returns \ref status_codes */ - int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort = 1, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + int16_t sendMacCommandReq(uint8_t cid); + + /*! + \brief Returns the quality of connectivity after requesting a LinkCheck MAC command. + Returns 'true' if a network response was successfully parsed. + Returns 'false' if there was no network response / parsing failed. + \param margin Link margin in dB of LinkCheckReq demodulation at gateway side. + \param gwCnt Number of gateways that received the LinkCheckReq. + \returns \ref status_codes + */ + int16_t getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt); + + /*! + \brief Returns the network time after requesting a DeviceTime MAC command. + Returns 'true' if a network response was successfully parsed. + Returns 'false' if there was no network response / parsing failed. + \param gpsEpoch Number of seconds since GPS epoch (Jan. 6th 1980) + \param fraction Fractional-second, in 1/256-second steps + \param returnUnix If true, returns Unix timestamp instead of GPS (default true) + \returns \ref status_codes + */ + int16_t getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix = true); + + /*! + \brief Set uplink datarate. This should not be used when ADR is enabled. + \param drUp Datarate to use for uplinks. + \returns \ref status_codes + */ + int16_t setDatarate(uint8_t drUp); + + /*! + \brief Configure TX power of the radio module. + \param txPower Output power during TX mode to be set in dBm. + \returns \ref status_codes + */ + int16_t setTxPower(int8_t txPower); + + /*! + \brief Configure the Rx2 datarate for ABP mode. + This should not be needed for LoRaWAN 1.1 as it is configured through the first downlink. + \param dr The datarate to be used for listening for downlinks in Rx2. + \returns \ref status_codes + */ + int16_t setRx2Dr(uint8_t dr); + + /*! + \brief Toggle ADR to on or off. + \param enable Whether to disable ADR or not. + */ + void setADR(bool enable = true); + + /*! + \brief Toggle adherence to dutyCycle limits to on or off. + \param enable Whether to adhere to dutyCycle limits or not (default true). + \param msPerHour The maximum allowed Time-on-Air per hour in milliseconds + (default 0 = maximum allowed for configured band). + */ + void setDutyCycle(bool enable = true, RadioLibTime_t msPerHour = 0); + + /*! + \brief Toggle adherence to dwellTime limits to on or off. + \param enable Whether to adhere to dwellTime limits or not (default true). + \param msPerUplink The maximum allowed Time-on-Air per uplink in milliseconds + (default 0 = maximum allowed for configured band). + */ + void setDwellTime(bool enable, RadioLibTime_t msPerUplink = 0); + + /*! + \brief Configures CSMA for LoRaWAN as per TR013, LoRa Alliance. + \param csmaEnabled Enable/disable CSMA for LoRaWAN. + \param maxChanges Maximum number of channel hops if channel is used (default 4). + \param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO (default). + \param difsSlots Num of CADs to estimate a clear CH (default 2). + */ + void setCSMA(bool csmaEnabled, uint8_t maxChanges = 4, uint8_t backoffMax = 0, uint8_t difsSlots = 2); /*! \brief Set device status. @@ -752,6 +774,15 @@ class LoRaWANNode { */ void setDeviceStatus(uint8_t battLevel); + /*! + \brief Set the exact time a transmission should occur. Note: this is the internal clock time. + On Arduino platforms, this is the usual time supplied by millis(). + If the supplied time is larger than the current time, sendReceive() or uplink() will delay + until the scheduled time. + \param tUplink Transmission time in milliseconds, based on internal clock. + */ + void scheduleTransmission(RadioLibTime_t tUplink); + /*! \brief Returns the last uplink's frame counter; also 0 if no uplink occured yet. @@ -778,25 +809,16 @@ class LoRaWANNode { void resetFCntDown(); /*! - \brief Set uplink datarate. This should not be used when ADR is enabled. - \param drUp Datarate to use for uplinks. - \returns \ref status_codes + \brief Returns the DevAddr of the device, regardless of OTAA or ABP mode + \returns 4-byte DevAddr */ - int16_t setDatarate(uint8_t drUp); + uint32_t getDevAddr(); /*! - \brief Toggle ADR to on or off. - \param enable Whether to disable ADR or not. + \brief Get the Time-on-air of the last uplink message (in milliseconds). + \returns (RadioLibTime_t) time-on-air (ToA) of last uplink message (in milliseconds). */ - void setADR(bool enable = true); - - /*! - \brief Toggle adherence to dutyCycle limits to on or off. - \param enable Whether to adhere to dutyCycle limits or not (default true). - \param msPerHour The maximum allowed Time-on-Air per hour in milliseconds - (default 0 = maximum allowed for configured band). - */ - void setDutyCycle(bool enable = true, RadioLibTime_t msPerHour = 0); + RadioLibTime_t getLastToA(); /*! \brief Calculate the minimum interval to adhere to a certain dutyCycle. @@ -810,60 +832,11 @@ class LoRaWANNode { /*! \brief Returns time in milliseconds until next uplink is available under dutyCycle limits */ RadioLibTime_t timeUntilUplink(); - /*! - \brief Toggle adherence to dwellTime limits to on or off. - \param enable Whether to adhere to dwellTime limits or not (default true). - \param msPerUplink The maximum allowed Time-on-Air per uplink in milliseconds - (default 0 = maximum allowed for configured band). - */ - void setDwellTime(bool enable, RadioLibTime_t msPerUplink = 0); - /*! - \brief Returns the maximum payload given the currently present dwell time limits. - WARNING: the addition of MAC commands may cause uplink errors; - if you want to be sure that your payload fits within dwell time limits, subtract 16 from the result! + \brief Returns the maximum allowed uplink payload size given the current MAC state. + Most importantly, this includes dwell time limitations and ADR. */ - uint8_t maxPayloadDwellTime(); - - /*! - \brief Configure TX power of the radio module. - \param txPower Output power during TX mode to be set in dBm. - \returns \ref status_codes - */ - int16_t setTxPower(int8_t txPower); - - /*! - \brief Returns the quality of connectivity after requesting a LinkCheck MAC command. - Returns 'true' if a network response was successfully parsed. - Returns 'false' if there was no network response / parsing failed. - \param margin Link margin in dB of LinkCheckReq demodulation at gateway side. - \param gwCnt Number of gateways that received the LinkCheckReq. - \returns \ref status_codes - */ - int16_t getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt); - - /*! - \brief Returns the network time after requesting a DeviceTime MAC command. - Returns 'true' if a network response was successfully parsed. - Returns 'false' if there was no network response / parsing failed. - \param gpsEpoch Number of seconds since GPS epoch (Jan. 6th 1980) - \param fraction Fractional-second, in 1/256-second steps - \param returnUnix If true, returns Unix timestamp instead of GPS (default true) - \returns \ref status_codes - */ - int16_t getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix = true); - - /*! - \brief Returns the DevAddr of the device, regardless of OTAA or ABP mode - \returns 4-byte DevAddr - */ - uint32_t getDevAddr(); - - /*! - \brief Get the Time-on-air of the last uplink message (in milliseconds). - \returns (RadioLibTime_t) time-on-air (ToA) of last uplink message (in milliseconds). - */ - RadioLibTime_t getLastToA(); + uint8_t getMaxPayloadLen(); /*! \brief TS009 Protocol Specification Verification switch @@ -886,31 +859,21 @@ class LoRaWANNode { RadioLibTime_t scanGuard = 10; #if !RADIOLIB_GODMODE - private: + protected: #endif PhysicalLayer* phyLayer = NULL; const LoRaWANBand_t* band = NULL; - static int16_t checkBufferCommon(uint8_t *buffer, uint16_t size); - - void activateCommon(uint8_t initialDr); - // 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, - .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } }, - }; - LoRaWANMacCommandQueue_t commandsDown = { - .numCommands = 0, - .len = 0, - .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } }, - }; + uint8_t fOptsUp[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN] = { 0 }; + uint8_t fOptsDown[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN] = { 0 }; + uint8_t fOptsUpLen = 0; + uint8_t fOptsDownLen = 0; uint16_t lwMode = RADIOLIB_LORAWAN_MODE_NONE; uint8_t lwClass = RADIOLIB_LORAWAN_CLASS_A; @@ -955,6 +918,7 @@ class LoRaWANNode { // ADR is enabled by default bool adrEnabled = true; + bool adrAckReq = false; // duty cycle is set upon initialization and activated in regions that impose this bool dutyCycleEnabled = false; @@ -965,29 +929,34 @@ class LoRaWANNode { uint16_t dwellTimeUp = 0; bool dwellTimeEnabledDn = false; uint16_t dwellTimeDn = 0; - - // enable/disable CSMA for LoRaWAN - bool enableCSMA; - // number of backoff slots to be decremented after DIFS phase. 0 to disable BO. + RadioLibTime_t tUplink = 0; // scheduled uplink transmission time (internal clock) + RadioLibTime_t tDownlink = 0; // time at end of downlink reception + + // enable/disable CSMA for LoRaWAN + bool csmaEnabled = false; + + // maximum number of channel hops during CSMA + uint8_t maxChanges = RADIOLIB_LORAWAN_MAX_CHANGES_DEFAULT; + + // number of backoff slots to be checked after DIFS phase. // A random BO avoids collisions in the case where two or more nodes start the CSMA // process at the same time. - uint8_t backoffMax; + uint8_t backoffMax = RADIOLIB_LORAWAN_BACKOFF_MAX_DEFAULT; // number of CADs to estimate a clear CH - uint8_t difsSlots; + uint8_t difsSlots = RADIOLIB_LORAWAN_DIFS_DEFAULT; // available channel frequencies from list passed during OTA activation - LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; + LoRaWANChannel_t channelPlan[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; - // currently configured channels for TX and RX1 - LoRaWANChannel_t currentChannels[2] = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE }; + // currently configured channels for TX, RX1, RX2 + LoRaWANChannel_t channels[3] = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }; - // currently configured datarates for TX and RX1 - uint8_t dataRates[2] = { RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED }; - - // Rx2 channel properties - may be changed by MAC command - LoRaWANChannel_t rx2 = RADIOLIB_LORAWAN_CHANNEL_NONE; + // delays between the uplink and RX1/2 windows + // the first field is meaningless, but is used for offsetting for Rx windows 1 and 2 + RadioLibTime_t rxDelays[3] = { 0, RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS }; // offset between TX and RX1 (such that RX1 has equal or lower DR) uint8_t rx1DrOffset = 0; @@ -1004,9 +973,6 @@ class LoRaWANNode { // timestamp when the Rx1/2 windows were closed (timeout or uplink received) RadioLibTime_t rxDelayEnd = 0; - // delays between the uplink and RX1/2 windows - RadioLibTime_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS }; - // device status - battery level uint8_t battLevel = 0xFF; @@ -1016,14 +982,123 @@ class LoRaWANNode { // save the selected sub-band in case this must be restored in ADR control uint8_t subBand = 0; - // initalize the Nonces buffer after beginX() has been called - void createNonces(); + // allow port 226 for devices implementing TS011 + bool TS011 = false; // this will reset the device credentials, so the device starts completely new void clearNonces(); - // wait for, open and listen during Rx1 and Rx2 windows; only performs listening - int16_t downlinkCommon(); + // start a fresh session using default parameters + void createSession(uint16_t lwMode, uint8_t initialDr); + + // setup Join-Request payload + void composeJoinRequest(uint8_t* joinRequestMsg); + + // extract Join-Accept payload and start a new session + int16_t processJoinAccept(LoRaWANJoinEvent_t *joinEvent); + + // a join-accept can piggy-back a set of channels or channel masks + void processCFList(uint8_t* cfList); + + // check whether payload length and fport are allowed + int16_t isValidUplink(uint8_t* len, uint8_t fPort); + + // perform ADR backoff + void adrBackoff(); + + // create an encrypted uplink buffer, composing metadata, user data and MAC data + void composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t fPort, bool isConfirmed); + + // generate and set the MIC of an uplink buffer (depends on selected channels) + void micUplink(uint8_t* inOut, uint8_t lenInOut); + + // transmit uplink buffer on a specified channel + int16_t transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len, bool retrans); + + // wait for, open and listen during receive windows; only performs listening + int16_t receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChannels, const RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference); + + // extract downlink payload and process MAC commands + int16_t parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event = NULL); + + // execute mac command, return the number of processed bytes for sequential processing + bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn); + bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut); + + // possible override for additional MAC commands that are not in the base specification + virtual bool derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut); + + // pre-process a (set of) LinkAdrReq commands into one super-channel-mask + Tx/Dr/NbTrans fields + void preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt); + + // post-process a (set of) LinkAdrAns commands depending on LoRaWAN version + void postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen); + + // get the properties of a MAC command given a certain command ID + int16_t getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd); + + // possible override for additional MAC commands that are not in the base specification + virtual int16_t derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd); + + // get the length of a certain MAC command in a specific direction (up/down) + // if inclusive is true, add one for the CID byte + int16_t getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive = false); + + // find out of a MAC command should persist destruction + // in uplink direction, some commands must persist if no downlink is received + // in downlink direction, the user-accessible MAC commands remain available for retrieval + bool isPersistentMacCommand(uint8_t cid, uint8_t dir); + + // push MAC command to queue, done by copy + int16_t pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir); + + // retrieve the payload of a certain MAC command, if present in the buffer + int16_t getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir); + + // delete a specific MAC command from queue, indicated by the command ID + int16_t deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir); + + // clear a MAC buffer, possible retaining persistent MAC commands + void clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir); + + // configure the common physical layer properties (frequency, sync word etc.) + int16_t setPhyProperties(const LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre = 0); + + // Performs CSMA as per LoRa Alliance Technical Recommendation 13 (TR-013). + bool csmaChannelClear(uint8_t difs, uint8_t numBackoff); + + // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. + bool cadChannelClear(); + + // get or create a complete 80-bit channel mask for current configuration + void getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45); + + // setup uplink/downlink channel data rates and frequencies + // for dynamic channels, there is a small set of predefined channels + // in case of JoinRequest, add some optional extra frequencies + void selectChannelPlanDyn(bool joinRequest = false); + + // setup uplink/downlink channel data rates and frequencies + // for fixed bands, we only allow one sub-band at a time to be selected + void selectChannelPlanFix(); + + // get the number of available channels, + // along with a 16-bit mask indicating which channels can be used next for uplink/downlink + uint8_t getAvailableChannels(uint16_t* mask); + + // (re)set/restore which channels can be used next for uplink/downlink + void setAvailableChannels(uint16_t mask); + + // select a set of random TX/RX channels for up- and downlink + int16_t selectChannels(); + + // apply a 96-bit channel mask + bool applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45); + +#if RADIOLIB_DEBUG_PROTOCOL + // print the available channels through debug + void printChannels(); +#endif // method to generate message integrity code uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key); @@ -1032,78 +1107,17 @@ class LoRaWANNode { // it assumes that the MIC is the last 4 bytes of the message bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key); - // configure the common physical layer properties (preamble, sync word etc.) - // channels must be configured separately by setupChannelsDyn()! - int16_t setPhyProperties(uint8_t dir); - - // print the available channels through debug - void printChannels(); - - // setup uplink/downlink channel data rates and frequencies - // for dynamic channels, there is a small set of predefined channels - // in case of JoinRequest, add some optional extra frequencies - int16_t setupChannelsDyn(bool joinRequest = false); - - // setup uplink/downlink channel data rates and frequencies - // for fixed bands, we only allow one sub-band at a time to be selected - int16_t setupChannelsFix(uint8_t subBand); - - // a join-accept can piggy-back a set of channels or channel masks - int16_t processCFList(uint8_t* cfList); - - // select a set of random TX/RX channels for up- and downlink - int16_t selectChannels(); - // find the first usable data rate for the given band int16_t findDataRate(uint8_t dr, DataRate_t* dataRate); - // restore all available channels from persistent storage - int16_t restoreChannels(); - - // parse a MAC command queue into a buffer (uplinks) - void macQueueToBuff(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer); - - // parse a MAC buffer into a command queue (downlinks) - void macBufftoQueue(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer, uint8_t len); - - // push MAC command to queue, done by copy - int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue); - - // delete a specific MAC command from queue, indicated by the command ID - // if a payload pointer is supplied, this returns the payload of the MAC command - 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); - - // 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); - - // get the payload length for a specific MAC command - uint8_t getMacPayloadLength(uint8_t cid); - - /*! - \brief Configures CSMA for LoRaWAN as per TR-13, LoRa Alliance. - \param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO. - \param difsSlots Num of CADs to estimate a clear CH. - \param enableCSMA enable/disable CSMA for LoRaWAN. - */ - void setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA = false); - - // Performs CSMA as per LoRa Alliance Technical Recommendation 13 (TR-013). - void performCSMA(); - - // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. - bool performCAD(); - - // function to encrypt and decrypt payloads + // function to encrypt and decrypt payloads (regular uplink/downlink) void processAES(const 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, uint16_t keyLen); + static uint16_t checkSum16(const uint8_t *key, uint16_t keyLen); + + // check the integrity of a buffer using a 16-bit checksum located in the last two bytes of the buffer + static int16_t checkBufferCommon(uint8_t *buffer, uint16_t size); // 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 f67b8573..61de3844 100644 --- a/src/protocols/LoRaWAN/LoRaWANBands.cpp +++ b/src/protocols/LoRaWAN/LoRaWANBands.cpp @@ -20,16 +20,19 @@ const LoRaWANBand_t* LoRaWANBands[RADIOLIB_LORAWAN_NUM_SUPPORTED_BANDS] = { 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 }, + .freqMin = 8630000, + .freqMax = 8700000, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 250, 250, 58, 123, 58, 123, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 868.100, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 868.300, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 868.500, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 8681000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 8683000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 8685000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -42,21 +45,45 @@ const LoRaWANBand_t EU868 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 869.525, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 8695250, .drMin = 0, .drMax = 7, .dr = 0, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 8651000, .drMin = 2, .drMax = 2, .dr = 2, .available = true }, + { .enabled = true, .idx = 1, .freq = 8655000, .drMin = 2, .drMax = 2, .dr = 2, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 8653000, .drMin = 2, .drMax = 2, .dr = 2, .available = true }, + { .enabled = true, .idx = 1, .freq = 8659000, .drMin = 2, .drMax = 2, .dr = 2, .available = true } + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED @@ -66,12 +93,15 @@ 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 }, + .freqMin = 9020000, + .freqMax = 9280000, + .payloadLenMax = { 19, 61, 133, 250, 250, 58, 133, 0, 61, 137, 250, 250, 250, 250, 0 }, .powerMax = 30, .powerNumSteps = 10, .dutyCycle = 0, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -86,46 +116,70 @@ const LoRaWANBand_t US915 = { .txSpans = { { .numChannels = 64, - .freqStart = 902.300, - .freqStep = 0.200, + .freqStart = 9023000, + .freqStep = 2000, .drMin = 0, .drMax = 3, - .joinRequestDataRate = 0 + .drJoinRequest = 0 }, { .numChannels = 8, - .freqStart = 903.000, - .freqStep = 1.600, + .freqStart = 9030000, + .freqStep = 16000, .drMin = 4, .drMax = 4, - .joinRequestDataRate = 4 + .drJoinRequest = 4 } }, .rx1Span = { .numChannels = 8, - .freqStart = 923.300, - .freqStep = 0.600, + .freqStart = 9233000, + .freqStep = 6000, .drMin = 8, .drMax = 13, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .rx1DrTable = { + { 10, 9, 8, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 11, 10, 9, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 12, 11, 10, 9, 0xFF, 0xFF, 0xFF, 0xFF }, + { 13, 12, 11, 10, 0xFF, 0xFF, 0xFF, 0xFF }, + { 13, 13, 12, 11, 0xFF, 0xFF, 0xFF, 0xFF }, + { 10, 9, 8, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 11, 10, 9, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9233000, .drMin = 8, .drMax = 13, .dr = 8, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9167000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9199000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9183000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9215000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } }, - .rx1DataRateBase = 10, - .rx2 = { .enabled = true, .idx = 0, .freq = 923.300, .drMin = 8, .drMax = 8 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; @@ -133,16 +187,19 @@ const LoRaWANBand_t US915 = { 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 }, + .freqMin = 4330000, + .freqMax = 4340000, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 12, .powerNumSteps = 5, .dutyCycle = 36000, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 433.175, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 433.375, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 433.575, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 4331750, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 4333750, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 4335750, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -155,17 +212,41 @@ const LoRaWANBand_t EU433 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 434.665, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 4346650, .drMin = 0, .drMax = 7, .dr = 0, .available = true }, + .txWoR = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -179,12 +260,15 @@ 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 }, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 250, 58, 61, 137, 250, 250, 250, 250, 0 }, .powerMax = 30, .powerNumSteps = 10, .dutyCycle = 0, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = 0, + .txParamSupported = true, // conflict: not implemented according to RP v1.1 .txFreqs = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -199,46 +283,70 @@ const LoRaWANBand_t AU915 = { .txSpans = { { .numChannels = 64, - .freqStart = 915.200, - .freqStep = 0.200, + .freqStart = 9152000, + .freqStep = 2000, .drMin = 0, .drMax = 5, - .joinRequestDataRate = 2 + .drJoinRequest = 2 }, { .numChannels = 8, - .freqStart = 915.900, - .freqStep = 1.600, + .freqStart = 9159000, + .freqStep = 16000, .drMin = 6, .drMax = 6, - .joinRequestDataRate = 6 + .drJoinRequest = 6 } }, .rx1Span = { .numChannels = 8, - .freqStart = 923.300, - .freqStep = 0.600, + .freqStart = 9233000, + .freqStep = 6000, .drMin = 8, .drMax = 13, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .rx1DrTable = { + { 8, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 9, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 10, 9, 8, 8, 8, 8, 0xFF, 0xFF }, + { 11, 10, 9, 8, 8, 8, 0xFF, 0xFF }, + { 12, 11, 10, 9, 8, 8, 0xFF, 0xFF }, + { 13, 12, 11, 10, 9, 8, 0xFF, 0xFF }, + { 13, 13, 12, 11, 10, 9, 0xFF, 0xFF }, + { 9, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9233000, .drMin = 8, .drMax = 13, .dr = 8, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9167000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9199000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9183000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9215000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } }, - .rx1DataRateBase = 8, - .rx2 = { .enabled = true, .idx = 0, .freq = 923.300, .drMin = 8, .drMax = 8 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; @@ -246,12 +354,15 @@ 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 }, + .freqMin = 4700000, + .freqMax = 5100000, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 19, .powerNumSteps = 7, .dutyCycle = 0, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -266,31 +377,55 @@ const LoRaWANBand_t CN500 = { .txSpans = { { .numChannels = 96, - .freqStart = 470.300, - .freqStep = 0.200, + .freqStart = 4703000, + .freqStep = 2000, .drMin = 0, .drMax = 5, - .joinRequestDataRate = 0 + .drJoinRequest = 0 }, RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = { .numChannels = 48, - .freqStart = 500.300, - .freqStep = 0.200, + .freqStart = 5003000, + .freqStep = 2000, .drMin = 0, .drMax = 5, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 1, 1, 1, 1, 1, 0xFF, 0xFF }, + { 2, 1, 1, 1, 1, 1, 0xFF, 0xFF }, + { 3, 2, 1, 1, 1, 1, 0xFF, 0xFF }, + { 4, 3, 2, 1, 1, 1, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 1, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 5053000, .drMin = 0, .drMax = 5, .dr = 0, .available = true }, + .txWoR = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 505.300, .drMin = 0, .drMax = 0 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -306,15 +441,18 @@ 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 }, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 923.200, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 923.400, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9232000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9234000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -328,17 +466,41 @@ const LoRaWANBand_t AS923 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 923.200, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9232000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9236000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9238000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -352,15 +514,18 @@ const LoRaWANBand_t AS923 = { const LoRaWANBand_t AS923_2 = { .bandNum = BandAS923_2, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 921.400, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 921.600, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9214000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9216000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -374,17 +539,41 @@ const LoRaWANBand_t AS923_2 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 921.400, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9214000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9218000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9220000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -398,15 +587,18 @@ const LoRaWANBand_t AS923_2 = { const LoRaWANBand_t AS923_3 = { .bandNum = BandAS923_3, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 916.600, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 916.800, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9166000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9168000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -420,17 +612,41 @@ const LoRaWANBand_t AS923_3 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 916.600, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9166000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9170000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9172000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -444,15 +660,18 @@ const LoRaWANBand_t AS923_3 = { const LoRaWANBand_t AS923_4 = { .bandNum = BandAS923_4, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .freqMin = 9170000, + .freqMax = 9200000, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 917.300, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 917.500, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9173000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9175000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -466,17 +685,41 @@ const LoRaWANBand_t AS923_4 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 917.300, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9173000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9177000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9179000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -490,16 +733,19 @@ const LoRaWANBand_t AS923_4 = { 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 }, + .freqMin = 9209000, + .freqMax = 9233000, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 14, .powerNumSteps = 7, .dutyCycle = 0, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 922.100, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 922.300, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 922.500, .drMin = 0, .drMax = 5} + { .enabled = true, .idx = 0, .freq = 9221000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9223000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 9225000, .drMin = 0, .drMax = 5, .dr = 5, .available = true } }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -512,15 +758,39 @@ const LoRaWANBand_t KR920 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 921.900, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9219000, .drMin = 0, .drMax = 5, .dr = 0, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9227000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 9231000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9229000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 9231000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -536,16 +806,19 @@ 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 }, + .freqMin = 8650000, + .freqMax = 8670000, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 0, 250, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 30, .powerNumSteps = 10, .dutyCycle = 0, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 865.0625, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 865.4025, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 865.9850, .drMin = 0, .drMax = 5} + { .enabled = true, .idx = 0, .freq = 8650625, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 8654025, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 8659850, .drMin = 0, .drMax = 5, .dr = 5, .available = true } }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -558,17 +831,41 @@ const LoRaWANBand_t IN865 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 866.550, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, + { 1, 0, 0, 0, 0, 0, 2, 3 }, + { 2, 1, 0, 0, 0, 0, 3, 4 }, + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 5 }, + { 5, 4, 3, 2, 1, 0, 5, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 8665500, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 8660000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 8667000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 8662000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 8669000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED,