[LoRaWAN] improve examples, add unified sendReceive, bugfixes, add FSK

This commit is contained in:
StevenCellist 2023-11-08 17:42:05 +01:00
parent 5796bbe746
commit 3df866e23d
13 changed files with 683 additions and 264 deletions

View file

@ -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
@ -91,21 +82,16 @@ void setup() {
// for example, either of the following corresponds to US915 FSB2 in TTN
/*
node.selectSubband(2);
node.selectSubband(8, 16);
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 = 8;
uint8_t joinDr = 4;
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, joinDr);
*/
// a specific band can be selected for joining in fixed bands (e.g. US915):
/*
uint8_t subband = 2;
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, subband);
*/
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
@ -117,21 +103,6 @@ void setup() {
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);
}
*/
delay(5000);
}
// counter to keep track of transmitted packets
@ -141,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"));
@ -183,19 +141,13 @@ 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 "));
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);
}

View file

@ -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
@ -87,7 +78,7 @@ void setup() {
// for example, either of the following corresponds to US915 FSB2 in TTN
/*
node.selectSubband(2);
node.selectSubband(8, 16);
node.selectSubband(8, 15);
*/
// if using EU868 on ABP in TTN, you need to set the SF for RX2 window manually
@ -112,20 +103,6 @@ void setup() {
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);
}
*/
}
// counter to keep track of transmitted packets
@ -135,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"));
@ -177,19 +141,13 @@ 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 "));
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(10000);
delay(30000);
}

View file

