diff --git a/README.md b/README.md index 6d2904bc..dbe288bc 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x * [__POCSAG__](https://www.sigidwiki.com/wiki/POCSAG) using 2-FSK for modules: SX127x, RFM9x, RF69, SX1231, CC1101, nRF24L01, RFM2x and Si443x * [__LoRaWAN__](https://lora-alliance.org/) using LoRa for modules: -SX127x, RFM9x, SX126x and SX128x +SX127x, RFM9x, SX126x and SX128x + * NOTE: LoRaWAN support is currently in beta, feedback via [Issues](https://github.com/jgromes/RadioLib/issues) and [Discussions](https://github.com/jgromes/RadioLib/discussions) is appreciated! ### Supported Arduino platforms: * __Arduino__ diff --git a/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino b/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino index 63dfe2bf..b5eaaf8c 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino @@ -7,14 +7,12 @@ After your device is registered, you can run this example. The device will join the network and start uploading data. - NOTE: LoRaWAN requires storing some parameters persistently! - RadioLib does this by using EEPROM, by default - starting at address 0 and using 32 bytes. - If you already use EEPROM in your application, - you will have to either avoid this range, or change it - by setting a different start address by changing the value of - RADIOLIB_HAL_PERSISTENT_STORAGE_BASE macro, either - during build or in src/BuildOpt.h. + LoRaWAN v1.1 requires the use of EEPROM (persistent storage). + Please refer to the 'persistent' example once you are familiar + with LoRaWAN. + Running this examples REQUIRES you to check "Resets DevNonces" + on your LoRaWAN dashboard. Refer to the network's + documentation on how to do this. For default module settings, see the wiki page https://github.com/jgromes/RadioLib/wiki/Default-configuration @@ -53,13 +51,6 @@ void setup() { while(true); } - // first we need to initialize the device storage - // this will reset all persistently stored parameters - // NOTE: This should only be done once prior to first joining a network! - // After wiping persistent storage, you will also have to reset - // the end device in TTN and perform the join procedure again! - //node.wipe(); - // application identifier - pre-LoRaWAN 1.1.0, this was called appEUI // when adding new end device in TTN, you will have to enter this number // you can pick any number you want, but it has to be unique @@ -87,17 +78,23 @@ void setup() { // and can be set to NULL // some frequency bands only use a subset of the available channels - // you can set the starting channel and their number - // for example, the following corresponds to US915 FSB2 in TTN + // you can select the specific band or set the first channel and last channel + // for example, either of the following corresponds to US915 FSB2 in TTN /* - node.startChannel = 8; - node.numChannels = 8; + node.selectSubband(2); + node.selectSubband(8, 15); */ // now we can start the activation - // this can take up to 20 seconds, and requires a LoRaWAN gateway in range + // this can take up to 10 seconds, and requires a LoRaWAN gateway in range + // a specific starting-datarate can be selected in dynamic bands (e.g. EU868): + /* + uint8_t joinDr = 4; + state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, joinDr); + */ Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... ")); state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); + if(state == RADIOLIB_ERR_NONE) { Serial.println(F("success!")); } else { @@ -106,20 +103,6 @@ void setup() { while(true); } - // after the device has been activated, - // network can be rejoined after device power cycle - // by calling "begin" - /* - Serial.print(F("[LoRaWAN] Resuming previous session ... ")); - state = node.begin(); - if(state == RADIOLIB_ERR_NONE) { - Serial.println(F("success!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - while(true); - } - */ } // counter to keep track of transmitted packets @@ -129,23 +112,10 @@ void loop() { // send uplink to port 10 Serial.print(F("[LoRaWAN] Sending uplink packet ... ")); String strUp = "Hello World! #" + String(count++); - int state = node.uplink(strUp, 10); - if(state == RADIOLIB_ERR_NONE) { - Serial.println(F("success!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - } - - // after uplink, you can call downlink(), - // to receive any possible reply from the server - // this function must be called within a few seconds - // after uplink to receive the downlink! - Serial.print(F("[LoRaWAN] Waiting for downlink ... ")); String strDown; - state = node.downlink(strDown); + int state = node.sendReceive(strUp, 10, strDown); if(state == RADIOLIB_ERR_NONE) { - Serial.println(F("success!")); + Serial.println(F("received a downlink!")); // print data of the packet (if there are any) Serial.print(F("[LoRaWAN] Data:\t\t")); @@ -171,7 +141,7 @@ void loop() { Serial.println(F(" Hz")); } else if(state == RADIOLIB_ERR_RX_TIMEOUT) { - Serial.println(F("timeout!")); + Serial.println(F("no downlink!")); } else { Serial.print(F("failed, code ")); @@ -179,5 +149,5 @@ void loop() { } // wait before sending another packet - delay(10000); + delay(30000); } diff --git a/examples/LoRaWAN/LoRaWAN_End_Device_ABP/LoRaWAN_End_Device_ABP.ino b/examples/LoRaWAN/LoRaWAN_End_Device_ABP/LoRaWAN_End_Device_ABP.ino index 2f1d4f18..05198f5f 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device_ABP/LoRaWAN_End_Device_ABP.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device_ABP/LoRaWAN_End_Device_ABP.ino @@ -8,15 +8,13 @@ The device will start uploading data directly, without having to join the network. - NOTE: LoRaWAN requires storing some parameters persistently! - RadioLib does this by using EEPROM, by default - starting at address 0 and using 32 bytes. - If you already use EEPROM in your application, - you will have to either avoid this range, or change it - by setting a different start address by changing the value of - RADIOLIB_HAL_PERSISTENT_STORAGE_BASE macro, either - during build or in src/BuildOpt.h. - + LoRaWAN v1.1 requires the use of EEPROM (persistent storage). + Please refer to the 'persistent' example once you are familiar + with LoRaWAN. + Running this examples REQUIRES you to check "Resets DevNonces" + on your LoRaWAN dashboard. Refer to the network's + documentation on how to do this. + For default module settings, see the wiki page https://github.com/jgromes/RadioLib/wiki/Default-configuration @@ -54,13 +52,6 @@ void setup() { while(true); } - // first we need to initialize the device storage - // this will reset all persistently stored parameters - // NOTE: This should only be done once prior to first joining a network! - // After wiping persistent storage, you will also have to reset - // the end device in TTN! - //node.wipe(); - // device address - this number can be anything // when adding new end device in TTN, you can generate this number, // or you can set any value you want, provided it is unique @@ -83,16 +74,27 @@ void setup() { // and can be set to NULL // some frequency bands only use a subset of the available channels - // you can set the starting channel and their number - // for example, the following corresponds to US915 FSB2 in TTN + // you can select the specific band or set the first channel and last channel + // for example, either of the following corresponds to US915 FSB2 in TTN /* - node.startChannel = 8; - node.numChannels = 8; + node.selectSubband(2); + node.selectSubband(8, 15); + */ + + // if using EU868 on ABP in TTN, you need to set the SF for RX2 window manually + /* + node.rx2.drMax = 3; + */ + + // to start a LoRaWAN v1.1 session, the user should also provide + // fNwkSIntKey and sNwkSIntKey similar to nwkSKey and appSKey + /* + state = node.beginABP(devAddr, nwkSKey, appSKey, fNwkSIntKey, sNwkSIntKey); */ // start the device by directly providing the encryption keys and device address Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... ")); - state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey); + state = node.beginABP(devAddr, nwkSKey, appSKey); if(state == RADIOLIB_ERR_NONE) { Serial.println(F("success!")); } else { @@ -101,20 +103,6 @@ void setup() { while(true); } - // after the device has been activated, - // network can be rejoined after device power cycle - // by calling "begin" - /* - Serial.print(F("[LoRaWAN] Resuming previous session ... ")); - state = node.begin(); - if(state == RADIOLIB_ERR_NONE) { - Serial.println(F("success!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - while(true); - } - */ } // counter to keep track of transmitted packets @@ -124,23 +112,10 @@ void loop() { // send uplink to port 10 Serial.print(F("[LoRaWAN] Sending uplink packet ... ")); String strUp = "Hello World! #" + String(count++); - int state = node.uplink(strUp, 10); - if(state == RADIOLIB_ERR_NONE) { - Serial.println(F("success!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - } - - // after uplink, you can call downlink(), - // to receive any possible reply from the server - // this function must be called within a few seconds - // after uplink to receive the downlink! - Serial.print(F("[LoRaWAN] Waiting for downlink ... ")); String strDown; - state = node.downlink(strDown); + int state = node.sendReceive(strUp, 10, strDown); if(state == RADIOLIB_ERR_NONE) { - Serial.println(F("success!")); + Serial.println(F("received a downlink!")); // print data of the packet (if there are any) Serial.print(F("[LoRaWAN] Data:\t\t")); @@ -166,7 +141,7 @@ void loop() { Serial.println(F(" Hz")); } else if(state == RADIOLIB_ERR_RX_TIMEOUT) { - Serial.println(F("timeout!")); + Serial.println(F("no downlink!")); } else { Serial.print(F("failed, code ")); @@ -174,5 +149,5 @@ void loop() { } // wait before sending another packet - delay(10000); + delay(30000); } diff --git a/examples/LoRaWAN/LoRaWAN_End_Device_persistent/LoRaWAN_End_Device_persistent.ino b/examples/LoRaWAN/LoRaWAN_End_Device_persistent/LoRaWAN_End_Device_persistent.ino new file mode 100644 index 00000000..43275a7c --- /dev/null +++ b/examples/LoRaWAN/LoRaWAN_End_Device_persistent/LoRaWAN_End_Device_persistent.ino @@ -0,0 +1,145 @@ +/* + RadioLib LoRaWAN End Device Example + + This example assumes you have tried one of the OTAA or ABP + examples and are familiar with the required keys and procedures. + This example restores and saves a session such that you can use + deepsleep or survive power cycles. Before you start, you will + have to register your device at https://www.thethingsnetwork.org/ + and join the network using either OTAA or ABP. + Please refer to one of the other examples for more + information regarding joining a network. + + NOTE: LoRaWAN requires storing some parameters persistently! + RadioLib does this by using EEPROM, by default + starting at address 0 and using 384 bytes. + If you already use EEPROM in your application, + you will have to either avoid this range, or change it + by setting a different start address by changing the value of + RADIOLIB_HAL_PERSISTENT_STORAGE_BASE macro, either + during build or in src/BuildOpt.h. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 radio = new Module(10, 2, 9, 3); + +// create the node instance on the EU-868 band +// using the radio module and the encryption key +// make sure you are using the correct band +// based on your geographical location! +LoRaWANNode node(&radio, &EU868); + +void setup() { + Serial.begin(9600); + + // initialize SX1278 with default settings + Serial.print(F("[SX1278] Initializing ... ")); + int state = radio.begin(); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // first we need to initialize the device storage + // this will reset all persistently stored parameters + // NOTE: This should only be done once prior to first joining a network! + // After wiping persistent storage, you will also have to reset + // the end device in TTN and perform the join procedure again! + // Here, a delay is added to make sure that during re-flashing + // the .wipe() is not triggered and the session is lost + //delay(5000); + //node.wipe(); + + // now we can start the activation + // Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... ")); + // uint64_t joinEUI = 0x12AD1011B0C0FFEE; + // uint64_t devEUI = 0x70B3D57ED005E120; + // uint8_t nwkKey[] = { 0x74, 0x6F, 0x70, 0x53, 0x65, 0x63, 0x72, 0x65, + // 0x74, 0x4B, 0x65, 0x79, 0x31, 0x32, 0x33, 0x34 }; + // uint8_t appKey[] = { 0x61, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, + // 0x6E, 0x74, 0x4B, 0x65, 0x79, 0x41, 0x42, 0x43 }; + // state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); + + // after the device has been activated, + // the session can be restored without rejoining after device power cycle + // on EEPROM-enabled boards by calling "restore" + Serial.print(F("[LoRaWAN] Resuming previous session ... ")); + state = node.restore(); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + +} + +// counter to keep track of transmitted packets +int count = 0; + +void loop() { + // send uplink to port 10 + Serial.print(F("[LoRaWAN] Sending uplink packet ... ")); + String strUp = "Hello World! #" + String(count++); + String strDown; + int state = node.sendReceive(strUp, 10, strDown); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("received a downlink!")); + + // print data of the packet (if there are any) + Serial.print(F("[LoRaWAN] Data:\t\t")); + if(strDown.length() > 0) { + Serial.println(strDown); + } else { + Serial.println(F("")); + } + + // print RSSI (Received Signal Strength Indicator) + Serial.print(F("[LoRaWAN] RSSI:\t\t")); + Serial.print(radio.getRSSI()); + Serial.println(F(" dBm")); + + // print SNR (Signal-to-Noise Ratio) + Serial.print(F("[LoRaWAN] SNR:\t\t")); + 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")); + + } else if(state == RADIOLIB_ERR_RX_TIMEOUT) { + Serial.println(F("no downlink!")); + + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + + // on EEPROM enabled boards, you can save the current session + // by calling "saveSession" which allows retrieving the session after reboot or deepsleep + node.saveSession(); + + // wait before sending another packet + // alternatively, call a deepsleep function here + // make sure to send the radio to sleep as well using radio.sleep() + delay(30000); +} diff --git a/examples/LoRaWAN/LoRaWAN_End_Device_reference/LoRaWAN_End_Device_reference.ino b/examples/LoRaWAN/LoRaWAN_End_Device_reference/LoRaWAN_End_Device_reference.ino new file mode 100644 index 00000000..e72e977c --- /dev/null +++ b/examples/LoRaWAN/LoRaWAN_End_Device_reference/LoRaWAN_End_Device_reference.ino @@ -0,0 +1,216 @@ +/* + RadioLib LoRaWAN End Device Example + + 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/ + After your device is registered, you can run this example. + The device will join the network and start uploading data. + + Also, most of the possible and available functions are + shown here for reference. + + LoRaWAN v1.1 requires the use of EEPROM (persistent storage). + Please refer to the 'persistent' example once you are familiar + with LoRaWAN. + Running this examples REQUIRES you to check "Resets DevNonces" + on your LoRaWAN dashboard. Refer to the network's + documentation on how to do this. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 radio = new Module(10, 2, 9, 3); + +// create the node instance on the EU-868 band +// using the radio module and the encryption key +// make sure you are using the correct band +// based on your geographical location! +LoRaWANNode node(&radio, &EU868); + +void setup() { + Serial.begin(9600); + + // initialize SX1278 with default settings + Serial.print(F("[SX1278] Initializing ... ")); + int state = radio.begin(); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // application identifier - pre-LoRaWAN 1.1.0, this was called appEUI + // when adding new end device in TTN, you will have to enter this number + // you can pick any number you want, but it has to be unique + uint64_t joinEUI = 0x12AD1011B0C0FFEE; + + // device identifier - this number can be anything + // when adding new end device in TTN, you can generate this number, + // or you can set any value you want, provided it is also unique + uint64_t devEUI = 0x70B3D57ED005E120; + + // select some encryption keys which will be used to secure the communication + // there are two of them - network key and application key + // because LoRaWAN uses AES-128, the key MUST be 16 bytes (or characters) long + + // network key is the ASCII string "topSecretKey1234" + uint8_t nwkKey[] = { 0x74, 0x6F, 0x70, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x4B, 0x65, 0x79, 0x31, 0x32, 0x33, 0x34 }; + + // application key is the ASCII string "aDifferentKeyABC" + uint8_t appKey[] = { 0x61, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, + 0x6E, 0x74, 0x4B, 0x65, 0x79, 0x41, 0x42, 0x43 }; + + // prior to LoRaWAN 1.1.0, only a single "nwkKey" is used + // when connecting to LoRaWAN 1.0 network, "appKey" will be disregarded + // and can be set to NULL + + // some frequency bands only use a subset of the available channels + // you can select the specific band or set the first channel and last channel + // for example, either of the following corresponds to US915 FSB2 in TTN + /* + node.selectSubband(2); + node.selectSubband(8, 15); + */ + + // now we can start the activation + // this can take up to 10 seconds, and requires a LoRaWAN gateway in range + // a specific starting-datarate can be selected in dynamic bands (e.g. EU868): + /* + uint8_t joinDr = 4; + state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, joinDr); + */ + Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... ")); + state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); + + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // after the device has been activated, + // the session can be restored without rejoining after device power cycle + // on EEPROM-enabled boards by calling "restore" + /* + Serial.print(F("[LoRaWAN] Resuming previous session ... ")); + state = node.restore(); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + */ + + // disable the ADR algorithm + node.setADR(false); + + // set a fixed datarate + node.setDatarate(5); + + // enable CSMA + // this tries to minimize packet loss by searching for a free channel + // before actually sending an uplink + node.setCSMA(6, 2, true); + +} + +void loop() { + int state = RADIOLIB_ERR_NONE; + + // set battery fill level, + // 0 = external power source + // 1 = lowest (empty battery) + // 254 = highest (full battery) + // 255 = unable to measure + uint8_t battLevel = 146; + node.setDeviceStatus(battLevel); + + // retrieve the last uplink frame counter + uint32_t fcntUp = node.getFcntUp(); + + Serial.print(F("[LoRaWAN] Sending uplink packet ... ")); + String strUp = "Hello World! #" + String(fcntUp); + + // send a confirmed uplink to port 10 every 64th frame + if(fcntUp % 64 == 0) { + state = node.uplink(strUp, 10, true); + } else { + state = node.uplink(strUp, 10); + } + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + + // after uplink, you can call downlink(), + // to receive any possible reply from the server + // this function must be called within a few seconds + // after uplink to receive the downlink! + Serial.print(F("[LoRaWAN] Waiting for downlink ... ")); + String strDown; + state = node.downlink(strDown); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + + // print data of the packet (if there are any) + Serial.print(F("[LoRaWAN] Data:\t\t")); + if(strDown.length() > 0) { + Serial.println(strDown); + } else { + Serial.println(F("")); + } + + // print RSSI (Received Signal Strength Indicator) + Serial.print(F("[LoRaWAN] RSSI:\t\t")); + Serial.print(radio.getRSSI()); + Serial.println(F(" dBm")); + + // print SNR (Signal-to-Noise Ratio) + Serial.print(F("[LoRaWAN] SNR:\t\t")); + 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")); + + } else if(state == RADIOLIB_ERR_RX_TIMEOUT) { + Serial.println(F("timeout!")); + + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + + // on EEPROM enabled boards, you can save the current session + // by calling "saveSession" which allows retrieving the session after reboot or deepsleep + /* + node.saveSession(); + */ + + // wait before sending another packet + delay(30000); +} diff --git a/keywords.txt b/keywords.txt index 4cd39fb4..a791ce51 100644 --- a/keywords.txt +++ b/keywords.txt @@ -124,7 +124,6 @@ setSyncWord KEYWORD2 setOutputPower KEYWORD2 setCurrentLimit KEYWORD2 setPreambleLength KEYWORD2 -invertPreamble KEYWORD2 setGain KEYWORD2 getFrequencyError KEYWORD2 getRSSI KEYWORD2 @@ -222,7 +221,6 @@ spectralScanStart KEYWORD2 spectralScanAbort KEYWORD2 spectralScanGetStatus KEYWORD2 spectralScanGetResult KEYWORD2 -setPaConfig KEYWORD2 # nRF24 setIrqAction KEYWORD2 @@ -291,12 +289,18 @@ setModem KEYWORD2 # LoRaWAN wipe KEYWORD2 -restoreOTAA KEYWORD2 +restore KEYWORD2 beginOTAA KEYWORD2 beginABP KEYWORD2 +saveSession KEYWORD2 uplink KEYWORD2 downlink KEYWORD2 -configureChannel KEYWORD2 +sendReceive KEYWORD2 +setDeviceStatus KEYWORD2 +setDatarate KEYWORD2 +setADR KEYWORD2 +selectSubband KEYWORD2 +setCSMA KEYWORD2 ####################################### # Constants (LITERAL1) @@ -408,4 +412,5 @@ RADIOLIB_ERR_COMMAND_QUEUE_EMPTY LITERAL1 RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND LITERAL1 RADIOLIB_ERR_JOIN_NONCE_INVALID LITERAL1 RADIOLIB_ERR_N_FCNT_DOWN_INVALID LITERAL1 -RADIOLIB_ERR_A_FCNT_DOWN_INVALID LITERAL1 \ No newline at end of file +RADIOLIB_ERR_A_FCNT_DOWN_INVALID LITERAL1 +RADIOLIB_ERR_DATA_RATE_INVALID LITERAL1 \ No newline at end of file diff --git a/src/ArduinoHal.cpp b/src/ArduinoHal.cpp index 86a58f90..0485fad8 100644 --- a/src/ArduinoHal.cpp +++ b/src/ArduinoHal.cpp @@ -58,19 +58,35 @@ void inline ArduinoHal::detachInterrupt(uint32_t interruptNum) { } void inline ArduinoHal::delay(unsigned long ms) { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) ::delay(ms); +#else + ::delay(ms * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif } void inline ArduinoHal::delayMicroseconds(unsigned long us) { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) ::delayMicroseconds(us); +#else + ::delayMicroseconds(us * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif } unsigned long inline ArduinoHal::millis() { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) return(::millis()); +#else + return(::millis() * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif } unsigned long inline ArduinoHal::micros() { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) return(::micros()); +#else + return(::micros() * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif } long inline ArduinoHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) { diff --git a/src/BuildOpt.h b/src/BuildOpt.h index 324a7fbe..5ade1f49 100644 --- a/src/BuildOpt.h +++ b/src/BuildOpt.h @@ -442,7 +442,19 @@ // the amount of space allocated to the persistent storage #if !defined(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE) - #define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0xD0) + #define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x0180) +#endif + +/* + * Uncomment on boards whose clock runs too slow or too fast + * Set the value according to the following scheme: + * Enable timestamps on your terminal + * Print something to terminal, wait 1000 milliseconds, print something again + * If the difference is e.g. 1014 milliseconds between the prints, set this value to 14 + * Or, for more accuracy, wait for 100,000 milliseconds and divide the total drift by 100 + */ +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + //#define RADIOLIB_CLOCK_DRIFT_MS (0) #endif // This only compiles on STM32 boards with SUBGHZ module, but also diff --git a/src/Hal.cpp b/src/Hal.cpp index 6ba08bb7..1b4e818e 100644 --- a/src/Hal.cpp +++ b/src/Hal.cpp @@ -60,14 +60,14 @@ uint32_t RadioLibHal::getPersistentAddr(uint32_t id) { } template -void RadioLibHal::setPersistentParameter(uint32_t id, T val) { +void RadioLibHal::setPersistentParameter(uint32_t id, T val, uint32_t offset) { uint8_t *ptr = (uint8_t*)&val; - this->writePersistentStorage(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE + RadioLibPersistentParamTable[id], ptr, sizeof(T)); + this->writePersistentStorage(RADIOLIB_HAL_PERSISTENT_STORAGE_BASE + RadioLibPersistentParamTable[id] + offset, ptr, sizeof(T)); } -template void RadioLibHal::setPersistentParameter(uint32_t id, uint8_t val); -template void RadioLibHal::setPersistentParameter(uint32_t id, uint16_t val); -template void RadioLibHal::setPersistentParameter(uint32_t id, uint32_t val); +template void RadioLibHal::setPersistentParameter(uint32_t id, uint8_t val, uint32_t offset); +template void RadioLibHal::setPersistentParameter(uint32_t id, uint16_t val, uint32_t offset); +template void RadioLibHal::setPersistentParameter(uint32_t id, uint32_t val, uint32_t offset); template T RadioLibHal::getPersistentParameter(uint32_t id) { diff --git a/src/Hal.h b/src/Hal.h index 9d89811f..780c3483 100644 --- a/src/Hal.h +++ b/src/Hal.h @@ -7,47 +7,59 @@ #include "BuildOpt.h" // list of persistent parameters -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID (0) // this is NOT the LoRaWAN version, but version of this table -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (1) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID (2) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID (3) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID (4) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID (5) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID (6) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID (7) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID (8) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID (9) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID (10) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST_ID (11) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID (12) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID (13) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID (14) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID (15) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_ID (16) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_ID (17) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID (18) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID (0) // this is NOT the LoRaWAN version, but version of this table +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (1) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID (2) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID (3) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID (4) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID (5) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID (6) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID (7) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID (8) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID (9) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID (10) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID (11) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID (12) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID (13) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID (14) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID (15) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID (16) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID (17) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID (18) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID (19) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID (20) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID (21) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID (22) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID (23) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID (24) static const uint32_t RadioLibPersistentParamTable[] = { - 0x00, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION - 0x08, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID + 0x00, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID + 0x01, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID + 0x03, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION + 0x04, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID + 0x05, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID + 0x06, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID + 0x07, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID + 0x0A, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID + 0x0B, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID 0x0C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID 0x10, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID 0x20, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID 0x30, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID 0x40, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID - 0x50, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID - 0x54, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID - 0x58, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID - 0x5C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID - 0x60, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST - 0x70, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID - 0x74, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID - 0x78, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_AFCNT_DOWN_ID - 0x7C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_NFCNT_DOWN_ID - 0x80, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_ID - 0x84, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_ID - 0x88, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID - 0xD0, // end + 0x50, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID + 0x54, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID + 0x58, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID + 0x5C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID + 0x60, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID + 0x64, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID + 0x68, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID + 0x6C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID + 0x70, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID + 0x8E, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID + 0xD0, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID + 0x0180, // end }; /*! @@ -287,9 +299,10 @@ class RadioLibHal { will be stored in the system endian! \param id Parameter ID to save at. \param val Value to set. + \param offset An additional offset added to the address. */ template - void setPersistentParameter(uint32_t id, T val); + void setPersistentParameter(uint32_t id, T val, uint32_t offset = 0); /*! \brief Method to get arbitrary parameter from persistent storage. diff --git a/src/TypeDef.h b/src/TypeDef.h index 0ebcbbb1..2e16145c 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -553,6 +553,11 @@ */ #define RADIOLIB_ERR_A_FCNT_DOWN_INVALID (-1114) +/*! + \brief Datarate requested by user is invalid. +*/ +#define RADIOLIB_ERR_DATA_RATE_INVALID (-1115) + /*! \} */ diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index 7c6f8baa..28e55b79 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -1436,6 +1436,25 @@ uint32_t SX126x::getTimeOnAir(size_t len) { } } +uint32_t SX126x::calculateRxTimeout(uint32_t timeoutUs) { + // the timeout value is given in units of 15.625 microseconds + // the calling function should provide some extra width, as this number of units is truncated to integer + uint32_t timeout = timeoutUs / 15.625; + return(timeout); +} + +int16_t SX126x::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) { + irqFlags = RADIOLIB_SX126X_IRQ_RX_DEFAULT; // flags that can appear in the IRQ register + irqMask = RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; // flags that will trigger DIO0 + return(RADIOLIB_ERR_NONE); +} + +bool SX126x::isRxTimeout() { + uint16_t irq = getIrqStatus(); + bool rxTimedOut = irq & RADIOLIB_SX126X_IRQ_TIMEOUT; + return(rxTimedOut); +} + int16_t SX126x::implicitHeader(size_t len) { return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, len)); } diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index 30900802..0c2f9e20 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -56,7 +56,7 @@ #define RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS 0x8C #define RADIOLIB_SX126X_CMD_SET_CAD_PARAMS 0x88 #define RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS 0x8F -#define RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT 0x0A +#define RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT 0xA0 // status commands #define RADIOLIB_SX126X_CMD_GET_STATUS 0xC0 @@ -219,7 +219,7 @@ #define RADIOLIB_SX126X_IRQ_HEADER_ERR 0b0000000000100000 // 5 5 LoRa header CRC error #define RADIOLIB_SX126X_IRQ_HEADER_VALID 0b0000000000010000 // 4 4 valid LoRa header received #define RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID 0b0000000000001000 // 3 3 valid sync word detected -#define RADIOLIB_SX126X_IRQ_RADIOLIB_PREAMBLE_DETECTED 0b0000000000000100 // 2 2 preamble detected +#define RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED 0b0000000000000100 // 2 2 preamble detected #define RADIOLIB_SX126X_IRQ_RX_DONE 0b0000000000000010 // 1 1 packet received #define RADIOLIB_SX126X_IRQ_TX_DONE 0b0000000000000001 // 0 0 packet transmission completed #define RADIOLIB_SX126X_IRQ_RX_DEFAULT 0b0000001001100010 // 14 0 default for Rx (RX_DONE, TIMEOUT, CRC_ERR and HEADER_ERR) @@ -949,6 +949,27 @@ class SX126x: public PhysicalLayer { */ uint32_t getTimeOnAir(size_t len) override; + /*! + \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) + \param timeoutUs Timeout in microseconds to listen for + \returns Timeout value in a unit that is specific for the used module + */ + uint32_t calculateRxTimeout(uint32_t timeoutUs); + + /*! + \brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks + \param irqFlags The flags for which IRQs must be triggered + \param irqMask Mask indicating which IRQ triggers a DIO + \returns \ref status_codes + */ + int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); + + /*! + \brief Check whether the IRQ bit for RxTimeout is set + \returns \ref RxTimeout IRQ is set + */ + bool isRxTimeout(); + /*! \brief Set implicit header mode for future reception/transmission. \param len Payload length in bytes. diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index 38fb6979..02a2d378 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -432,10 +432,20 @@ int16_t SX127x::startReceive(uint8_t len, uint8_t mode) { return(setMode(mode)); } -int16_t SX127x::startReceive(uint32_t mode, uint16_t irqFlags, uint16_t irqMask, size_t len) { +int16_t SX127x::startReceive(uint32_t timeout, uint16_t irqFlags, uint16_t irqMask, size_t len) { (void)irqFlags; (void)irqMask; - return(startReceive((uint8_t)len, (uint8_t)mode)); + uint8_t mode = RADIOLIB_SX127X_RXCONTINUOUS; + if(timeout != 0) { + // for non-zero timeout value, change mode to Rx single and set the timeout + mode = RADIOLIB_SX127X_RXSINGLE; + uint8_t msb_sym = (timeout > 0x3FF) ? 0x3 : (uint8_t)(timeout >> 8); + uint8_t lsb_sym = (timeout > 0x3FF) ? 0xFF : (uint8_t)(timeout & 0xFF); + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, msb_sym, 1, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYMB_TIMEOUT_LSB, lsb_sym); + RADIOLIB_ASSERT(state); + } + return(startReceive((uint8_t)len, mode)); } void SX127x::setDio0Action(void (*func)(void), uint32_t dir) { @@ -1221,28 +1231,45 @@ int16_t SX127x::variablePacketLengthMode(uint8_t maxLen) { return(SX127x::setPacketMode(RADIOLIB_SX127X_PACKET_VARIABLE, maxLen)); } +float SX127x::getNumSymbols(size_t len) { + // get symbol length in us + float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + + // get Low Data Rate optimization flag + float de = 0; + if (symbolLength >= 16.0) { + de = 1; + } + + // get explicit/implicit header enabled flag + float ih = (float) this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, 0, 0); + + // get CRC enabled flag + float crc = (float) (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, 2, 2) >> 2); + + // get number of preamble symbols + float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB)); + + // get number of payload symbols + float n_pay = 8.0 + RADIOLIB_MAX(ceil((8.0 * (float) len - 4.0 * (float) this->spreadingFactor + 28.0 + 16.0 * crc - 20.0 * ih) / (4.0 * (float) this->spreadingFactor - 8.0 * de)) * (float) this->codingRate, 0.0); + + // add 4.25 symbols for the sync + return(n_pre + n_pay + 4.25f); +} + uint32_t SX127x::getTimeOnAir(size_t len) { // check active modem uint8_t modem = getActiveModem(); if (modem == RADIOLIB_SX127X_LORA) { - // Get symbol length in us + // get symbol length in us float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; - // Get Low Data Rate optimization flag - float de = 0; - if (symbolLength >= 16.0) { - de = 1; - } - // Get explicit/implicit header enabled flag - float ih = (float) this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, 0, 0); - // Get CRC enabled flag - float crc = (float) (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, 2, 2) >> 2); - // Get number of bits preamble - float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB)); - // Get number of bits payload - float n_pay = 8.0 + RADIOLIB_MAX(ceil((8.0 * (float) len - 4.0 * (float) this->spreadingFactor + 28.0 + 16.0 * crc - 20.0 * ih) / (4.0 * (float) this->spreadingFactor - 8.0 * de)) * (float) this->codingRate, 0.0); + + // get number of symbols + float n_sym = getNumSymbols(len); // Get time-on-air in us - return ceil(symbolLength * (n_pre + n_pay + 4.25)) * 1000; + return ceil((double)symbolLength * (double)n_sym) * 1000; + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { // Get number of bits preamble float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB_FSK)) * 8; @@ -1267,6 +1294,28 @@ uint32_t SX127x::getTimeOnAir(size_t len) { } +uint32_t SX127x::calculateRxTimeout(uint32_t timeoutUs) { + // the timeout is given as the number of symbols + // the calling function should provide some extra width, as this number of symbols is truncated to integer + // the order of operators is swapped here to decrease the effects of this truncation error + float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + uint32_t numSymbols = (timeoutUs / symbolLength) / 1000; + return(numSymbols); +} + +int16_t SX127x::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) { + // IRQ flags/masks are inverted to what seems logical for SX127x (0 being activated, 1 being deactivated) + irqFlags = RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DEFAULT; + irqMask = RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DONE & RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_TIMEOUT; + return(RADIOLIB_ERR_NONE); +} + +bool SX127x::isRxTimeout() { + uint16_t irq = getIRQFlags(); + bool rxTimedOut = irq & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT; + return(rxTimedOut); +} + int16_t SX127x::setCrcFiltering(bool enable) { this->crcOn = enable; diff --git a/src/modules/SX127x/SX127x.h b/src/modules/SX127x/SX127x.h index 59fc29a5..fa4ab65b 100644 --- a/src/modules/SX127x/SX127x.h +++ b/src/modules/SX127x/SX127x.h @@ -160,6 +160,7 @@ #define RADIOLIB_SX127X_MASK_IRQ_FLAG_CAD_DONE 0b11111011 // 2 2 CAD complete #define RADIOLIB_SX127X_MASK_IRQ_FLAG_FHSS_CHANGE_CHANNEL 0b11111101 // 1 1 FHSS change channel #define RADIOLIB_SX127X_MASK_IRQ_FLAG_CAD_DETECTED 0b11111110 // 0 0 valid LoRa signal detected during CAD operation +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DEFAULT 0b00011111 // 7 0 default for Rx (RX_TIMEOUT, RX_DONE, CRC_ERR) // RADIOLIB_SX127X_REG_FIFO_TX_BASE_ADDR #define RADIOLIB_SX127X_FIFO_TX_BASE_ADDR_MAX 0b00000000 // 7 0 allocate the entire FIFO buffer for TX only @@ -824,13 +825,16 @@ class SX127x: public PhysicalLayer { /*! \brief Interrupt-driven receive method, implemented for compatibility with PhysicalLayer. - \param mode Receive mode to be used. + \param timeout Receive mode type and/or raw timeout value in symbols. + When set to 0, the timeout will be infinite and the device will remain + in Rx mode until explicitly commanded to stop (Rx continuous mode). + When non-zero (maximum 1023), the device will be set to Rx single mode and timeout will be set. \param irqFlags Ignored. \param irqMask Ignored. \param len Expected length of packet to be received. Required for LoRa spreading factor 6. \returns \ref status_codes */ - int16_t startReceive(uint32_t mode, uint16_t irqFlags, uint16_t irqMask, size_t len); + int16_t startReceive(uint32_t timeout, uint16_t irqFlags, uint16_t irqMask, size_t len); /*! \brief Reads data that was received after calling startReceive method. When the packet length is not known in advance, @@ -1041,6 +1045,13 @@ class SX127x: public PhysicalLayer { */ int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK); + /*! + \brief Convert from bytes to LoRa symbols. + \param len Payload length in bytes. + \returns The total number of LoRa symbols, including preamble, sync and possible header. + */ + float getNumSymbols(size_t len); + /*! \brief Get expected time-on-air for a given size of payload. \param len Payload length in bytes. @@ -1048,6 +1059,27 @@ class SX127x: public PhysicalLayer { */ uint32_t getTimeOnAir(size_t len) override; + /*! + \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) + \param timeoutUs Timeout in microseconds to listen for + \returns Timeout value in a unit that is specific for the used module + */ + uint32_t calculateRxTimeout(uint32_t timeoutUs); + + /*! + \brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks + \param irqFlags The flags for which IRQs must be triggered + \param irqMask Mask indicating which IRQ triggers a DIO + \returns \ref status_codes + */ + int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); + + /*! + \brief Check whether the IRQ bit for RxTimeout is set + \returns \ref RxTimeout IRQ is set + */ + bool isRxTimeout(); + /*! \brief Enable CRC filtering and generation. \param enable Set or unset CRC filtering and generation. diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 30ffe5ee..fc0b3164 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -1,51 +1,40 @@ #include "LoRaWAN.h" - #include #if !defined(RADIOLIB_EXCLUDE_LORAWAN) -// flag to indicate whether we have received a downlink -static volatile bool downlinkReceived = false; - #if defined(RADIOLIB_EEPROM_UNSUPPORTED) #warning "Persistent storage not supported!" #endif +// flag to indicate whether there was some action during Rx mode (timeout or downlink) +static volatile bool downlinkAction = false; + // interrupt service routine to handle downlinks automatically #if defined(ESP8266) || defined(ESP32) IRAM_ATTR #endif -static void LoRaWANNodeOnDownlink(void) { - downlinkReceived = true; +static void LoRaWANNodeOnDownlinkAction(void) { + downlinkAction = true; } -// flag to indicate whether channel scan operation is complete -static volatile bool scanFlag = false; - -// interrupt service routine to handle downlinks automatically -#if defined(ESP8266) || defined(ESP32) - IRAM_ATTR -#endif -static void LoRaWANNodeOnChannelScan(void) { - scanFlag = 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) { this->phyLayer = phy; this->band = band; - this->FSK = false; - this->startChannel = -1; - this->numChannels = -1; - this->backupFreq = this->band->backupChannel.freqStart; + this->rx2 = this->band->rx2; this->difsSlots = 2; this->backoffMax = 6; this->enableCSMA = false; - -} - -void LoRaWANNode::wipe() { - Module* mod = this->phyLayer->getMod(); - mod->hal->wipePersistentStorage(); } void LoRaWANNode::setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA) { @@ -54,133 +43,195 @@ void LoRaWANNode::setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA this->enableCSMA = enableCSMA; } - -int16_t LoRaWANNode::restoreOTAA() { - int16_t state = this->setPhyProperties(); - RADIOLIB_ASSERT(state); - - // check the magic value +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) +void LoRaWANNode::wipe() { Module* mod = this->phyLayer->getMod(); - if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) { - // the magic value is not set, user will have to do perform the join procedure - return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + mod->hal->wipePersistentStorage(); +} + +int16_t LoRaWANNode::restore() { + // if already joined, ignore + if(this->isJoinedFlag) { + return(RADIOLIB_ERR_NONE); } - // in case of future revisions of NVM, use a version parameter to allow transitioning from one version to another while keeping session alive - uint16_t nvm_table_version = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID); - // if (RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION > nvm_table_version) { + Module* mod = this->phyLayer->getMod(); + + uint8_t nvm_table_version = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID); + // if (RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION > nvm_table_version) { // // set default values for variables that are new or something // } (void)nvm_table_version; - // pull all needed information from persistent storage + // check the magic value + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) { + #if defined(RADIOLIB_DEBUG) + RADIOLIB_DEBUG_PRINTLN("magic id not set (no saved session)"); + RADIOLIB_DEBUG_PRINTLN("first 16 bytes of NVM:"); + uint8_t nvmBuff[16]; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(0), nvmBuff, 16); + RADIOLIB_DEBUG_HEXDUMP(nvmBuff, 16); + #endif + // the magic value is not set, user will have to do perform the join procedure + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + // pull all authentication keys from persistent storage this->devAddr = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); - RADIOLIB_DEBUG_PRINTLN("appSKey:"); - RADIOLIB_DEBUG_HEXDUMP(this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); - uint32_t dlSettings = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID); - this->rev = (dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) >> 7; - uint8_t rx1DrOffset = (dlSettings & 0x70) >> 4; - uint8_t rx2DataRate = dlSettings & 0x0F; - RADIOLIB_DEBUG_PRINTLN("LoRaWAN revision: %d", this->rev); + // get session parameters + this->rev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID); + RADIOLIB_DEBUG_PRINTLN("LoRaWAN session: v1.%d", this->rev); + this->devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); + this->joinNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); - // TODO process the RX2 data rate - (void)rx2DataRate; - - // TODO process the data rate offset - (void)rx1DrOffset; - - // parse Rx1 delay (and subsequently Rx2) - this->rxDelays[0] = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID); + // get MAC state + uint8_t txDrRx2Dr = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID); + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = (txDrRx2Dr >> 4) & 0x0F; + + this->txPwrCur = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID); + + uint8_t rx1DrOffDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID); + this->rx1DrOffset = (rx1DrOffDel >> 4) & 0x0F; + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] + this->band->rx1DataRateBase + this->rx1DrOffset; + this->rxDelays[0] = ((rx1DrOffDel >> 0) & 0x0F) * 1000; if(this->rxDelays[0] == 0) { this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS; } - this->rxDelays[1] = this->rxDelays[0] + 1000; + this->rxDelays[1] = this->rxDelays[0] + 1000; + + uint8_t rx2FreqBuf[3]; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); + uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); + this->rx2.drMax = (txDrRx2Dr >> 0) & 0x0F; + this->rx2.freq = (float)rx2Freq / 10000.0; + + uint8_t adrLimDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID); + this->adrLimitExp = (adrLimDel >> 4) & 0x0F; + this->adrDelayExp = (adrLimDel >> 0) & 0x0F; + + this->nbTrans = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID); - // process CFlist if any bit is non-zero - uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 }; - uint8_t allZero[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 }; - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST_ID), cfList, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN); - RADIOLIB_DEBUG_PRINTLN("cfList:"); - RADIOLIB_DEBUG_HEXDUMP(cfList, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN); - if(memcmp(cfList, allZero, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) { - if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { - // list of frequencies - for(uint8_t i = 0; i < 5; i++) { - uint32_t freq = LoRaWANNode::ntoh(&cfList[3*i], 3); - availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = (float)freq/10000.0; - availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i] = availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]; - RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", i, availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]); - } - - } else { - // frequency mask, we need to find out which frequencies are actually being used - uint8_t channelId = 0; - uint8_t chSpan = 0; - uint8_t chNum = 0; - for(uint8_t i = 0; i < 5; i++) { - uint16_t mask = LoRaWANNode::ntoh(&cfList[2*i]); - RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", i, mask); - for(uint8_t j = 0; j < 16; j++) { - if(chNum >= this->band->defaultChannels[chSpan].numChannels) { - chNum -= this->band->defaultChannels[chSpan].numChannels; - chSpan++; - - if(chSpan >= this->band->numChannelSpans) { - RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!"); - return(RADIOLIB_ERR_UNKNOWN); - } - } - - if(mask & (1UL << j)) { - RADIOLIB_DEBUG_PRINTLN("chNum = %d, chSpan = %d", chNum, chSpan); - uint8_t dir = this->band->defaultChannels[chSpan].direction; - float freq = this->band->defaultChannels[chSpan].freqStart + chNum*this->band->defaultChannels[chSpan].freqStep; - availableChannelsFreq[dir][channelId] = freq; - RADIOLIB_DEBUG_PRINTLN("Channel %cL %d frequency = %f MHz", dir ? 'U': 'D', channelId, availableChannelsFreq[dir][channelId]); - channelId++; - } - - chNum++; - } - } - - } - } + this->aFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID); + this->nFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID); + this->confFcntUp = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID); + this->confFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID); + this->adrFcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID); + + // fcntUp is stored in highly efficient wear-leveling system, so parse it as required + this->restoreFcntUp(); uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); - memcpy(&queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); - - state = this->setupChannels(); - RADIOLIB_ASSERT(state); + memcpy(&this->commandsUp, queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + RADIOLIB_DEBUG_PRINTLN("Number of MAC commands: %d", this->commandsUp.numCommands); + int16_t state = this->restoreChannels(); + RADIOLIB_ASSERT(state); + + state = this->setPhyProperties(); + RADIOLIB_ASSERT(state); + + // full session is restored, so set joined flag + this->isJoinedFlag = true; + return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) { +int16_t LoRaWANNode::restoreFcntUp() { + Module* mod = this->phyLayer->getMod(); + + uint8_t fcntBuffStart = mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID); + uint8_t fcntBuffEnd = mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID + 1); + uint8_t buffSize = fcntBuffEnd - fcntBuffStart; + uint8_t fcntBuff[buffSize] = { 0 }; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID), fcntBuff, buffSize); + + // copy the two most significant bytes from the first two bytes + uint32_t bits_30_22 = (uint32_t)fcntBuff[0]; + uint32_t bits_22_14 = (uint32_t)fcntBuff[1]; + + // the next 7 bits must be retrieved from the byte to which was written most recently + // this is the last byte that has its state bit (most significant bit) set equal to its predecessor + // we find the first byte that has its state bit different, and subtract one + uint8_t idx = 2; + uint8_t state = fcntBuff[idx] >> 7; + for(; idx < 5; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + uint32_t bits_14_7 = (uint32_t)fcntBuff[idx-1] & 0x7F; + + // equally, the last 7 bits must be retrieved from the byte to which was written most recently + // this is the last byte that has its state bit (most significant bit) set equal to its predecessor + // we find the first byte that has its state bit different, and subtract one + idx = 5; + state = fcntBuff[idx] >> 7; + for(; idx < buffSize; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + uint32_t bits_7_0 = (uint32_t)fcntBuff[idx-1] & 0x7F; + + this->fcntUp = (bits_30_22 << 22) | (bits_22_14 << 14) | (bits_14_7 << 7) | bits_7_0; + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::restoreChannels() { + uint8_t bytesPerChannel = 5; + uint8_t numBytes = 2 * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel; + uint8_t buffer[numBytes]; + Module* mod = this->phyLayer->getMod(); + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes); + for(uint8_t dir = 0; dir < 2; dir++) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t chBuff[bytesPerChannel] = { 0 }; + memcpy(chBuff, &buffer[(dir * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel) + i * bytesPerChannel], bytesPerChannel); + this->availableChannels[dir][i].enabled = (chBuff[0] & 0x80) >> 7; + this->availableChannels[dir][i].idx = chBuff[0] & 0x7F; + uint32_t freq = LoRaWANNode::ntoh(&chBuff[1], 3); + this->availableChannels[dir][i].freq = (float)freq/10000.0; + this->availableChannels[dir][i].drMax = (chBuff[4] & 0xF0) >> 4; + this->availableChannels[dir][i].drMin = (chBuff[4] & 0x0F) >> 0; + } + } + return(RADIOLIB_ERR_NONE); +} +#endif + +int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, uint8_t joinDr, bool force) { // check if we actually need to send the join request Module* mod = this->phyLayer->getMod(); - if(!force && (mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) { + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + if(!force && (mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) { // the device has joined already, we can just pull the data from persistent storage - return(this->restoreOTAA()); + return(this->restore()); } +#endif // set the physical layer configuration + this->txPwrCur = this->band->powerMax; int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); // setup uplink/downlink frequencies and datarates - state = this->setupChannels(); + state = this->selectChannelsJR(this->devNonce, joinDr); RADIOLIB_ASSERT(state); - // get dev nonce from persistent storage and increment it - uint16_t devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1); + // configure for uplink with default configuration + state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); + RADIOLIB_ASSERT(state); + + // increment devNonce as we are sending another join-request + this->devNonce += 1; // build the join-request message uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; @@ -189,7 +240,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI); LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI); - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce); + LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], this->devNonce); // add the authentication code uint32_t mic = this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), nwkKey); @@ -197,46 +248,17 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // send it state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_DEBUG_PRINTLN("Join-request sent <-- Rx Delay start"); RADIOLIB_ASSERT(state); - // configure for downlink with default configuration - state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); + // 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; + + // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received + state = downlinkCommon(); RADIOLIB_ASSERT(state); - - // set the function that will be called when the reply is received - this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); - - // downlink messages are sent with inverted IQ - // TODO use downlink() for this - if(!this->FSK) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } - - // start receiving - uint32_t start = mod->hal->millis(); - downlinkReceived = false; - state = this->phyLayer->startReceive(); - RADIOLIB_ASSERT(state); - - // wait for the reply or timeout - while(!downlinkReceived) { - if(mod->hal->millis() - start >= RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS + 2000) { - downlinkReceived = false; - if(!this->FSK) { - this->phyLayer->invertIQ(false); - } - return(RADIOLIB_ERR_RX_TIMEOUT); - } - } - - // we have a message, reset the IQ inversion - downlinkReceived = false; - this->phyLayer->clearPacketReceivedAction(); - if(!this->FSK) { - state = this->phyLayer->invertIQ(false); - RADIOLIB_ASSERT(state); - } // build the buffer for the reply data uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; @@ -273,20 +295,22 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe RADIOLIB_DEBUG_PRINTLN("joinAcceptMsg:"); RADIOLIB_DEBUG_HEXDUMP(joinAcceptMsg, lenRx); - // get current JoinNonce from downlink and previous JoinNonce from NVM - uint32_t joinNonce = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); - uint32_t joinNoncePrev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); - RADIOLIB_DEBUG_PRINTLN("JoinNoncePrev: %d, JoinNonce: %d", joinNoncePrev, joinNonce); - - // JoinNonce received must be greater than the last JoinNonce heard, else error - if(joinNonce <= joinNoncePrev) { + // get current JoinNonce from downlink and previous JoinNonce from persistent storage + uint32_t joinNonceNew = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); + + RADIOLIB_DEBUG_PRINTLN("JoinNoncePrev: %d, JoinNonce: %d", this->joinNonce, joinNonceNew); + // JoinNonce received must be greater than the last JoinNonce heard, else error + if((this->joinNonce > 0) && (joinNonceNew <= this->joinNonce)) { return(RADIOLIB_ERR_JOIN_NONCE_INVALID); } + this->joinNonce = joinNonceNew; // check LoRaWAN revision (the MIC verification depends on this) uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS]; this->rev = (dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) >> 7; RADIOLIB_DEBUG_PRINTLN("LoRaWAN revision: 1.%d", this->rev); + this->rx1DrOffset = (dlSettings & 0x70) >> 4; + this->rx2.drMax = dlSettings & 0x0F; // verify MIC if(this->rev == 1) { @@ -301,7 +325,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE; LoRaWANNode::hton(&micBuff[1], joinEUI); - LoRaWANNode::hton(&micBuff[9], devNonce); + LoRaWANNode::hton(&micBuff[9], this->devNonce); memcpy(&micBuff[11], joinAcceptMsg, lenRx); if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) { @@ -315,17 +339,9 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe } } - uint8_t rx1DrOffset = (dlSettings & 0x70) >> 4; - uint8_t rx2DataRate = dlSettings & 0x0F; - // TODO process the RX2 data rate - (void)rx2DataRate; - - // TODO process the data rate offset - (void)rx1DrOffset; - // parse other contents - uint32_t homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); + this->homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); this->devAddr = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]); // parse Rx1 delay (and subsequently Rx2) @@ -336,56 +352,13 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe this->rxDelays[1] = this->rxDelays[0] + 1000; // process CFlist if present - uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 }; if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) { + 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); - if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { - // list of frequencies - for(uint8_t i = 0; i < 5; i++) { - uint32_t freq = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + 3*i], 3); - availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = (float)freq/10000.0; - availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i] = availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]; - RADIOLIB_DEBUG_PRINT("Channel UL/DL %d frequency = ", i); - RADIOLIB_DEBUG_PRINT_FLOAT(availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i], 3); - RADIOLIB_DEBUG_PRINTLN(" MHz"); - } - - } else { - // frequency mask, we need to find out which frequencies are actually being used - uint8_t channelId = 0; - uint8_t chSpan = 0; - uint8_t chNum = 0; - for(uint8_t i = 0; i < 5; i++) { - uint16_t mask = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + 2*i]); - RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", i, mask); - for(uint8_t j = 0; j < 16; j++) { - if(chNum >= this->band->defaultChannels[chSpan].numChannels) { - chNum -= this->band->defaultChannels[chSpan].numChannels; - chSpan++; - - if(chSpan >= this->band->numChannelSpans) { - RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!"); - return(RADIOLIB_ERR_UNKNOWN); - } - } - - if(mask & (1UL << j)) { - RADIOLIB_DEBUG_PRINTLN("chNum = %d, chSpan = %d", chNum, chSpan); - uint8_t dir = this->band->defaultChannels[chSpan].direction; - float freq = this->band->defaultChannels[chSpan].freqStart + chNum*this->band->defaultChannels[chSpan].freqStep; - availableChannelsFreq[dir][channelId] = freq; - RADIOLIB_DEBUG_PRINT("Channel %cL %d frequency = ", dir ? 'U': 'D', channelId); - RADIOLIB_DEBUG_PRINT_FLOAT(availableChannelsFreq[dir][channelId], 3); - RADIOLIB_DEBUG_PRINTLN(" MHz"); - channelId++; - } - - chNum++; - } - } - - } - } + this->setupChannels(cfList); + } else { + this->setupChannels(nullptr); + } // prepare buffer for key derivation uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; @@ -395,7 +368,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe if(this->rev == 1) { // 1.1 version, derive the keys LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], this->devNonce); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(appKey); @@ -416,17 +389,17 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // enqueue the RekeyInd MAC command to be sent in the next uplink LoRaWANMacCommand_t cmd = { .cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY, - .len = sizeof(uint8_t), .payload = { this->rev }, - .repeat = RADIOLIB_LORAWAN_ADR_ACK_LIMIT, + .len = sizeof(uint8_t), + .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], homeNetId, 3); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], this->homeNetId, 3); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], this->devNonce); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); @@ -440,36 +413,50 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe } - // save the device address - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr); + // 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; - // update the keys +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // save the device address & keys as well as JoinAccept values; these are only ever set when joining + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); - // save uplink parameters - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID, joinNonce); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID, homeNetId); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID, this->rxDelays[0]); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID, (uint32_t)dlSettings); + // save join-request parameters + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID, this->homeNetId); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID, this->joinNonce); - // save cfList (all 0 if none is present) - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST_ID), cfList, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN); + this->saveSession(); + this->saveChannels(); - // all complete, reset device counters and set the magic number - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, 0); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); + // everything written to NVM, write current table version to persistent storage and set magic number + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); +#endif + + this->isJoinedFlag = true; - // everything written to NVM, write current version to NVM - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION); return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey) { +int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, bool force) { + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // check if we actually need to restart from a clean session + Module* mod = this->phyLayer->getMod(); + if(!force && (mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) { + // the device has joined already, we can just pull the data from persistent storage + return(this->restore()); + } +#endif + this->devAddr = addr; memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); memcpy(this->nwkSEncKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE); @@ -484,45 +471,223 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, } // set the physical layer configuration + this->txPwrCur = this->band->powerMax; int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); // setup uplink/downlink frequencies and datarates - state = this->setupChannels(); + state = this->setupChannels(nullptr); RADIOLIB_ASSERT(state); - // everything written to NVM, write current version to NVM - Module* mod = this->phyLayer->getMod(); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION); +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // save the device address & keys + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); + + this->saveSession(); + this->saveChannels(); + + // everything written to NVM, write current table version to persistent storage and set magic number + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); +#endif + + this->isJoinedFlag = true; return(RADIOLIB_ERR_NONE); } +bool LoRaWANNode::isJoined() { + return(this->isJoinedFlag); +} + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) +int16_t LoRaWANNode::saveSession() { + Module* mod = this->phyLayer->getMod(); + + // store session configuration (MAC commands) + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID) != this->rev) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID, this->rev); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID) != this->devNonce) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, this->devNonce); + + uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID) != txDrRx2Dr) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID) != this->txPwrCur) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID, this->txPwrCur); + + uint8_t rx1DrOffDel = (this->rx1DrOffset << 4) | (this->rxDelays[0] / 1000); + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID) != rx1DrOffDel) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); + + uint8_t rx2FreqBuf[3]; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); + uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); + if(rx2Freq != uint32_t(this->rx2.freq * 10000)) { + rx2Freq = uint32_t(this->rx2.freq * 10000); + LoRaWANNode::hton(&rx2FreqBuf[0], rx2Freq, 3); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); + } + + uint8_t adrLimDel = (this->adrLimitExp << 4) | (this->adrDelayExp << 0); + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID) != adrLimDel) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID, adrLimDel); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID) != this->nbTrans) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID, this->nbTrans); + + // store all frame counters + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID) != this->aFcntDown) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, this->aFcntDown); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID) != this->nFcntDown) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, this->nFcntDown); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID) != this->confFcntUp) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID, this->confFcntUp); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID) != this->confFcntDown) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID, this->confFcntDown); + + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID) != this->adrFcnt) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID, this->adrFcnt); + + // fcntUp is saved using highly efficient wear-leveling as this is by far going to be written most often + this->saveFcntUp(); + + // if there is, or was, any MAC command in the queue, overwrite with the current MAC queue + uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)] = { 0 }; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + LoRaWANMacCommandQueue_t cmdTemp; + memcpy(&cmdTemp, queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + if(this->commandsUp.numCommands > 0 || cmdTemp.numCommands > 0) { + memcpy(queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::saveFcntUp() { + Module* mod = this->phyLayer->getMod(); + + uint8_t fcntBuff[30] = { 0 }; + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID), fcntBuff, 30); + + // we discard the first two bits - your flash will likely be far dead by the time you reach 2^30 uplinks + // the first two bytes of the remaining 30 bytes are stored straight into storage without additional wear leveling + // because they hardly ever change + uint8_t bits_30_22 = (uint8_t)(this->fcntUp >> 22); + if(fcntBuff[0] != bits_30_22) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_30_22, 0); + uint8_t bits_22_14 = (uint8_t)(this->fcntUp >> 14); + if(fcntBuff[1] != bits_22_14) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_22_14, 1); + + // the next 7 bits are stored into one of few indices + // this index is indicated by the first byte that has its state (most significant bit) different from its predecessor + // if all have an equal state, restart from the beginning + // always flip the state bit of the byte that we write to, to indicate that this is the most recently written byte + uint8_t idx = 2; + uint8_t state = fcntBuff[idx] >> 7; + for(; idx < 5; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + // check if the last written byte is equal to current, only rewrite if different + uint8_t bits_14_7 = (this->fcntUp >> 7) & 0x7F; + if((fcntBuff[idx - 1] & 0x7F) != bits_14_7) { + // find next index to write + idx = idx < 5 ? idx : 2; + + // flip the first bit of this byte to indicate that we just wrote here + bits_14_7 |= (~(fcntBuff[idx] >> 7)) << 7; + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_14_7, idx); + } + + // equally, the last 7 bits are stored into one of many indices + // this index is indicated by the first byte that has its state (most significant bit) different from its predecessor + // if all have an equal state, restart from the beginning + // always flip the state bit of the byte that we write to, to indicate that this is the most recently written byte + idx = 5; + state = fcntBuff[idx] >> 7; + for(; idx < 30; idx++) { + if(fcntBuff[idx] >> 7 != state) { + break; + } + } + idx = idx < 30 ? idx : 5; + uint8_t bits_7_0 = (this->fcntUp >> 0) & 0x7F; + + // flip the first bit of this byte to indicate that we just wrote here + bits_7_0 |= (~(fcntBuff[idx] >> 7)) << 7; + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_7_0, idx); + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::saveChannels() { + uint8_t bytesPerChannel = 5; + uint8_t numBytes = 2 * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel; + uint8_t buffer[numBytes]; + for(uint8_t dir = 0; dir < 2; dir++) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t chBuff[bytesPerChannel] = { 0 }; + chBuff[0] = (uint8_t)this->availableChannels[dir][i].enabled << 7; + chBuff[0] |= this->availableChannels[dir][i].idx; + uint32_t freq = this->availableChannels[dir][i].freq*10000.0; + LoRaWANNode::hton(&chBuff[1], freq, 3); + chBuff[4] = this->availableChannels[dir][i].drMax << 4; + chBuff[4] |= this->availableChannels[dir][i].drMin << 0; + memcpy(&buffer[(dir * RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS * bytesPerChannel) + i * bytesPerChannel], chBuff, bytesPerChannel); + } + } + Module* mod = this->phyLayer->getMod(); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes); + return(RADIOLIB_ERR_NONE); +} +#endif // RADIOLIB_EEPROM_UNSUPPORTED + + #if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::uplink(String& str, uint8_t port) { - return(this->uplink(str.c_str(), port)); +int16_t LoRaWANNode::uplink(String& str, uint8_t port, bool isConfirmed) { + return(this->uplink(str.c_str(), port, isConfirmed)); } #endif -int16_t LoRaWANNode::uplink(const char* str, uint8_t port) { - return(this->uplink((uint8_t*)str, strlen(str), port)); +int16_t LoRaWANNode::uplink(const char* str, uint8_t port, bool isConfirmed) { + return(this->uplink((uint8_t*)str, strlen(str), port, isConfirmed)); } -int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { +int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed) { + 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) { + // not enough time elapsed since the last uplink, we may still be in an Rx window + return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); + } + // check destination port if(port > 0xDF) { return(RADIOLIB_ERR_INVALID_PORT); } // port 0 is only allowed for MAC-only payloads if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - if (!isMACPayload) { + if (!this->isMACPayload) { return(RADIOLIB_ERR_INVALID_PORT); } // if this is MAC only payload, continue and reset for next uplink - isMACPayload = false; + this->isMACPayload = false; } - - Module* mod = this->phyLayer->getMod(); // check if there are some MAC commands to piggyback (only when piggybacking onto a application-frame) uint8_t foptsLen = 0; @@ -534,21 +699,61 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { } // check maximum payload len as defined in phy - if(len > this->band->payloadLenMax[this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]) { + if(len > this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]) { return(RADIOLIB_ERR_PACKET_TOO_LONG); } + // increase frame counter by one + this->fcntUp += 1; + + // check if we need to do ADR stuff + uint32_t adrLimit = 0x01 << this->adrLimitExp; + uint32_t adrDelay = 0x01 << this->adrDelayExp; + bool adrAckReq = false; + if((this->fcntUp - this->adrFcnt) >= adrLimit) { + adrAckReq = true; + } + if ((this->fcntUp - this->adrFcnt) == (adrLimit + adrDelay)) { + // try one of three, in order: set TxPower to max, set DR to min, enable all defined channels + + // set the maximum power supported by both the module and the band + int8_t pwr = this->band->powerMax; + int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; + while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { + // go from the highest power in band and lower it until we hit one supported by the module + state = this->phyLayer->setOutputPower(pwr--); + } + RADIOLIB_ASSERT(state); + if(pwr == this->txPwrCur) { + + // failed to increase Tx power, so try to decrease the datarate + if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMin) { + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]--; + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]--; + } else { + + // failed to decrease datarate, so enable all available channels + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled = true; + } + } + } + + } else { + this->txPwrCur = pwr; + } + + // we tried something to improve the range, so increase the ADR frame counter by 'ADR delay' + this->adrFcnt += adrDelay; + } + // configure for uplink - // TODO select randomly from available channels + this->selectChannels(); int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); RADIOLIB_ASSERT(state); - // check if sufficient time has elapsed since the last uplink - if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) { - // not enough time elapsed since the last uplink, we may still be in an RX window - return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); - } - // build the uplink message // the first 16 bytes are reserved for MIC calculation blocks size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsBufSize); @@ -559,49 +764,67 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { #endif // set the packet fields - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + if(isConfirmed) { + uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; + this->confFcntUp = this->fcntUp; + } else { + uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; + } + uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); - // TODO implement adaptive data rate // 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; + } + } - // get frame counter from persistent storage - uint32_t fcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1; - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt); - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt); + // if the saved confirm-fcnt is set, set the ACK bit + bool isConfirmingDown; + if(this->confFcntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + isConfirmingDown = true; + uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; + } + (void)isConfirmingDown; + + LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fcntUp); // check if we have some MAC commands to append if(foptsLen > 0) { - uint8_t foptsNum = this->commandsUp.numCommands; uint8_t foptsBuff[foptsBufSize]; - size_t idx = 0; - for (size_t i = 0; i < foptsNum; i++) { - LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, }; - popMacCommand(&cmd, &this->commandsUp, i); - if (cmd.cid == 0) { + uint8_t* foptsPtr = foptsBuff; + + // append all MAC replies into fopts buffer + size_t i = 0; + for (; i < this->commandsUp.numCommands; i++) { + LoRaWANMacCommand_t cmd = this->commandsUp.commands[i]; + memcpy(foptsPtr, &cmd, 1 + cmd.len); + foptsPtr += cmd.len + 1; + } + RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands); + RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsLen); + + // pop the commands from back to front + for (; i >= 0; i--) { + if(this->commandsUp.commands[i].repeat > 0) { + this->commandsUp.commands[i].repeat--; + } else { + deleteMacCommand(this->commandsUp.commands[i].cid, &this->commandsUp); + } + if(i == 0) { break; } - foptsBuff[idx] = cmd.cid; - for(size_t i = 1; i < cmd.len; i++) { - foptsBuff[idx + i] = cmd.payload[i]; - } - idx += cmd.len + 1; } - RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum); - RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize); - uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen); uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen; // encrypt it - processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); + processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fcntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); - // write the current MAC command queue to nvm for next uplink - uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)]; - memcpy(&queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); } // set the port @@ -614,22 +837,23 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { } // encrypt the frame payload - // TODO check ctrId --> erratum says it should be 0x01? - processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); + processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], this->fcntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); // create blocks for MIC calculation uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt); + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fcntUp); block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = uplinkMsgLen - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); - // TODO implement confirmed frames - block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; - block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; + 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_PRINTLN("uplinkMsg pre-MIC:"); RADIOLIB_DEBUG_HEXDUMP(uplinkMsg, uplinkMsgLen); @@ -648,25 +872,142 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF); } + RADIOLIB_DEBUG_PRINTLN("uplinkMsg:"); + RADIOLIB_DEBUG_HEXDUMP(uplinkMsg, uplinkMsgLen); + // perform CSMA if enabled. if (enableCSMA) { performCSMA(); } - RADIOLIB_DEBUG_PRINTLN("uplinkMsg:"); - RADIOLIB_DEBUG_HEXDUMP(uplinkMsg, uplinkMsgLen); - // send it (without the MIC calculation blocks) - uint32_t txStart = mod->hal->millis(); - uint32_t timeOnAir = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000; state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS); + + // set the timestamp so that we can measure when to start receiving + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_DEBUG_PRINTLN("Uplink sent <-- Rx Delay start"); + #if !defined(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; + + // LoRaWANEvent: + // dir = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK + // confirmed = isConfirmed + // confirming = isConfirmingDown + // power = this->txPwrCur + // fcnt = this->fcntUp + // port = port + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::downlinkCommon() { + Module* mod = this->phyLayer->getMod(); + const uint32_t scanGuard = 10; + + // 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 + if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[0] - scanGuard)) { + // if between start of Rx1 and end of Rx2, wait until Rx2 closes + if(mod->hal->millis() - this->rxDelayStart < this->rxDelays[1]) { + mod->hal->delay(this->rxDelays[1] + this->rxDelayStart - mod->hal->millis()); + } + // update the end timestamp in case user got stuck between uplink and downlink + this->rxDelayEnd = mod->hal->millis(); + return(RADIOLIB_ERR_NO_RX_WINDOW); + } + + // configure for downlink + int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); + RADIOLIB_ASSERT(state); + + // downlink messages are sent with inverted IQ + if(!this->FSK) { + state = this->phyLayer->invertIQ(true); + RADIOLIB_ASSERT(state); + } + + // create the masks that are required for receiving downlinks + uint16_t irqFlags = 0x0000; + uint16_t irqMask = 0x0000; + this->phyLayer->irqRxDoneRxTimeout(irqFlags, irqMask); + + this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction); + + // perform listening in the two Rx windows + for(uint8_t i = 0; i < 2; i++) { + downlinkAction = false; + + // calculate the Rx timeout + // according to the spec, this must be at least enough time to effectively detect a preamble + // but pad it a bit on both sides (start and end) to make sure it is wide enough + uint32_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000; + uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(timeoutHost); + + // wait for the start of the Rx window + // the waiting duration is shortened a bit to cover any possible timing errors + uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart); + if(waitLen > scanGuard) { + waitLen -= scanGuard; + } + mod->hal->delay(waitLen); + + // open Rx window by starting receive with specified timeout + state = this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0); + RADIOLIB_DEBUG_PRINTLN("Opening Rx%d window (%d us timeout)... <-- Rx Delay end ", i+1, timeoutHost); + + // wait for the timeout to complete (and a small additional delay) + mod->hal->delay(timeoutHost / 1000 + scanGuard / 2); + RADIOLIB_DEBUG_PRINTLN("closing"); + + // check if the IRQ bit for Rx Timeout is set + if(!this->phyLayer->isRxTimeout()) { + break; + + } else if(i == 0) { + // nothing in the first window, configure for the second + this->phyLayer->standby(); + state = this->phyLayer->setFrequency(this->rx2.freq); + RADIOLIB_ASSERT(state); + + DataRate_t dataRate; + findDataRate(this->rx2.drMax, &dataRate); + 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(this->phyLayer->isRxTimeout()) { + this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? + if(!this->FSK) { + this->phyLayer->invertIQ(false); + } + + return(RADIOLIB_ERR_RX_TIMEOUT); + } + + // wait for the DIO to fire indicating a downlink is received + while(!downlinkAction) { + mod->hal->yield(); + } + + // 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? + this->phyLayer->clearPacketReceivedAction(); + if(!this->FSK) { + state = this->phyLayer->invertIQ(false); + RADIOLIB_ASSERT(state); + } - // set the timestamp so that we can measure when to start receiving - this->rxDelayStart = txStart + timeOnAir; return(RADIOLIB_ERR_NONE); } @@ -694,138 +1035,14 @@ int16_t LoRaWANNode::downlink(String& str) { #endif int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { - // check if there are any upcoming Rx windows - Module* mod = this->phyLayer->getMod(); - const uint32_t scanGuard = 500; - if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) { - // time since last Tx is greater than RX2 delay + some guard period - // we have nothing to downlink - return(RADIOLIB_ERR_NO_RX_WINDOW); - } - - // configure for downlink - int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); + + // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received + int16_t state = downlinkCommon(); RADIOLIB_ASSERT(state); - // downlink messages are sent with inverted IQ - if(!this->FSK) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } - - // calculate the channel scanning timeout - // according to the spec, this must be at least enough time to effectively detect a preamble - uint32_t scanTimeout = this->phyLayer->getTimeOnAir(0)/1000; - - // set up everything for channel scan - downlinkReceived = false; - scanFlag = false; - bool packetDetected = false; - this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan); - - // perform listening in the two Rx windows - for(uint8_t i = 0; i < 2; i++) { - // wait for the start of the Rx window - // the waiting duration is shortened a bit to cover any possible timing errors - uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart); - if(waitLen > scanGuard) { - waitLen -= scanGuard; - } - mod->hal->delay(waitLen); - - // wait until we get a preamble - uint32_t scanStart = mod->hal->millis(); - while((mod->hal->millis() - scanStart) < (scanTimeout + scanGuard)) { - // check channel detection timeout - state = this->phyLayer->startChannelScan(); - RADIOLIB_ASSERT(state); - - // wait with some timeout, though it should not be hit - uint32_t cadStart = mod->hal->millis(); - while(!scanFlag) { - mod->hal->yield(); - if(mod->hal->millis() - cadStart >= 3000) { - // timed out, stop waiting - break; - } - } - - // check the scan result - scanFlag = false; - state = this->phyLayer->getChannelScanResult(); - if((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { - packetDetected = true; - break; - } - - } - - // check if we have a packet - if(packetDetected) { - break; - - } else if(i == 0) { - // nothing in the first window, configure for the second - state = this->phyLayer->setFrequency(this->backupFreq); - RADIOLIB_ASSERT(state); - - DataRate_t dataRate; - findDataRate(RADIOLIB_LORAWAN_DATA_RATE_UNUSED, &dataRate, &this->band->backupChannel); - state = this->phyLayer->setDataRate(dataRate); - RADIOLIB_ASSERT(state); - - } - - } - - // check if we received a packet at all - if(!packetDetected) { - this->phyLayer->standby(); - if(!this->FSK) { - this->phyLayer->invertIQ(false); - } - - // restore the original uplink channel - this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); - - return(RADIOLIB_ERR_RX_TIMEOUT); - } - - // channel scan is finished, swap the actions - this->phyLayer->clearChannelScanAction(); - downlinkReceived = false; - this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); - - // start receiving - state = this->phyLayer->startReceive(); - RADIOLIB_ASSERT(state); - - // wait for reception with some timeout - uint32_t rxStart = mod->hal->millis(); - while(!downlinkReceived) { - mod->hal->yield(); - // let's hope 30 seconds is long enough timeout - if(mod->hal->millis() - rxStart >= 30000) { - // timed out - this->phyLayer->standby(); - if(!this->FSK) { - this->phyLayer->invertIQ(false); - } - return(RADIOLIB_ERR_RX_TIMEOUT); - } - } - - // we have a message, clear actions, go to standby and reset the IQ inversion - downlinkReceived = false; - this->phyLayer->standby(); - this->phyLayer->clearPacketReceivedAction(); - if(!this->FSK) { - state = this->phyLayer->invertIQ(false); - RADIOLIB_ASSERT(state); - } - // get the packet length size_t downlinkMsgLen = this->phyLayer->getPacketLength(); + RADIOLIB_DEBUG_PRINTLN("Downlink message length: %d", downlinkMsgLen); // check the minimum required frame length // an extra byte is subtracted because downlink frames may not have a port @@ -843,7 +1060,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { #endif // set the MIC calculation block - // TODO implement confirmed frames memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); @@ -866,10 +1082,17 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { } // get the frame counter and set it to the MIC calculation block - // TODO cache the ADR bit? uint16_t fcnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt16); - uint32_t fcnt32 = fcnt16; // calculate possible rollover once decided if this is network downlink or application downlink + + // if this downlink is confirming an uplink, its MIC was generated with the least-significant 16 bits of that fcntUp + // TODO get this to the user somehow + bool isConfirmingUp = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK) && (this->rev == 1)) { + isConfirmingUp = true; + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFcntUp); + } + (void)isConfirmingUp; RADIOLIB_DEBUG_PRINTLN("downlinkMsg:"); RADIOLIB_DEBUG_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); @@ -886,32 +1109,31 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { // check the FcntDown value (Network or Application) uint32_t fcntDownPrev = 0; if (isAppDownlink) { - fcntDownPrev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID); + fcntDownPrev = this->aFcntDown; } else { - fcntDownPrev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID); + fcntDownPrev = this->nFcntDown; } - // assume a 16-bit to 32-bit rollover when difference in LSB is smaller than MAX_FCNT_GAP + RADIOLIB_DEBUG_PRINTLN("fcnt: %d, fcntPrev: %d, isAppDownlink: %d", fcnt16, fcntDownPrev, (int)isAppDownlink); + + // if this is not the first downlink... + // assume a 16-bit to 32-bit rollover if difference between counters in LSB is smaller than MAX_FCNT_GAP // if that isn't the case and the received fcnt is smaller or equal to the last heard fcnt, then error - if ((fcnt16 <= fcntDownPrev) && ((0xFFFF - (uint16_t)fcntDownPrev + fcnt16) > RADIOLIB_LORAWAN_MAX_FCNT_GAP)) { - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] downlinkMsg; - #endif - if (isAppDownlink) { - return(RADIOLIB_ERR_A_FCNT_DOWN_INVALID); - } else { - return(RADIOLIB_ERR_N_FCNT_DOWN_INVALID); + uint32_t fcnt32 = fcnt16; + if(fcntDownPrev > 0) { + if((fcnt16 <= fcntDownPrev) && ((0xFFFF - (uint16_t)fcntDownPrev + fcnt16) > RADIOLIB_LORAWAN_MAX_FCNT_GAP)) { + #if !defined(RADIOLIB_STATIC_ONLY) + delete[] downlinkMsg; + #endif + if (isAppDownlink) { + return(RADIOLIB_ERR_A_FCNT_DOWN_INVALID); + } else { + return(RADIOLIB_ERR_N_FCNT_DOWN_INVALID); + } + } else if (fcnt16 <= fcntDownPrev) { + uint16_t msb = (fcntDownPrev >> 16) + 1; // assume a rollover + fcnt32 |= ((uint32_t)msb << 16); // add back the MSB part } - } else if (fcnt16 <= fcntDownPrev) { - uint16_t msb = (fcntDownPrev >> 16) + 1; // assume a rollover - fcnt32 |= (msb << 16); // add back the MSB part - } - - // save current fcnt to NVM - if (isAppDownlink) { - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, fcnt32); - } else { - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, fcnt32); } // check the MIC @@ -921,6 +1143,21 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { #endif return(RADIOLIB_ERR_CRC_MISMATCH); } + + // save current fcnt to respective frame counter + if (isAppDownlink) { + this->aFcntDown = fcnt32; + } else { + this->nFcntDown = fcnt32; + } + + // if this is a confirmed frame, save the downlink number (only app frames can be confirmed) + bool isConfirmedDown = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { + this->confFcntDown = this->aFcntDown; + isConfirmedDown = true; + } + (void)isConfirmedDown; // check the address uint32_t addr = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]); @@ -950,8 +1187,8 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { while(remLen > 0) { LoRaWANMacCommand_t cmd = { .cid = *foptsPtr, - .len = (uint8_t)(remLen - 1), .payload = { 0 }, + .len = (uint8_t)(remLen - 1), .repeat = 0, }; memcpy(cmd.payload, foptsPtr + 1, cmd.len); @@ -967,39 +1204,52 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { // if FOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink if(this->commandsUp.len > 15) { - uint8_t foptsNum = this->commandsUp.numCommands; size_t foptsBufSize = this->commandsUp.len; uint8_t foptsBuff[foptsBufSize]; - size_t idx = 0; - for(size_t i = 0; i < foptsNum; i++) { - LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, }; - popMacCommand(&cmd, &this->commandsUp, i); - if(cmd.cid == 0) { - break; - } - foptsBuff[idx] = cmd.cid; - for(size_t i = 1; i < cmd.len; i++) { - foptsBuff[idx + i] = cmd.payload[i]; - } - idx += cmd.len + 1; + uint8_t* foptsPtr = foptsBuff; + // append all MAC replies into fopts buffer + size_t i = 0; + for (; i < this->commandsUp.numCommands; i++) { + LoRaWANMacCommand_t cmd = this->commandsUp.commands[i]; + memcpy(foptsPtr, &cmd, 1 + cmd.len); + foptsPtr += cmd.len + 1; } - RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum); + RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands); RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize); - isMACPayload = true; + // pop the commands from back to front + for (; i >= 0; i--) { + if(this->commandsUp.commands[i].repeat > 0) { + this->commandsUp.commands[i].repeat--; + } else { + deleteMacCommand(this->commandsUp.commands[i].cid, &this->commandsUp); + } + if(i == 0) { + break; + } + } + + this->isMACPayload = true; this->uplink(foptsBuff, foptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); - uint8_t strDown[this->band->payloadLenMax[this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]]; + uint8_t strDown[this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]]; size_t lenDown = 0; state = this->downlink(strDown, &lenDown); RADIOLIB_ASSERT(state); } - // write the MAC command queue to nvm for next uplink - uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)]; - memcpy(&queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t)); } + // a downlink was received, so reset the ADR counter to this uplink's fcnt + this->adrFcnt = this->fcntUp; + + // LoRaWANEvent: + // dir = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK + // confirmed = isConfirmedDown + // confirming = isConfirmingUp + // power = this->txPwrCur + // fcnt = isAppDownlink ? this->aFcntDown : this->nFcntDown + // port = ... + // process payload (if there is any) if(payLen <= 0) { // no payload @@ -1014,14 +1264,46 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { // there is payload, and so there should be a port too // TODO pass the port? *len = payLen - 1; + // 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 - // TODO does the erratum hold here as well? - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], payLen - 1, this->appSKey, data, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); #if !defined(RADIOLIB_STATIC_ONLY) delete[] downlinkMsg; #endif + return(RADIOLIB_ERR_NONE); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t port, String& strDown, bool isConfirmed) { + // send the uplink + int16_t state = this->uplink(strUp, port, isConfirmed); + RADIOLIB_ASSERT(state); + + // wait for the downlink + state = this->downlink(strDown); + return(state); +} +#endif + +int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t port, uint8_t* dataDown, size_t* lenDown, bool isConfirmed) { + // send the uplink + int16_t state = this->uplink(strUp, port, isConfirmed); + RADIOLIB_ASSERT(state); + + // wait for the downlink + state = this->downlink(dataDown, lenDown); + return(state); +} + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t port, uint8_t* dataDown, size_t* lenDown, bool isConfirmed) { + // send the uplink + int16_t state = this->uplink(dataUp, lenUp, port, isConfirmed); + RADIOLIB_ASSERT(state); + + // wait for the downlink + state = this->downlink(dataDown, lenDown); return(state); } @@ -1029,6 +1311,10 @@ void LoRaWANNode::setDeviceStatus(uint8_t battLevel) { this->battLevel = battLevel; } +uint32_t LoRaWANNode::getFcntUp() { + return(this->fcntUp); +} + uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { if((msg == NULL) || (len == 0)) { return(0); @@ -1060,30 +1346,15 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { int16_t LoRaWANNode::setPhyProperties() { // set the physical layer configuration - int16_t state = RADIOLIB_ERR_NONE; - if(this->FSK) { - // for FSK, configure the channel - state = this->phyLayer->setFrequency(this->band->fskFreq); - RADIOLIB_ASSERT(state); - DataRate_t dr; - dr.fsk.bitRate = 50; - dr.fsk.freqDev = 25; - state = this->phyLayer->setDataRate(dr); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); - } - RADIOLIB_ASSERT(state); - + // set the maximum power supported by both the module and the band - int8_t pwr = this->band->powerMax; - state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; + int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { // go from the highest power in band and lower it until we hit one supported by the module - state = this->phyLayer->setOutputPower(pwr--); + state = this->phyLayer->setOutputPower(this->txPwrCur--); } RADIOLIB_ASSERT(state); + this->txPwrCur++; uint8_t syncWord[3] = { 0 }; uint8_t syncWordLen = 0; @@ -1109,67 +1380,296 @@ int16_t LoRaWANNode::setPhyProperties() { return(state); } -int16_t LoRaWANNode::setupChannels() { - // find appropriate channel IDs for uplink and downlink, the uplink channel is random - int8_t chMin = -1; - int8_t chMax = -1; - if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_MASK) { - chMin = this->startChannel; - chMax = this->startChannel + this->numChannels; - } - int16_t state = this->findChannelId(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - &this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], - &this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], chMin, chMax); - RADIOLIB_ASSERT(state); - - // RX1 channel is not random, but determined by uplink channel - if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { - // for frequency-list type bands, it's just the previous uplink channel - this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; - this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; +int16_t LoRaWANNode::setupChannels(uint8_t* cfList) { + size_t num = 0; + + // in case of frequency list-type band, copy the default TX channels into the available channels, with RX1 = TX + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // copy the default defined channels into the first slots + for(; num < 3 && this->band->txFreqs[num].enabled; num++) { + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = this->band->txFreqs[num]; + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = this->band->txFreqs[num]; + RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].idx, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); + } + // if there is a cflist present, parse its frequencies into the next five slots, with datarate range copied from default channel 0 + if(cfList != nullptr) { + for(uint8_t i = 0; i < 5; i++, num++) { + LoRaWANChannel_t chnl; + chnl.enabled = true; + chnl.idx = num; + uint32_t freq = LoRaWANNode::ntoh(&cfList[3*i], 3); + chnl.freq = (float)freq/10000.0; + chnl.drMin = this->band->txFreqs[0].drMin; // drMin is equal for all channels + chnl.drMax = this->band->txFreqs[0].drMax; // drMax is equal for all channels + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = chnl; + RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", chnl.idx, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); + } + } + for(; num < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; num++) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } - } else { - // for mask type bands, it's the uplink mod num_downlink_channels - for(uint8_t i = 0; i < this->band->numChannelSpans; i++) { - const LoRaWANChannelSpan_t* span = &this->band->defaultChannels[i]; - if(span->direction == RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK) { - this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] % span->numChannels; - this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = span->joinRequestDataRate; - break; + } else { // RADIOLIB_LORAWAN_BAND_FIXED + if(cfList != nullptr) { + uint8_t chSpan = 0; + uint8_t chNum = 0; + // in case of mask-type bands, copy those frequencies that are masked true into the available TX channels + for(size_t chMaskCntl = 0; chMaskCntl < 5; chMaskCntl++) { + uint16_t mask = LoRaWANNode::ntoh(&cfList[2*chMaskCntl]); + RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, mask); + for(size_t i = 0; i < 16; i++) { + // if we must roll over to next span, reset chNum and move to next channel span + if(chNum >= this->band->txSpans[chSpan].numChannels) { + chNum = 0; + chSpan++; + } + + if(mask & (1UL << i)) { + if(chSpan >= this->band->numTxSpans) { + RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!"); + return(RADIOLIB_ERR_UNKNOWN); + } + LoRaWANChannel_t chnl; + chnl.enabled = true; + chnl.idx = chMaskCntl*16 + i; + chnl.freq = this->band->txSpans[chSpan].freqStart + chNum*this->band->txSpans[chSpan].freqStep; + chnl.drMin = this->band->txSpans[chSpan].drMin; + chnl.drMax = this->band->txSpans[chSpan].drMax; + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; + // downlink channels are dynamically calculated on each uplink in selectChannels() + RADIOLIB_DEBUG_PRINTLN("Channel UL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); + num++; + } + chNum++; + } + } + for(; chNum < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; chNum++) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chNum] = RADIOLIB_LORAWAN_CHANNEL_NONE; } } } + return(RADIOLIB_ERR_NONE); +} - // based on the channel IDs, find the frequencies - state = this->findChannelFreq(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], - &this->channelFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]); - RADIOLIB_ASSERT(state); - state = this->findChannelFreq(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK], - &this->channelFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]); - RADIOLIB_ASSERT(state); - - // configure channel for uplink - state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); +int16_t LoRaWANNode::selectSubband(uint8_t idx) { + int16_t state = this->selectSubband((idx - 1) * 8, idx * 8 - 1); return(state); } -uint8_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate, const LoRaWANChannelSpan_t* span) { - uint8_t dataRateBand = 0; - uint8_t dataRateFound = 0; - if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) { - if(span->dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - dataRateBand = span->dataRates[i]; - dataRateFound = i; +int16_t LoRaWANNode::selectSubband(uint8_t startChannel, uint8_t endChannel) { + if(this->isJoinedFlag) { + RADIOLIB_DEBUG_PRINTLN("There is already an active session - cannot change subband"); + return(RADIOLIB_ERR_INVALID_CHANNEL); + } + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + RADIOLIB_DEBUG_PRINTLN("This is a dynamic band plan which does not support subbands"); + return(RADIOLIB_ERR_INVALID_CHANNEL); + } + + uint8_t numChannels = endChannel - startChannel + 1; + if(startChannel > this->band->txSpans[0].numChannels) { + RADIOLIB_DEBUG_PRINTLN("There are only %d channels available in this band", this->band->txSpans[0].numChannels); + return(RADIOLIB_ERR_INVALID_CHANNEL); + } + if(startChannel + numChannels > this->band->txSpans[0].numChannels) { + numChannels = this->band->txSpans[0].numChannels - startChannel; + RADIOLIB_DEBUG_PRINTLN("Could only select %d channels due to end of band", numChannels); + } + if(numChannels > RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { + numChannels = RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; + RADIOLIB_DEBUG_PRINTLN("Could only select %d channels due to specified limit", numChannels); + } + + LoRaWANChannel_t chnl; + for(size_t chNum = 0; chNum < numChannels; chNum++) { + chnl.enabled = true; + chnl.idx = startChannel + chNum; + chnl.freq = this->band->txSpans[0].freqStart + chnl.idx*this->band->txSpans[0].freqStep; + chnl.drMin = this->band->txSpans[0].drMin; + chnl.drMax = this->band->txSpans[0].drMax; + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chNum] = chnl; + // downlink channel is dynamically calculated on each uplink in selectChannels() + RADIOLIB_DEBUG_PRINTLN("Channel UL %d frequency = %f MHz", chNum, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chNum].freq); + } + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t joinDr) { + LoRaWANChannel_t channelUp; + LoRaWANChannel_t channelDown; + uint8_t drUp; + uint8_t drDown; + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // count the number of available channels for a join-request (default channels + join-request channels) + uint8_t numJRChannels = 0; + for(size_t i = 0; i < 3; i++) { + if(this->band->txFreqs[i].enabled) { + numJRChannels++; + } + if(this->band->txJoinReq[i].enabled) { + numJRChannels++; + } + } + + // cycle through the available channels (seed with devNonce) + uint8_t channelId = devNonce % numJRChannels; + + // find the channel whose index is selected + for(size_t i = 0; i < 3; i++) { + if(this->band->txFreqs[i].idx == channelId) { + channelUp = this->band->txFreqs[i]; + break; + } + if(this->band->txJoinReq[i].idx == channelId) { + channelUp = this->band->txJoinReq[i]; + } + } + + // if join datarate is user-specified and valid, select that value; otherwise use + if(joinDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + if(joinDr >= channelUp.drMin && joinDr <= channelUp.drMax) { + drUp = joinDr; + } else { + RADIOLIB_DEBUG_PRINTLN("Datarate %d is not valid (min: %d, max %d) - using default", joinDr, channelUp.drMin, channelUp.drMax); + joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + } + } + if(joinDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + drUp = int((channelUp.drMax + channelUp.drMin) / 2); + } + + // derive the downlink channel and datarate from the uplink channel and datarate + channelDown = channelUp; + drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, channelDown.drMin, channelDown.drMax); + + } else { // RADIOLIB_LORAWAN_BAND_FIXED + uint8_t spanID = 0; + uint8_t channelID = 0; + uint8_t numEnabledChannels = 0; + // if there are any predefined channels because user selected a subband, select one of these channels + for(; numEnabledChannels < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; numEnabledChannels++) { + if(this->availableChannels[numEnabledChannels][RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].enabled == false) { break; } } - } else { - dataRateBand = span->dataRates[dr]; - dataRateFound = dr; + if(numEnabledChannels > 0) { + uint8_t channelID = this->phyLayer->random(numEnabledChannels); + channelUp = this->availableChannels[channelID][RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; + spanID = channelUp.idx / this->band->txSpans[0].numChannels; + channelID = channelUp.idx; + + } else { // no pre-selected subband, cycle through size-8 (or size-9) blocks + channelUp.enabled = true; + uint8_t numBlocks = this->band->txSpans[0].numChannels / 8; // calculate number of 8-channel blocks + uint8_t numBlockChannels = 8 + (this->band->numTxSpans == 2 ? 1 : 0); // add a 9th channel if there's a second span + uint8_t blockID = devNonce % numBlocks; // currently selected block (seed with devNonce) + channelID = this->phyLayer->random(numBlockChannels); // select randomly from these 8 or 9 channels + RADIOLIB_DEBUG_PRINTLN("blocks: %d, channels/block: %d, blockID: %d, channelID: %d", numBlocks, numBlockChannels, blockID, channelID); + + // if channel 0-7 is selected, retrieve this channel from span 0; otherwise span 1 + if(channelID < 8) { + spanID = 0; + channelUp.idx = blockID * 8 + channelID; + } else { + spanID = 1; + channelUp.idx = blockID; + } + channelUp.freq = this->band->txSpans[spanID].freqStart + channelUp.idx*this->band->txSpans[spanID].freqStep; + } + + // for fixed channel plans, the user-specified datarate is ignored and span-specific value must be used + drUp = this->band->txSpans[spanID].joinRequestDataRate; + + // derive the downlink channel and datarate from the uplink channel and datarate + channelDown.enabled = true; + channelDown.idx = channelID % this->band->rx1Span.numChannels; + channelDown.freq = this->band->rx1Span.freqStart + channelDown.idx*this->band->rx1Span.freqStep; + channelDown.drMin = this->band->rx1Span.drMin; + channelDown.drMax = this->band->rx1Span.drMax; + drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, channelDown.drMin, channelDown.drMax); + } + this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = channelUp; + this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDown; + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp; + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown; + + return(RADIOLIB_ERR_NONE); +} + +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++; + } + } else { + break; + } + } + if(numChannels == 0) { + RADIOLIB_DEBUG_PRINTLN("There are no channels defined - are you in ABP mode with no defined subband?"); + return(RADIOLIB_ERR_INVALID_CHANNEL); + } + // 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] = availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][channelID]; + + 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] = availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][channelID]; + + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // for fixed bands, the downlink channel is the uplink channel ID `modulo` number of downlink channels + LoRaWANChannel_t channelDn; + channelDn.enabled = true; + channelDn.idx = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_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; + + } + 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; + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::setDatarate(uint8_t drUp) { + // find the minimum and maximum available datarates by checking the enabled uplink channels + uint8_t drMin = RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; + uint8_t drMax = 0; + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { + drMin = RADIOLIB_MIN(drMin, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin); + drMax = RADIOLIB_MAX(drMax, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax); + } + } + if((drUp < drMin) || (drUp > drMax)) { + RADIOLIB_DEBUG_PRINTLN("Cannot configure DR %d (min: %d, max: %d)", drUp, drMin, drMax); + return(RADIOLIB_ERR_DATA_RATE_INVALID); + } + this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp; + + RADIOLIB_DEBUG_PRINTLN("Configured DR up = %d", drUp); + + return(RADIOLIB_ERR_NONE); +} + +void LoRaWANNode::setADR(bool enable) { + this->adrEnabled = enable; +} + +int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { + uint8_t dataRateBand = this->band->dataRates[dr]; if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { dataRate->fsk.bitRate = 50; @@ -1193,165 +1693,38 @@ uint8_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate, const LoRaWA dataRate->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6; dataRate->lora.codingRate = (dataRateBand & 0x03) + 5; + RADIOLIB_DEBUG_PRINTLN("DR %d: LORA (SF: %d, BW: %f, CR: %d)", + dataRateBand, dataRate->lora.spreadingFactor, dataRate->lora.bandwidth, dataRate->lora.codingRate); } - return(dataRateFound); -} - -int16_t LoRaWANNode::findChannelId(uint8_t dir, uint8_t* ch, uint8_t* dr, int8_t min, int8_t max) { - // find the first channel span that supports the requested direction - uint8_t spanId = 0; - LoRaWANChannelSpan_t* span = NULL; - for(; spanId < this->band->numChannelSpans; spanId++) { - span = (LoRaWANChannelSpan_t*)&this->band->defaultChannels[spanId]; - if((span->direction == dir) || (span->direction == RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH)) { - break; - } - } - - // shouldn't happen, but just to be sure - if(!span) { - RADIOLIB_DEBUG_PRINTLN("findChannelId span not found"); - return(RADIOLIB_ERR_INVALID_CHANNEL); - } - - // if requested, save the data rate - if(dr) { - *dr = span->joinRequestDataRate; - } - - // determine min and max based on number of channels in span and user constraints - uint8_t chMin = (min > 0) ? min : 0; - uint8_t chMax = (max > 0) ? max : span->numChannels; - - // select channel ID as random number between min and max (global number 0 - N for single direction) - int32_t chId = this->phyLayer->random(chMin, chMax); - *ch = chId; - return(RADIOLIB_ERR_NONE); -} - -LoRaWANChannelSpan_t* LoRaWANNode::findChannelSpan(uint8_t dir, uint8_t ch, uint8_t* spanChannelId) { - // find the span based on the channel ID - uint8_t chanCtr = 0; - *spanChannelId = 0; - for(uint8_t span = 0; span < this->band->numChannelSpans; span++) { - // check if this channel span can be used - uint8_t direction = this->band->defaultChannels[span].direction; - if((direction != dir) && (direction != RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH)) { - continue; - } - - // iterate over the usable spans to the channel ID - for(; *spanChannelId < this->band->defaultChannels[span].numChannels; (*spanChannelId)++) { - if(chanCtr >= ch) { - // we found it, return the pointer (channel ID within the span is already set) - return((LoRaWANChannelSpan_t*)&this->band->defaultChannels[span]); - } - chanCtr++; - } - } - - return(NULL); -} - -int16_t LoRaWANNode::findChannelFreq(uint8_t dir, uint8_t ch, float* freq) { - // find the channel span based on channel ID and direction - uint8_t spanChannelId = 0; - LoRaWANChannelSpan_t* span = findChannelSpan(dir, ch, &spanChannelId); - if(!span) { - return(RADIOLIB_ERR_INVALID_CHANNEL); - } - - // set the frequency - *freq = span->freqStart + span->freqStep * (float)spanChannelId; return(RADIOLIB_ERR_NONE); } int16_t LoRaWANNode::configureChannel(uint8_t dir) { // set the frequency - RADIOLIB_DEBUG_PRINT("Channel frequency %cL = ", dir ? 'D' : 'U'); - RADIOLIB_DEBUG_PRINT_FLOAT(this->channelFreq[dir], 3); - RADIOLIB_DEBUG_PRINTLN(" MHz"); - int state = this->phyLayer->setFrequency(this->channelFreq[dir]); + RADIOLIB_DEBUG_PRINTLN(""); + RADIOLIB_DEBUG_PRINTLN("Channel frequency %cL = %f MHz", dir ? 'D' : 'U', this->currentChannels[dir].freq); + int state = this->phyLayer->setFrequency(this->currentChannels[dir].freq); RADIOLIB_ASSERT(state); - // find the channel span based on channel ID and direction - uint8_t spanChannelId = 0; - LoRaWANChannelSpan_t* span = findChannelSpan(dir, this->chIndex[dir], &spanChannelId); - if(!span) { - return(RADIOLIB_ERR_INVALID_CHANNEL); + // 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->FSK = true; + } else { + this->FSK = false; } - // set the data rate - DataRate_t dataRate; - this->dataRate[dir] = findDataRate(this->dataRate[dir], &dataRate, span); - state = this->phyLayer->setDataRate(dataRate); - return(state); -} - -int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen) { - // build the command - size_t macReqLen = 1 + payloadLen; - #if !defined(RADIOLIB_STATIC_ONLY) - uint8_t* macReqBuff = new uint8_t[macReqLen]; - #else - uint8_t macReqBuff[RADIOLIB_STATIC_ARRAY_SIZE]; - #endif - macReqBuff[0] = cid; - memcpy(&macReqBuff[1], payload, payloadLen); - - // uplink it - int16_t state = this->uplink(macReqBuff, macReqLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] macReqBuff; - #endif + DataRate_t dr; + findDataRate(this->dataRates[dir], &dr); + state = this->phyLayer->setDataRate(dr); RADIOLIB_ASSERT(state); - // build the reply buffer - size_t macRplLen = 1 + replyLen; - #if !defined(RADIOLIB_STATIC_ONLY) - uint8_t* macRplBuff = new uint8_t[this->band->payloadLenMax[this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]]; - #else - uint8_t macRplBuff[RADIOLIB_STATIC_ARRAY_SIZE]; - #endif - - // wait for reply from the server - size_t rxRplLen = 0; - state = this->downlink(macRplBuff, &rxRplLen); - if(state != RADIOLIB_ERR_NONE) { - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] macRplBuff; - #endif - return(state); + if(this->FSK) { + state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); } - RADIOLIB_DEBUG_PRINTLN("macRplBuff:"); - RADIOLIB_DEBUG_HEXDUMP(macRplBuff, rxRplLen); - - // check the length - it may be longer than expected - // if the server decided to append more MAC commands, but never shorter - // TODO how to handle the additional command(s)? - if(rxRplLen < macRplLen) { - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] macRplBuff; - #endif - return(RADIOLIB_ERR_DOWNLINK_MALFORMED); - } - - // check the CID - if(macRplBuff[0] != cid) { - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] macRplBuff; - #endif - return(RADIOLIB_ERR_INVALID_CID); - } - - // copy the data - memcpy(reply, &macRplBuff[1], replyLen); - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] macRplBuff; - #endif - return(state); } @@ -1361,48 +1734,12 @@ int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQ } memcpy(&queue->commands[queue->numCommands], cmd, sizeof(LoRaWANMacCommand_t)); - /*RADIOLIB_DEBUG_PRINTLN("push MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ", - queue->commands[queue->numCommands - 1].cid, - queue->commands[queue->numCommands - 1].len, - queue->commands[queue->numCommands - 1].payload[0], - queue->commands[queue->numCommands - 1].payload[1], - queue->commands[queue->numCommands - 1].payload[2], - queue->commands[queue->numCommands - 1].payload[3], - queue->commands[queue->numCommands - 1].payload[4], - queue->commands[queue->numCommands - 1].repeat);*/ queue->numCommands++; queue->len += 1 + cmd->len; // 1 byte for command ID, len bytes for payload return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, size_t index) { - if(queue->numCommands == 0) { - return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY); - } - - if(cmd) { - // RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ", - // queue->commands[index].cid, - // queue->commands[index].len, - // queue->commands[index].payload[0], - // queue->commands[index].payload[1], - // queue->commands[index].payload[2], - // queue->commands[index].payload[3], - // queue->commands[index].payload[4], - // queue->commands[index].repeat); - memcpy(cmd, &queue->commands[index], sizeof(LoRaWANMacCommand_t)); - } - - if(queue->commands[index].repeat > 0) { - queue->commands[index].repeat--; - } else { - deleteMacCommand(queue->commands[index].cid, queue); - } - - return(RADIOLIB_ERR_NONE); -} - int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue) { if(queue->numCommands == 0) { return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY); @@ -1410,19 +1747,10 @@ int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* que for(size_t index = 0; index < queue->numCommands; index++) { if(queue->commands[index].cid == cid) { - // RADIOLIB_DEBUG_PRINTLN("delete MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ", - // queue->commands[index].cid, - // queue->commands[index].len, - // queue->commands[index].payload[0], - // queue->commands[index].payload[1], - // queue->commands[index].payload[2], - // queue->commands[index].payload[3], - // queue->commands[index].payload[4], - // queue->commands[index].repeat); 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) * sizeof(LoRaWANMacCommand_t)); + memmove(&queue->commands[index], &queue->commands[index + 1], (RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - index - 1) * sizeof(LoRaWANMacCommand_t)); } // set the latest element to all 0 memset(&queue->commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1], 0x00, sizeof(LoRaWANMacCommand_t)); @@ -1466,55 +1794,102 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { case(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR): { // get the ADR configuration - uint8_t dr = (cmd->payload[0] & 0xF0) >> 4; + // TODO all these configuration should only be set if all ACKs are set, otherwise retain previous state (per spec) + uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4; uint8_t txPower = cmd->payload[0] & 0x0F; uint16_t chMask = LoRaWANNode::ntoh(&cmd->payload[1]); uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4; uint8_t nbTrans = cmd->payload[3] & 0x0F; - RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %02x, nbTrans = %d", dr, txPower, chMask, chMaskCntl, nbTrans); + RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %02x, nbTrans = %d", drUp, txPower, chMask, chMaskCntl, nbTrans); // apply the configuration uint8_t drAck = 0; - if(dr != 0x0F) { - // first figure out which channel span this data rate applies to - // TODO do that by processing the chMask/chMaskCntl? - uint8_t spanChannelId = 0; - LoRaWANChannelSpan_t* span = findChannelSpan(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], &spanChannelId); - - // seems to be only applicable to uplink - if(span) { - DataRate_t dataRate; - this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = findDataRate(dr, &dataRate, span); - if(this->phyLayer->setDataRate(dataRate) == RADIOLIB_ERR_NONE) { - RADIOLIB_DEBUG_PRINTLN("ADR set dr = %d channel = %d", dr, spanChannelId); - drAck = 1; - } - } - - } else { + if(drUp == 0x0F) { drAck = 1; - - } + } else if (this->band->dataRates[drUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + 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; + } // try to apply the power configuration uint8_t pwrAck = 0; - if(txPower != 0x0F) { + if(txPower == 0x0F) { + pwrAck = 1; + } else { int8_t pwr = this->band->powerMax - 2*txPower; if(this->phyLayer->setOutputPower(pwr) == RADIOLIB_ERR_NONE) { RADIOLIB_DEBUG_PRINTLN("ADR set pwr = %d", pwr); pwrAck = 1; } - - } else { - pwrAck = 1; + this->txPwrCur = pwr; } - // TODO implement repeated uplinks with nbTrans - (void)nbTrans; - // TODO implement channel mask - uint8_t chMaskAck = 0; - (void)chMask; - (void)chMaskCntl; + uint8_t chMaskAck = 1; + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(chMaskCntl == 0) { + // if chMaskCntl == 0, apply the mask by looking at each channel bit + RADIOLIB_DEBUG_PRINTLN("ADR channel %d: %d --> %d", this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, (chMask >> i) & 0x01); + 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) { + chMaskAck = 0; + break; + } + 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) { + // 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; + } + } + + } + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // delete any prior ADR responses from the uplink queue, but do not care if none is present yet + (void)deleteMacCommand(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR, &this->commandsUp); + RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask); + uint8_t num = 0; + uint8_t chNum = chMaskCntl*16; + uint8_t chSpan = 0; + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + RADIOLIB_DEBUG_PRINTLN("chNum: %d, chSpan: %d, i: %d, mask: %d", chNum, chSpan, i, chMask & (1UL << i)); + // if we must roll over to next span, reset chNum and move to next channel span + if(chNum >= this->band->txSpans[chSpan].numChannels) { + chNum = 0; + chSpan++; + } + + if(chMask & (1UL << i)) { + if(chSpan >= this->band->numTxSpans) { + RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!"); + return(RADIOLIB_ERR_UNKNOWN); + } + LoRaWANChannel_t chnl; + chnl.enabled = true; + chnl.idx = chMaskCntl*16 + i; + chnl.freq = this->band->txSpans[chSpan].freqStart + chNum*this->band->txSpans[chSpan].freqStep; + chnl.drMin = this->band->txSpans[chSpan].drMin; + chnl.drMax = this->band->txSpans[chSpan].drMax; + availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; + // downlink channels are dynamically calculated on each uplink in selectChannels() + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", num, chnl.idx, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); + num++; + } + chNum++; + } + } + // TODO should we actually save the channels because the masks may have changed stuff? + // this may wear the storage quickly on more mobile devices / changing RF environment + + this->nbTrans = nbTrans; // send the reply cmd->len = 1; @@ -1535,29 +1910,22 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { case(RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP): { // get the configuration - uint8_t rx1DrOffset = (cmd->payload[0] & 0x70) >> 4; - uint8_t rx2DataRate = cmd->payload[0] & 0x0F; + 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); - RADIOLIB_DEBUG_PRINTLN("RX Param: rx1DrOffset = %d, rx2DataRate = %d, freq = %d", rx1DrOffset, rx2DataRate, freqRaw); - + this->rx2.freq = (float)freqRaw/10000.0; + RADIOLIB_DEBUG_PRINTLN("Rx param REQ: rx1DrOffset = %d, rx2DataRate = %d, freq = %f", this->rx1DrOffset, this->rx2.drMax, this->rx2.freq); + // apply the configuration - float freq = (float)freqRaw/10000.0; - float prevFreq = this->channelFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]; uint8_t chanAck = 0; - if(this->phyLayer->setFrequency(freq) == RADIOLIB_ERR_NONE) { - this->backupFreq = freq; + if(this->phyLayer->setFrequency(this->rx2.freq) == RADIOLIB_ERR_NONE) { chanAck = 1; - this->phyLayer->setFrequency(prevFreq); + this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); } - // TODO process the RX2 data rate - (void)rx2DataRate; - uint8_t rx2Ack = 0; - - // TODO process the data rate offset - (void)rx1DrOffset; - uint8_t rx1OffsAck = 0; - + // TODO this should be sent repeatedly until the next downlink // send the reply cmd->len = 1; cmd->payload[0] = (rx1OffsAck << 2) | (rx2Ack << 1) | (chanAck << 0); @@ -1585,18 +1953,86 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { float freq = (float)freqRaw/10000.0; uint8_t maxDr = (cmd->payload[4] & 0xF0) >> 4; uint8_t minDr = cmd->payload[4] & 0x0F; - RADIOLIB_DEBUG_PRINT("New channel: index = %d, freq = ", chIndex); - RADIOLIB_DEBUG_PRINT_FLOAT(freq, 3); - RADIOLIB_DEBUG_PRINTLN(" MHz, maxDr = %d, minDr = %d", maxDr, minDr); + RADIOLIB_DEBUG_PRINTLN("New channel: index = %d, freq = %f MHz, maxDr = %d, minDr = %d", chIndex, freq, maxDr, minDr); + uint8_t newChAck = 0; + uint8_t freqAck = 0; + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // find first empty channel and configure this as the new channel + if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx = chIndex; + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq = freq; + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin = minDr; + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax = maxDr; + + // downlink channel is identical to uplink channel + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]; + newChAck = 1; + + // check if the frequency is possible + if(this->phyLayer->setFrequency(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq) == RADIOLIB_ERR_NONE) { + freqAck = 1; + this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); + } + + break; + } + } + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // update saved frequencies + this->saveChannels(); +#endif + + // send the reply + cmd->len = 1; + cmd->payload[0] = (newChAck << 1) | (freqAck << 0); + + pushMacCommand(cmd, &this->commandsUp); - // TODO implement this - (void)chIndex; - (void)freq; - (void)maxDr; - (void)minDr; return(5); } break; + case(RADIOLIB_LORAWAN_MAC_CMD_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_PRINTLN("DL channel: 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; + } + } + } + +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + // update saved frequencies + this->saveChannels(); +#endif + + // send the reply + cmd->len = 1; + cmd->payload[0] = (freqUlAck << 1) | (freqDlAck << 0); + + pushMacCommand(cmd, &this->commandsUp); + + return(4); + } break; + case(RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP): { // get the configuration uint8_t delay = cmd->payload[0] & 0x0F; @@ -1635,21 +2071,6 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { return(1); } break; - case(RADIOLIB_LORAWAN_MAC_CMD_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_PRINT("DL channel: index = %d, freq = ", chIndex); - RADIOLIB_DEBUG_PRINT_FLOAT(freq, 3); - RADIOLIB_DEBUG_PRINTLN(" MHz"); - - // TODO implement this - (void)chIndex; - (void)freq; - return(4); - } break; - case(RADIOLIB_LORAWAN_MAC_CMD_REKEY): { // get the server version uint8_t srvVersion = cmd->payload[0]; @@ -1662,12 +2083,10 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { } break; case(RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP): { - // TODO implement this - uint8_t limitExp = (cmd->payload[0] & 0xF0) >> 4; - uint8_t delayExp = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PRINTLN("ADR param setup: limitExp = %d, delayExp = %d", limitExp, delayExp); - (void)limitExp; - (void)delayExp; + this->adrLimitExp = (cmd->payload[0] & 0xF0) >> 4; + this->adrDelayExp = cmd->payload[0] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("ADR param setup: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); + return(1); } break; @@ -1675,9 +2094,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { // TODO implement this - sent by gateway as reply to node request uint32_t gpsEpoch = LoRaWANNode::ntoh(&cmd->payload[0]); uint8_t fraction = cmd->payload[4]; - RADIOLIB_DEBUG_PRINT("Network time: gpsEpoch = %d s, delayExp = ", gpsEpoch, (float)fraction/256.0f); - RADIOLIB_DEBUG_PRINT_FLOAT((float)fraction/256.0f, 2); - RADIOLIB_DEBUG_PRINTLN(); + RADIOLIB_DEBUG_PRINTLN("Network time: gpsEpoch = %d s, delayExp = %f", gpsEpoch, (float)fraction/256.0f); (void)gpsEpoch; (void)fraction; return(5); @@ -1712,6 +2129,50 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { return(0); } +// 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_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_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(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; @@ -1732,6 +2193,7 @@ void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out // on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext size_t remLen = len; for(size_t i = 0; i < numBlocks; i++) { + if(counter) { encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1; } @@ -1778,52 +2240,4 @@ void LoRaWANNode::hton(uint8_t* buff, T val, size_t size) { } } -// 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_PRINTLN("OCCUPIED CHANNEL DURING DIFS"); - channelFreeDuringDIFS = false; - // Channel is occupied during DIFS, hop to another. - this->setupChannels(); - 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_PRINTLN("OCCUPIED CHANNEL DURING BO"); - // Channel is busy during CAD, hop to another and return to DIFS state again. - this->setupChannels(); - 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 -} - -#endif \ No newline at end of file +#endif diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 82b08127..5d0eacc1 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -6,7 +6,7 @@ #include "../../utils/Cryptography.h" // version of NVM table layout (NOT the LoRaWAN version) -#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION (0x01) +#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION (0x01) // preamble format #define RADIOLIB_LORAWAN_LORA_SYNC_WORD (0x34) @@ -69,9 +69,10 @@ #define RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK (0x01 << 0) #define RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH (0x02 << 0) #define RADIOLIB_LORAWAN_CHANNEL_DIR_NONE (0x03 << 0) -#define RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES (0) -#define RADIOLIB_LORAWAN_CFLIST_TYPE_MASK (1) -#define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (16) +#define RADIOLIB_LORAWAN_BAND_DYNAMIC (0) +#define RADIOLIB_LORAWAN_BAND_FIXED (1) +#define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (15) +#define RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE (0xFF >> 1) // reserve first bit for enable-flag // recommended default settings #define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000) @@ -81,8 +82,8 @@ #define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000) #define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000) #define RADIOLIB_LORAWAN_MAX_FCNT_GAP (16384) -#define RADIOLIB_LORAWAN_ADR_ACK_LIMIT (64) -#define RADIOLIB_LORAWAN_ADR_ACK_DELAY (32) +#define RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP (0x06) +#define RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP (0x05) #define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MIN_MS (1000) #define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MAX_MS (3000) #define RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM (-2) @@ -134,6 +135,7 @@ // payload encryption/MIC blocks common layout #define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0) +#define RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS (1) #define RADIOLIB_LORAWAN_BLOCK_DIR_POS (5) #define RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS (6) #define RADIOLIB_LORAWAN_BLOCK_FCNT_POS (10) @@ -150,7 +152,7 @@ #define RADIOLIB_LORAWAN_MIC_CH_INDEX_POS (4) // magic word saved in persistent memory upon activation -#define RADIOLIB_LORAWAN_MAGIC (0x12AD101B) +#define RADIOLIB_LORAWAN_MAGIC (0x39EA) // MAC commands #define RADIOLIB_LORAWAN_MAC_CMD_RESET (0x01) @@ -170,11 +172,39 @@ #define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP (0x0F) #define RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY (0x80) +// unused frame counter value +#define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) + // the length of internal MAC command queue - hopefully this is enough for most use cases #define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (8) // the maximum number of simultaneously available channels -#define RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS (8) +#define RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS (16) + +/*! + \struct LoRaWANChannelSpan_t + \brief Structure to save information about LoRaWAN channels. + To save space, adjacent channels are saved in "spans". +*/ +struct LoRaWANChannel_t { + /*! \brief Whether this channel is enabled (can be used) or is disabled */ + bool enabled; + + /*! \brief The channel number, as specified by defaults or the network */ + uint8_t idx; + + /*! \brief The channel frequency */ + float freq; + + /*! \brief Minimum allowed datarate for this channel */ + uint8_t drMin; + + /*! \brief Maximum allowed datarate for this channel (inclusive) */ + uint8_t drMax; +}; + +// alias for unused channel +#define RADIOLIB_LORAWAN_CHANNEL_NONE { .enabled = false, .idx = RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE, .freq = 0, .drMin = 0, .drMax = 0 } /*! \struct LoRaWANChannelSpan_t @@ -182,12 +212,6 @@ To save space, adjacent channels are saved in "spans". */ struct LoRaWANChannelSpan_t { - /*! \brief Whether this channel span is for uplink, downlink, or both directions*/ - uint8_t direction; - - /*! \brief Allowed data rates for a join request message */ - uint8_t joinRequestDataRate; - /*! \brief Total number of channels in the span */ uint8_t numChannels; @@ -197,23 +221,26 @@ struct LoRaWANChannelSpan_t { /*! \brief Frequency step between adjacent channels */ float freqStep; - /*! \brief Array of datarates supported by all channels in the span */ - uint8_t dataRates[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; + /*! \brief Minimum allowed datarate for all channels in this span */ + uint8_t drMin; + + /*! \brief Maximum allowed datarate for all channels in this span (inclusive) */ + uint8_t drMax; + + /*! \brief Allowed data rates for a join request message */ + uint8_t joinRequestDataRate; }; // alias for unused channel span -#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_NONE, .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, .numChannels = 0, .freqStart = 0, .freqStep = 0, .dataRates = { 0 } } +#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .numChannels = 0, .freqStart = 0, .freqStep = 0, .drMin = 0, .drMax = 0, .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED } /*! \struct LoRaWANBand_t \brief Structure to save information about LoRaWAN band */ struct LoRaWANBand_t { - /*! \brief The base downlink data rate. Used to calculate data rate changes for adaptive data rate */ - uint8_t downlinkDataRateBase; - - /*! \brief The minimum allowed downlink data rate. Used to calculate data rate changes for adaptive data rate */ - uint8_t downlinkDataRateMin; + /*! \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 */ uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; @@ -224,20 +251,29 @@ struct LoRaWANBand_t { /*! \brief Number of power steps in this band */ int8_t powerNumSteps; - /*! \brief Whether the optional channels are defined as list of frequencies or bit mask */ - uint8_t cfListType; + /*! \brief A set of default uplink (TX) channels for frequency-type bands */ + LoRaWANChannel_t txFreqs[3]; - /*! \brief FSK channel frequency */ - float fskFreq; + /*! \brief A set of possible extra channels for the Join-Request message for frequency-type bands */ + LoRaWANChannel_t txJoinReq[3]; - /*! \brief Number of channel spans in the band */ - uint8_t numChannelSpans; + /*! \brief The number of TX channel spans for mask-type bands */ + uint8_t numTxSpans; - /*! \brief Default uplink (TX/RX1) channels defined by LoRaWAN Regional Parameters */ - LoRaWANChannelSpan_t defaultChannels[3]; + /*! \brief Default uplink (TX) channel spans for mask-type bands, including Join-Request parameters */ + LoRaWANChannelSpan_t txSpans[2]; + + /*! \brief Default downlink (RX1) channel span for mask-type bands */ + LoRaWANChannelSpan_t rx1Span; + + /*! \brief The base downlink data rate. Used to calculate data rate changes for adaptive data rate */ + uint8_t rx1DataRateBase; + + /*! \brief Backup channel for downlink (RX2) window */ + LoRaWANChannel_t rx2; - /*! \brief Backup downlink (RX2) channel - just a single channel, but using the same structure for convenience */ - LoRaWANChannelSpan_t backupChannel; + /*! \brief The corresponding datarates, bandwidths and coding rates for DR index */ + uint8_t dataRates[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; }; // supported bands @@ -259,20 +295,20 @@ struct LoRaWANMacCommand_t { /*! \brief The command ID */ uint8_t cid; - /*! \brief Length of the payload */ - uint8_t len; - /*! \brief Payload buffer (5 bytes is the longest possible) */ uint8_t payload[5]; + /*! \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 { + uint8_t numCommands; + uint8_t len; LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE]; - size_t numCommands; - size_t len; }; /*! @@ -281,31 +317,12 @@ struct LoRaWANMacCommandQueue_t { */ class LoRaWANNode { public: - /*! \brief Set to true to force the node to only use FSK channels. Set to false by default. */ - bool FSK; - /*! \brief Starting channel offset. - Some band plans only support a subset of available channels. - Set to a positive value to set the first channel that will be used (e.g. 8 for US915 FSB2 used by TTN). - By default -1 (no channel offset). */ - int8_t startChannel; + // Offset between TX and RX1 (such that RX1 has equal or lower DR) + uint8_t rx1DrOffset; - /*! \brief Number of supported channels. - Some band plans only support a subset of available channels. - Set to a positive value to set the number of channels that will be used - (e.g. 8 for US915 FSB2 used by TTN). By default -1 (no channel offset). */ - int8_t numChannels; - - /*! \brief Num of Back Off(BO) slots to be decremented after DIFS phase. 0 to disable BO. - A random BO avoids collisions in the case where two or more nodes start the CSMA - process at the same time. */ - uint8_t backoffMax; - - /*! \brief Num of CADs to estimate a clear CH. */ - uint8_t difsSlots; - - /*! \brief enable/disable CSMA for LoRaWAN. */ - bool enableCSMA; + // RX2 channel properties - may be changed by MAC command + LoRaWANChannel_t rx2; /*! \brief Default constructor. @@ -314,6 +331,7 @@ class LoRaWANNode { */ LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band); +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) /*! \brief Wipe internal persistent parameters. This will reset all counters and saved variables, so the device will have to rejoin the network. @@ -321,18 +339,11 @@ class LoRaWANNode { void wipe(); /*! - \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); - - /*! - \brief Restore OTAA session by loading information from persistent storage. + \brief Restore session by loading information from persistent storage. \returns \ref status_codes */ - int16_t restoreOTAA(); + int16_t restore(); +#endif /*! \brief Join network by performing over-the-air activation. By this procedure, @@ -341,10 +352,12 @@ class LoRaWANNode { \param devEUI 8-byte device identifier. \param nwkKey Pointer to the network AES-128 key. \param appKey Pointer to the application AES-128 key. + \param joinDr (OTAA:) The datarate at which to send the join-request; (ABP:) ignored \param force Set to true to force joining even if previously joined. + \returns \ref status_codes */ - int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false); + int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, uint8_t joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, bool force = false); /*! \brief Join network by performing activation by personalization. @@ -354,36 +367,50 @@ class LoRaWANNode { \param appSKey Pointer to the application session AES-128 key. \param fNwkSIntKey Pointer to the network session F key (LoRaWAN 1.1), unused for LoRaWAN 1.0. \param sNwkSIntKey Pointer to the network session S key (LoRaWAN 1.1), unused for LoRaWAN 1.0. + \param force Set to true to force a new session, even if one exists. \returns \ref status_codes */ - int16_t beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL); + int16_t beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL, bool force = false); + + /*! \brief Whether there is an ongoing session active */ + bool isJoined(); + + /*! + \brief Save the current state of the session. + All variables are compared to what is saved and only the differences are rewritten. + \returns \ref status_codes + */ + int16_t saveSession(); #if defined(RADIOLIB_BUILD_ARDUINO) /*! \brief Send a message to the server. \param str Address of Arduino String that will be transmitted. \param port Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. \returns \ref status_codes */ - int16_t uplink(String& str, uint8_t port); + int16_t uplink(String& str, uint8_t port, bool isConfirmed = false); #endif /*! \brief Send a message to the server. \param str C-string that will be transmitted. \param port Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. \returns \ref status_codes */ - int16_t uplink(const char* str, uint8_t port); + int16_t uplink(const char* str, uint8_t port, bool isConfirmed = false); /*! \brief Send a message to the server. \param data Data to send. \param len Length of the data. \param port Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. \returns \ref status_codes */ - int16_t uplink(uint8_t* data, size_t len, uint8_t port); + int16_t uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed = false); #if defined(RADIOLIB_BUILD_ARDUINO) /*! @@ -402,6 +429,41 @@ class LoRaWANNode { */ int16_t downlink(uint8_t* data, size_t* len); + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param strUp Address of Arduino String that will be transmitted. + \param port Port number to send the message to. + \param strDown Address of Arduino String to save the received data. + \param isConfirmed Whether to send a confirmed uplink or not. + \returns \ref status_codes + */ + int16_t sendReceive(String& strUp, uint8_t port, String& strDown, bool isConfirmed = false); + #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 port Port number to send the message to. + \param dataDown Buffer to save received data into. + \param lenDown Pointer to variable that will be used to save the number of received bytes. + \param isConfirmed Whether to send a confirmed uplink or not. + \returns \ref status_codes + */ + int16_t sendReceive(const char* strUp, uint8_t port, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false); + + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param dataUp Data to send. + \param lenUp Length of the data. + \param port Port number to send the message to. + \param dataDown Buffer to save received data into. + \param lenDown Pointer to variable that will be used to save the number of received bytes. + \param isConfirmed Whether to send a confirmed uplink or not. + \returns \ref status_codes + */ + int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t port, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false); + /*! \brief Set device status. \param battLevel Battery level to set. 0 for external power source, 1 for lowest battery, @@ -409,6 +471,47 @@ class LoRaWANNode { */ void setDeviceStatus(uint8_t battLevel); + /*! \brief Returns the last uplink's frame counter */ + uint32_t getFcntUp(); + + /*! + \brief Set uplink datarate. This should not be used when ADR is enabled. + \param dr Datarate to use for uplinks. + \returns \ref status_codes + */ + int16_t setDatarate(uint8_t drUp); + + /*! + \brief Toggle ADR to on or off + \param enable Whether to disable ADR or not + */ + void setADR(bool enable = true); + + /*! + \brief Select a single subband (8 channels) for fixed bands such as US915. + Only available before joining a network. + \param idx The subband to be used (starting from 1!) + \returns \ref status_codes + */ + int16_t selectSubband(uint8_t idx); + + /*! + \brief Select a set of channels for fixed bands such as US915. + Only available before joining a network. + \param startChannel The first channel of the band to be used (inclusive) + \param endChannel The last channel of the band to be used (inclusive) + \returns \ref status_codes + */ + int16_t selectSubband(uint8_t startChannel, uint8_t endChannel); + + /*! + \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); + #if !defined(RADIOLIB_GODMODE) private: #endif @@ -416,14 +519,14 @@ class LoRaWANNode { const LoRaWANBand_t* band = NULL; LoRaWANMacCommandQueue_t commandsUp = { - .commands = { { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, } }, .numCommands = 0, .len = 0, + .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } }, }; LoRaWANMacCommandQueue_t commandsDown = { - .commands = { { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, } }, .numCommands = 0, .len = 0, + .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } }, }; // the following is either provided by the network server (OTAA) @@ -434,29 +537,62 @@ class LoRaWANNode { uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; uint8_t jSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + + // device-specific parameters, persistent through sessions + uint16_t devNonce = 0; + uint32_t joinNonce = 0; + + // session-specific parameters + uint32_t homeNetId = 0; + uint8_t adrLimitExp = RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP; + uint8_t adrDelayExp = RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; + uint8_t nbTrans = 1; // Number of allowed frame retransmissions + uint8_t txPwrCur = 0; + uint32_t fcntUp = 0; + uint32_t aFcntDown = 0; + uint32_t nFcntDown = 0; + uint32_t confFcntUp = RADIOLIB_LORAWAN_FCNT_NONE; + uint32_t confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; + uint32_t adrFcnt = 0; + + // whether the current configured channel is in FSK mode + bool FSK; + + // flag that shows whether the device is joined and there is an ongoing session + bool isJoinedFlag = false; + + // ADR is enabled by default + bool adrEnabled = true; + + // enable/disable CSMA for LoRaWAN + bool enableCSMA; + + // number of backoff slots to be decremented after DIFS phase. 0 to disable BO. + // A random BO avoids collisions in the case where two or more nodes start the CSMA + // process at the same time. + uint8_t backoffMax; + + // number of CADs to estimate a clear CH + uint8_t difsSlots; // available channel frequencies from list passed during OTA activation - float availableChannelsFreq[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS] = { { 0 }, { 0 } }; + LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS] = { { 0 }, { 0 } }; - // currently configured channel frequency - float channelFreq[2] = { 0 }; + // currently configured channels for TX and RX1 + LoRaWANChannel_t currentChannels[2] = { 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 }; // LoRaWAN revision (1.0 vs 1.1) uint8_t rev = 0; - // currently configured data rate for uplink and downlink: DR0 - DR15 (band-dependent!) - uint8_t dataRate[2] = { 0 }; - - // currently configured channel for uplink and downlink (band-dependent!) - uint8_t chIndex[2] = { 0 }; - - // backup channel properties - may be changed by MAC command - float backupFreq = 0; - uint8_t backupDataRate = 0; - // timestamp to measure the RX1/2 delay (from uplink end) uint32_t rxDelayStart = 0; + // timestamp when the Rx1/2 windows were closed (timeout or uplink received) + uint32_t rxDelayEnd = 0; + // delays between the uplink and RX1/2 windows uint32_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS }; @@ -466,6 +602,25 @@ class LoRaWANNode { // indicates whether an uplink has MAC commands as payload bool isMACPayload = false; +#if !defined(RADIOLIB_EEPROM_UNSUPPORTED) + /*! + \brief Save the current uplink frame counter. + Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling. + \returns \ref status_codes + */ + int16_t saveFcntUp(); + + /*! + \brief Restore frame counter for uplinks from persistent storage. + Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling. + \returns \ref status_codes + */ + int16_t restoreFcntUp(); +#endif + + // wait for, open and listen during Rx1 and Rx2 windows; only performs listening + int16_t downlinkCommon(); + // method to generate message integrity code uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key); @@ -479,37 +634,40 @@ class LoRaWANNode { // setup uplink/downlink channel data rates and frequencies // will attempt to randomly select based on currently used band plan - int16_t setupChannels(); + int16_t setupChannels(uint8_t* cfList); - // find the first usable data rate in a given channel span - uint8_t findDataRate(uint8_t dr, DataRate_t* dataRate, const LoRaWANChannelSpan_t* span); + // select a set of semi-random TX/RX channels for the join-request and -accept message + int16_t selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband); - // find a channel ID that conforms to the requested direction and ID range - int16_t findChannelId(uint8_t dir, uint8_t* ch, uint8_t* dr, int8_t min, int8_t max); + // select a set of random TX/RX channels for up- and downlink + int16_t selectChannels(); - // find a channel span that any given channel id belongs to - LoRaWANChannelSpan_t* findChannelSpan(uint8_t dir, uint8_t ch, uint8_t* spanChannelId); - - // calculate channel frequency in MHz based on channel ID and direction - int16_t findChannelFreq(uint8_t dir, uint8_t ch, float* freq); + // find the first usable data rate for the given band + int16_t findDataRate(uint8_t dr, DataRate_t* dataRate); // configure channel based on cached data rate ID and frequency int16_t configureChannel(uint8_t dir); - // send a MAC command to the network server - int16_t sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen); + // save all available channels to persistent storage + int16_t saveChannels(); + + // restore all available channels from persistent storage + int16_t restoreChannels(); // push MAC command to queue, done by copy int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue); - - // pop MAC command from queue, done by copy unless CMD is NULL - int16_t popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, size_t index); // delete a specific MAC command from queue, indicated by the command ID int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue); // execute mac command, return the number of processed bytes for sequential processing size_t execMacCommand(LoRaWANMacCommand_t* cmd); + + // Performs CSMA as per LoRa Alliance Technical Reccomendation 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 void processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter); @@ -521,12 +679,6 @@ class LoRaWANNode { // host-to-network conversion method - takes data from host variable and and converts it to network packet endians template static void hton(uint8_t* buff, T val, size_t size = 0); - - // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. - bool performCAD(); - - // Performs CSMA as per LoRa Alliance Technical Reccomendation 13 (TR-013). - void performCSMA(); }; #endif diff --git a/src/protocols/LoRaWAN/LoRaWANBands.cpp b/src/protocols/LoRaWAN/LoRaWANBands.cpp index c25b0af9..8e36f9c2 100644 --- a/src/protocols/LoRaWAN/LoRaWANBands.cpp +++ b/src/protocols/LoRaWAN/LoRaWANBands.cpp @@ -2,729 +2,437 @@ #if !defined(RADIOLIB_EXCLUDE_LORAWAN) -uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_t lim) { - int8_t dr = uplink - offset + base; - if(dr < lim) { - dr = lim; - } - return(dr); -} - const LoRaWANBand_t EU868 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 230, 230, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES, - .fskFreq = 868.8, - .numChannelSpans = 1, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 3, - .freqStart = 868.1, - .freqStep = 0.2, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .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}, }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 869.858, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + 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 }, + .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_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; const LoRaWANBand_t US915 = { - .downlinkDataRateBase = 10, - .downlinkDataRateMin = 8, - .payloadLenMax = { - 19, 61, 133, 250, 250, 0, 0, 0, - 41, 117, 230, 230, 230, 230, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_FIXED, + .payloadLenMax = { 19, 61, 133, 250, 250, 0, 0, 0, 41, 117, 230, 230, 230, 230, 0 }, .powerMax = 30, .powerNumSteps = 10, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK, - .fskFreq = 0, - .numChannelSpans = 3, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - .joinRequestDataRate = 0, - .numChannels = 64, - .freqStart = 902.3, - .freqStep = 0.2, - .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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - .joinRequestDataRate = 4, - .numChannels = 8, - .freqStart = 903, - .freqStep = 1.6, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = 10, - .numChannels = 8, - .freqStart = 923.3, - .freqStep = 0.6, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, + .txFreqs = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 923.3, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 2, + .txSpans = { + { + .numChannels = 64, + .freqStart = 902.300, + .freqStep = 0.200, + .drMin = 0, + .drMax = 3, + .joinRequestDataRate = 0 + }, + { + .numChannels = 8, + .freqStart = 903.000, + .freqStep = 1.600, + .drMin = 4, + .drMax = 4, + .joinRequestDataRate = 4 } + }, + .rx1Span = { + .numChannels = 8, + .freqStart = 923.300, + .freqStep = 0.600, + .drMin = 8, + .drMax = 13, + .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .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_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_UNUSED } }; const LoRaWANBand_t CN780 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 250, 230, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 250, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 12, .powerNumSteps = 5, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES, - .fskFreq = 0, - .numChannelSpans = 1, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 6, - .freqStart = 779.5, - .freqStep = 0.2, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 779.500, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 1, .freq = 779.700, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 2, .freq = 779.900, .drMin = 0, .drMax = 5}, }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 786, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + { .enabled = true, .idx = 3, .freq = 780.500, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 4, .freq = 780.700, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 5, .freq = 780.900, .drMin = 0, .drMax = 5} + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DataRateBase = 0, + .rx2 = { .enabled = true, .idx = 0, .freq = 786.000, .drMin = 0, .drMax = 0 }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + 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_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; const LoRaWANBand_t EU433 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 230, 230, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 12, .powerNumSteps = 5, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES, - .fskFreq = 0, - .numChannelSpans = 1, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 3, - .freqStart = 433.175, - .freqStep = 0.2, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .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}, }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 434.665, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + 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 }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + 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_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; const LoRaWANBand_t AU915 = { - .downlinkDataRateBase = 8, - .downlinkDataRateMin = 8, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 230, 0, - 41, 117, 230, 230, 230, 230, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_FIXED, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 0, 41, 117, 230, 230, 230, 230, 0 }, .powerMax = 30, .powerNumSteps = 10, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK, - .fskFreq = 0, - .numChannelSpans = 3, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - .joinRequestDataRate = 0, - .numChannels = 64, - .freqStart = 915.2, - .freqStep = 0.2, - .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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - .joinRequestDataRate = 6, - .numChannels = 8, - .freqStart = 915.9, - .freqStep = 1.6, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 8, - .freqStart = 923.3, - .freqStep = 0.6, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, + .txFreqs = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 923.3, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 2, + .txSpans = { + { + .numChannels = 64, + .freqStart = 915.200, + .freqStep = 0.200, + .drMin = 0, + .drMax = 5, + .joinRequestDataRate = 0 + }, + { + .numChannels = 8, + .freqStart = 915.900, + .freqStep = 1.600, + .drMin = 6, + .drMax = 6, + .joinRequestDataRate = 6 } + }, + .rx1Span = { + .numChannels = 8, + .freqStart = 923.300, + .freqStep = 0.600, + .drMin = 8, + .drMax = 13, + .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .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_UNUSED } }; const LoRaWANBand_t CN500 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_FIXED, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 19, .powerNumSteps = 7, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK, - .fskFreq = 0, - .numChannelSpans = 2, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 96, - .freqStart = 470.3, - .freqStep = 0.2, - .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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 48, - .freqStart = 500.3, - .freqStep = 0.2, - .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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .txFreqs = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 505.3, - .freqStep = 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 1, + .txSpans = { + { + .numChannels = 96, + .freqStart = 470.300, + .freqStep = 0.200, + .drMin = 0, + .drMax = 5, + .joinRequestDataRate = 0 + }, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = { + .numChannels = 48, + .freqStart = 500.300, + .freqStep = 0.200, + .drMin = 0, + .drMax = 5, + .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .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_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; const LoRaWANBand_t AS923 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 230, 230, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES, - .fskFreq = 921.8, - .numChannelSpans = 1, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH, - .joinRequestDataRate = 2, - .numChannels = 2, - .freqStart = 923.2, - .freqStep = 0.2, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 923.200, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 1, .freq = 923.400, .drMin = 0, .drMax = 5}, + RADIOLIB_LORAWAN_CHANNEL_NONE }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 923.2, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + 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 }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + 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_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; const LoRaWANBand_t KR920 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 14, .powerNumSteps = 7, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES, - .fskFreq = 0, - .numChannelSpans = 1, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 3, - .freqStart = 922.1, - .freqStep = 0.2, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .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} }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 921.9, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + 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 }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; const LoRaWANBand_t IN865 = { - .downlinkDataRateBase = 0, - .downlinkDataRateMin = 0, - .payloadLenMax = { - 59, 59, 59, 123, 230, 230, 230, 230, - 0, 0, 0, 0, 0, 0, 0, 0 }, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 30, .powerNumSteps = 10, - .cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES, - .fskFreq = 0, - .numChannelSpans = 1, - .defaultChannels = { - { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 3, - .freqStart = 865.0625, - .freqStep = 0.36, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - 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_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED - } - }, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .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} }, - .backupChannel = { - .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - .numChannels = 1, - .freqStart = 866.55, - .freqStep = 0, - .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - } + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + 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 }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_7, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + 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_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.cpp b/src/protocols/PhysicalLayer/PhysicalLayer.cpp index b244b856..0bb634f9 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.cpp +++ b/src/protocols/PhysicalLayer/PhysicalLayer.cpp @@ -293,7 +293,22 @@ uint32_t PhysicalLayer::getTimeOnAir(size_t len) { (void)len; return(0); } - + +uint32_t PhysicalLayer::calculateRxTimeout(uint32_t timeoutUs) { + (void)timeoutUs; + return(0); +} + +int16_t PhysicalLayer::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) { + (void)irqFlags; + (void)irqMask; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +bool PhysicalLayer::isRxTimeout() { + return(false); +} + int16_t PhysicalLayer::startChannelScan() { return(RADIOLIB_ERR_UNSUPPORTED); } diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.h b/src/protocols/PhysicalLayer/PhysicalLayer.h index c083e476..741cdb8c 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.h +++ b/src/protocols/PhysicalLayer/PhysicalLayer.h @@ -309,7 +309,28 @@ class PhysicalLayer { \returns Expected time-on-air in microseconds. */ virtual uint32_t getTimeOnAir(size_t len); - + + /*! + \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) + \param timeoutUs Timeout in microseconds to listen for + \returns Timeout value in a unit that is specific for the used module + */ + virtual uint32_t calculateRxTimeout(uint32_t timeoutUs); + + /*! + \brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks + \param irqFlags The flags for which IRQs must be triggered + \param irqMask Mask indicating which IRQ triggers a DIO + \returns \ref status_codes + */ + virtual int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); + + /*! + \brief Check whether the IRQ bit for RxTimeout is set + \returns \ref RxTimeout IRQ is set + */ + virtual bool isRxTimeout(); + /*! \brief Interrupt-driven channel activity detection method. interrupt will be activated when packet is detected. Must be implemented in module class.