@ -0,0 +1,153 @@
/*
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 <RadioLib.h>
// 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);
}
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("<MAC commands only>"));
}
// 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);
}

View file

@ -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 <RadioLib.h>
// 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("<MAC commands only>"));
}
// 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);
}

View file

@ -120,7 +120,7 @@ void inline ArduinoHal::spiEnd() {
void ArduinoHal::readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len) {
#if !defined(RADIOLIB_EEPROM_UNSUPPORTED)
#if defined(RADIOLIB_ESP32)
#if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040)
EEPROM.begin(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE);
#elif defined(ARDUINO_ARCH_APOLLO3)
EEPROM.init();
@ -128,7 +128,7 @@ void ArduinoHal::readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len)
for(size_t i = 0; i < len; i++) {
buff[i] = EEPROM.read(addr + i);
}
#if defined(RADIOLIB_ESP32)
#if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040)
EEPROM.end();
#endif
#endif
@ -136,7 +136,7 @@ void ArduinoHal::readPersistentStorage(uint32_t addr, uint8_t* buff, size_t len)
void ArduinoHal::writePersistentStorage(uint32_t addr, uint8_t* buff, size_t len) {
#if !defined(RADIOLIB_EEPROM_UNSUPPORTED)
#if defined(RADIOLIB_ESP32)
#if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040)
EEPROM.begin(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE);
#elif defined(ARDUINO_ARCH_APOLLO3)
EEPROM.init();
@ -144,7 +144,7 @@ void ArduinoHal::writePersistentStorage(uint32_t addr, uint8_t* buff, size_t len
for(size_t i = 0; i < len; i++) {
EEPROM.write(addr + i, buff[i]);
}
#if defined(RADIOLIB_ESP32)
#if defined(RADIOLIB_ESP32) || defined(ARDUINO_ARCH_RP2040)
EEPROM.commit();
EEPROM.end();
#endif

View file

@ -1436,8 +1436,9 @@ uint32_t SX126x::getTimeOnAir(size_t len) {
}
}
uint32_t SX126x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) {
(void)numSymbols; // not used for these modules
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);
}

View file

@ -950,12 +950,11 @@ class SX126x: public PhysicalLayer {
uint32_t getTimeOnAir(size_t len) override;
/*!
\brief Calculate the timeout value for this specific module / series based on number of symbols or time
\param numSymbols Number of payload symbols to listen for
\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(uint8_t numSymbols, uint32_t timeoutUs);
uint32_t calculateRxTimeout(uint32_t timeoutUs);
/*!
\brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks

View file

@ -411,7 +411,7 @@ int16_t SX127x::startReceive(uint8_t len, uint8_t mode) {
// timeout is only used in RxSingle, so when a packet length is defined, force mode to RxSingle
// and set the timeout value to the expected number of symbols (usually preamble + header)
if(len > 0) {
if((len > 0) && (this->spreadingFactor > 6)) {
mode = RADIOLIB_SX127X_RXSINGLE;
state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYMB_TIMEOUT_LSB, len);
}
@ -1274,17 +1274,19 @@ uint32_t SX127x::getTimeOnAir(size_t len) {
}
uint32_t SX127x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) {
(void)timeoutUs;
numSymbols = 20;
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) {
irqFlags = RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DONE;
irqMask = RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DONE;
irqFlags &= RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_TIMEOUT;
irqMask &= RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_TIMEOUT;
// 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);
}

View file

@ -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
@ -817,6 +818,7 @@ class SX127x: public PhysicalLayer {
\brief Interrupt-driven receive method. DIO0 will be activated when full valid packet is received.
\param len Expected length of packet to be received, or 0 when unused.
Defaults to 0, non-zero required for LoRa spreading factor 6.
If non-zero for LoRa spreading factor > 6, RxSingle is used and value must be given in symbols.
\param mode Receive mode to be used. Defaults to RxContinuous.
\returns \ref status_codes
*/
@ -1049,12 +1051,11 @@ class SX127x: public PhysicalLayer {
uint32_t getTimeOnAir(size_t len) override;
/*!
\brief Calculate the timeout value for this specific module / series based on number of symbols or time
\param numSymbols Number of payload symbols to listen for
\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(uint8_t numSymbols, uint32_t timeoutUs);
uint32_t calculateRxTimeout(uint32_t timeoutUs);
/*!
\brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks

View file

@ -31,7 +31,6 @@ uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
this->phyLayer = phy;
this->band = band;
this->FSK = false;
this->rx2 = this->band->rx2;
this->difsSlots = 2;
this->backoffMax = 6;
@ -51,7 +50,11 @@ void LoRaWANNode::wipe() {
}
int16_t LoRaWANNode::restore() {
// check the magic value
// if already joined, ignore
if(this->isJoinedFlag) {
return(RADIOLIB_ERR_NONE);
}
Module* mod = this->phyLayer->getMod();
uint8_t nvm_table_version = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID);
@ -60,8 +63,13 @@ int16_t LoRaWANNode::restore() {
// }
(void)nvm_table_version;
// check the magic value
if(mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) {
RADIOLIB_DEBUG_PRINTLN("magic id: %d", mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID));
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);
// the magic value is not set, user will have to do perform the join procedure
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
}
@ -126,6 +134,9 @@ int16_t LoRaWANNode::restore() {
state = this->setPhyProperties();
RADIOLIB_ASSERT(state);
// full session is restored, so set joined flag
this->isJoinedFlag = true;
return(RADIOLIB_ERR_NONE);
}
@ -179,21 +190,21 @@ int16_t LoRaWANNode::restoreChannels() {
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FREQS_ID), buffer, numBytes);
for(uint8_t dir = 0; dir < 2; dir++) {
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
uint8_t chBuff[5] = { 0 };
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<uint32_t>(&chBuff[1], 3);
this->availableChannels[dir][i].freq = (float)freq/10000.0;
this->availableChannels[dir][i].drMax = (chBuff[0] & 0xF0) >> 4;
this->availableChannels[dir][i].drMin = (chBuff[0] & 0x0F) >> 0;
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 drJoinSubband, bool force) {
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();
@ -210,7 +221,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
RADIOLIB_ASSERT(state);
// setup uplink/downlink frequencies and datarates
state = this->selectChannelsJR(this->devNonce, drJoinSubband);
state = this->selectChannelsJR(this->devNonce, joinDr);
RADIOLIB_ASSERT(state);
// configure for uplink with default configuration
@ -428,6 +439,8 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC);
#endif
this->isJoinedFlag = true;
return(RADIOLIB_ERR_NONE);
}
@ -480,9 +493,15 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey,
mod->hal->setPersistentParameter<uint16_t>(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();
@ -618,8 +637,8 @@ int16_t LoRaWANNode::saveChannels() {
uint8_t buffer[numBytes];
for(uint8_t dir = 0; dir < 2; dir++) {
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
uint8_t chBuff[5] = { 0 };
chBuff[0] |= (uint8_t)this->availableChannels[dir][i].enabled << 7;
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<uint32_t>(&chBuff[1], freq, 3);
@ -648,9 +667,10 @@ int16_t LoRaWANNode::uplink(const char* str, uint8_t port, bool isConfirmed) {
int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed) {
Module* mod = this->phyLayer->getMod();
// check if sufficient time has elapsed since the last uplink
if(mod->hal->millis() - this->rxDelayStart < this->rxDelays[1]) {
// not enough time elapsed since the last uplink, we may still be in an RX window
// 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);
}
@ -761,9 +781,12 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
}
// 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<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fcntUp);
@ -870,16 +893,30 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// 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() {
// check if there are any upcoming Rx windows
Module* mod = this->phyLayer->getMod();
const uint32_t scanGuard = 10;
if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) {
// time since last Tx is greater than RX2 delay + some guard period
// we have nothing to downlink
// 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);
}
@ -894,8 +931,8 @@ int16_t LoRaWANNode::downlinkCommon() {
}
// create the masks that are required for receiving downlinks
uint16_t irqFlags;
uint16_t irqMask;
uint16_t irqFlags = 0x0000;
uint16_t irqMask = 0x0000;
this->phyLayer->irqRxDoneRxTimeout(irqFlags, irqMask);
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction);
@ -908,7 +945,7 @@ int16_t LoRaWANNode::downlinkCommon() {
// 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(0, timeoutHost);
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
@ -943,6 +980,8 @@ int16_t LoRaWANNode::downlinkCommon() {
}
}
// 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()) {
@ -1201,6 +1240,14 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// 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
@ -1226,10 +1273,46 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
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);
}
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);
@ -1261,24 +1344,9 @@ 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
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(this->txPwrCur--);
@ -1383,17 +1451,21 @@ int16_t LoRaWANNode::setupChannels(uint8_t* cfList) {
}
int16_t LoRaWANNode::selectSubband(uint8_t idx) {
int16_t state = this->selectSubband((idx - 1) * 8, idx * 8);
int16_t state = this->selectSubband((idx - 1) * 8, idx * 8 - 1);
return(state);
}
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;
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);
@ -1421,7 +1493,7 @@ int16_t LoRaWANNode::selectSubband(uint8_t startChannel, uint8_t endChannel) {
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband) {
int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t joinDr) {
LoRaWANChannel_t channelUp;
LoRaWANChannel_t channelDown;
uint8_t drUp;
@ -1453,15 +1525,15 @@ int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband)
}
// if join datarate is user-specified and valid, select that value; otherwise use
if(drJoinSubband != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
if(drJoinSubband >= channelUp.drMin && drJoinSubband <= channelUp.drMax) {
drUp = drJoinSubband;
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", drJoinSubband, channelUp.drMin, channelUp.drMax);
drJoinSubband = RADIOLIB_LORAWAN_DATA_RATE_UNUSED;
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(drJoinSubband == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
if(joinDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
drUp = int((channelUp.drMax + channelUp.drMin) / 2);
}
@ -1470,27 +1542,39 @@ int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband)
drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, channelDown.drMin, channelDown.drMax);
} else { // RADIOLIB_LORAWAN_BAND_FIXED
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)
// if the user defined a specific subband, use that
if(drJoinSubband == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
blockID = (drJoinSubband - 1);
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;
}
}
uint8_t 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
uint8_t spanID;
if(channelID < 8) {
spanID = 0;
channelUp.idx = blockID * 8 + channelID;
} else {
spanID = 1;
channelUp.idx = blockID;
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;
}
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;
@ -1548,26 +1632,32 @@ int16_t LoRaWANNode::selectChannels() {
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, channelDn.drMin, channelDn.drMax);
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown;
}
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) {
if(drUp < this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMin) {
// 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);
}
if(drUp > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMax) {
return(RADIOLIB_ERR_DATA_RATE_INVALID);
}
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;
RADIOLIB_DEBUG_PRINTLN("Configured DR up = %d", drUp);
return(RADIOLIB_ERR_NONE);
}
@ -1615,11 +1705,24 @@ int16_t LoRaWANNode::configureChannel(uint8_t dir) {
int state = this->phyLayer->setFrequency(this->currentChannels[dir].freq);
RADIOLIB_ASSERT(state);
// set the data rate
DataRate_t dataRate;
findDataRate(this->dataRates[dir], &dataRate);
// 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;
}
DataRate_t dr;
findDataRate(this->dataRates[dir], &dr);
state = this->phyLayer->setDataRate(dr);
RADIOLIB_ASSERT(state);
if(this->FSK) {
state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0);
RADIOLIB_ASSERT(state);
state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING);
}
state = this->phyLayer->setDataRate(dataRate);
return(state);
}
@ -1695,7 +1798,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
uint16_t chMask = LoRaWANNode::ntoh<uint16_t>(&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%02x, chMaskCntl = %02x, nbTrans = %d", drUp, 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;
@ -1748,12 +1851,14 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
}
} else { // RADIOLIB_LORAWAN_BAND_FIXED
// delete any prior ADR responses from the uplink queue, but do not care about if none is present yet
// 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;
@ -1771,9 +1876,10 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
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][i] = chnl;
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", i, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq);
RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", num, chnl.idx, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
num++;
}
chNum++;
}

View file

@ -72,7 +72,7 @@
#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
#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)
@ -317,20 +317,6 @@ 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;
/*! \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;
// Offset between TX and RX1 (such that RX1 has equal or lower DR)
uint8_t rx1DrOffset;
@ -338,19 +324,6 @@ class LoRaWANNode {
// RX2 channel properties - may be changed by MAC command
LoRaWANChannel_t rx2;
/*!
\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;
/*!
\brief Default constructor.
\param phy Pointer to the PhysicalLayer radio module.
@ -379,12 +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 drJoinSubband (OTAA:) The datarate at which to send the join-request; (ABP:) the subband at which to send the join-request
\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, uint8_t joinDrSubband = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, 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.
@ -399,6 +372,9 @@ class LoRaWANNode {
*/
int16_t beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL, bool force = false);
/*! \brief 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.
@ -436,12 +412,6 @@ class LoRaWANNode {
*/
int16_t uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed = false);
/*!
\brief Wait for, open and listen during Rx1 and Rx2 windows; only performs listening
\returns \ref status_codes
*/
int16_t downlinkCommon();
#if defined(RADIOLIB_BUILD_ARDUINO)
/*!
\brief Wait for downlink from the server in either RX1 or RX2 window.
@ -459,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,
@ -466,9 +471,12 @@ 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
\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);
@ -480,16 +488,18 @@ class LoRaWANNode {
void setADR(bool enable = true);
/*!
\brief Select a single subband (8 channels) for fixed bands such as US915
\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
\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 (exclusive)
\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);
@ -545,8 +555,25 @@ class LoRaWANNode {
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
LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS] = { { 0 }, { 0 } };
@ -563,6 +590,9 @@ class LoRaWANNode {
// 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 };
@ -588,6 +618,9 @@ class LoRaWANNode {
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);

View file

@ -294,8 +294,7 @@ uint32_t PhysicalLayer::getTimeOnAir(size_t len) {
return(0);
}
uint32_t PhysicalLayer::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) {
(void)numSymbols;
uint32_t PhysicalLayer::calculateRxTimeout(uint32_t timeoutUs) {
(void)timeoutUs;
return(0);
}

View file

@ -311,12 +311,11 @@ class PhysicalLayer {
virtual uint32_t getTimeOnAir(size_t len);
/*!
\brief Calculate the timeout value for this specific module / series based on number of symbols or time
\param numSymbols Number of payload symbols to listen for
\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(uint8_t numSymbols, uint32_t timeoutUs);
virtual uint32_t calculateRxTimeout(uint32_t timeoutUs);
/*!
\brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks