Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LoRaWAN] Revamp internal processing, key checking, new MAC commands, implement DutyCycle & DwellTime #918

Merged
merged 9 commits into from
Jan 13, 2024
24 changes: 17 additions & 7 deletions examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
// include the library
#include <RadioLib.h>

// SX1278 has the following connections:
// NSS pin: 10
// DIO0 pin: 2
// RESET pin: 9
// DIO1 pin: 3
// SX1262 has the following pin order:
// Module(NSS/CS, DIO1, RESET, BUSY)
// SX1262 radio = new Module(8, 14, 12, 13);

// SX1278 has the following pin order:
// Module(NSS/CS, DIO0, RESET, DIO1)
SX1278 radio = new Module(10, 2, 9, 3);

// create the node instance on the EU-868 band
Expand Down Expand Up @@ -85,6 +86,11 @@ void setup() {
node.selectSubband(8, 15);
*/

// on EEPROM-enabled boards, after the device has been activated,
// the session can be restored without rejoining after device power cycle
// this is intrinsically done when calling `beginOTAA()` with the same keys
// in that case, the function will not need to transmit a JoinRequest

// 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):
Expand Down Expand Up @@ -147,7 +153,11 @@ void loop() {
Serial.print(F("failed, code "));
Serial.println(state);
}

// wait before sending another packet
delay(30000);
uint32_t minimumDelay = 60000; // try to send once every minute
uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per law!)
uint32_t delayMs = max(interval, minimumDelay); // cannot send faster than duty cycle allows
StevenCellist marked this conversation as resolved.
Show resolved Hide resolved

delay(delayMs);
}
38 changes: 28 additions & 10 deletions examples/LoRaWAN/LoRaWAN_End_Device_ABP/LoRaWAN_End_Device_ABP.ino
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
// include the library
#include <RadioLib.h>

// SX1278 has the following connections:
// NSS pin: 10
// DIO0 pin: 2
// RESET pin: 9
// DIO1 pin: 3
// SX1262 has the following pin order:
// Module(NSS/CS, DIO1, RESET, BUSY)
// SX1262 radio = new Module(8, 14, 12, 13);

// SX1278 has the following pin order:
// Module(NSS/CS, DIO0, RESET, DIO1)
SX1278 radio = new Module(10, 2, 9, 3);

// create the node instance on the EU-868 band
Expand Down Expand Up @@ -69,6 +70,14 @@ void setup() {
uint8_t appSKey[] = { 0x61, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65,
0x6E, 0x74, 0x4B, 0x65, 0x79, 0x41, 0x42, 0x43 };

// network key 2 is the ASCII string "topSecretKey5678"
uint8_t fNwkSIntKey[] = { 0x61, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65,
0x6E, 0x74, 0x4B, 0x65, 0x35, 0x36, 0x37, 0x38 };

// network key 3 is the ASCII string "aDifferentKeyDEF"
uint8_t sNwkSIntKey[] = { 0x61, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65,
0x6E, 0x74, 0x4B, 0x65, 0x79, 0x44, 0x45, 0x46 };

// 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
Expand All @@ -86,15 +95,20 @@ void setup() {
node.rx2.drMax = 3;
*/

// to start a LoRaWAN v1.1 session, the user should also provide
// fNwkSIntKey and sNwkSIntKey similar to nwkSKey and appSKey
// on EEPROM-enabled boards, after the device has been activated,
// the session can be restored without rejoining after device power cycle
// this is intrinsically done when calling `beginABP()` with the same keys
// in that case, the function will not need to transmit a JoinRequest

// to start a LoRaWAN v1.0 session,
// the user can remove the fNwkSIntKey and sNwkSIntKey
/*
state = node.beginABP(devAddr, nwkSKey, appSKey, fNwkSIntKey, sNwkSIntKey);
state = node.beginABP(devAddr, nwkSKey, appSKey);
*/

// start the device by directly providing the encryption keys and device address
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginABP(devAddr, nwkSKey, appSKey);
state = node.beginABP(devAddr, nwkSKey, appSKey, fNwkSIntKey, sNwkSIntKey);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
Expand Down Expand Up @@ -149,5 +163,9 @@ void loop() {
}

// wait before sending another packet
delay(30000);
uint32_t minimumDelay = 60000; // try to send once every minute
uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per law!)
uint32_t delayMs = max(interval, minimumDelay); // cannot send faster than duty cycle allows
StevenCellist marked this conversation as resolved.
Show resolved Hide resolved

delay(delayMs);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

NOTE: LoRaWAN requires storing some parameters persistently!
RadioLib does this by using EEPROM, by default
starting at address 0 and using 384 bytes.
starting at address 0 and using 448 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
Expand All @@ -29,11 +29,12 @@
// include the library
#include <RadioLib.h>

// SX1278 has the following connections:
// NSS pin: 10
// DIO0 pin: 2
// RESET pin: 9
// DIO1 pin: 3
// SX1262 has the following pin order:
// Module(NSS/CS, DIO1, RESET, BUSY)
// SX1262 radio = new Module(8, 14, 12, 13);

// SX1278 has the following pin order:
// Module(NSS/CS, DIO0, RESET, DIO1)
SX1278 radio = new Module(10, 2, 9, 3);

// create the node instance on the EU-868 band
Expand All @@ -56,33 +57,31 @@ 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!
// 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
// start the activation
// Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
// uint64_t joinEUI = 0x12AD1011B0C0FFEE;
// uint64_t devEUI = 0x70B3D57ED005E120;
// 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,
// on EEPROM-enabled boards, after the device has been activated,
// the session can be restored without rejoining after device power cycle
// on EEPROM-enabled boards by calling "restore"
// by calling the same `beginOTAA()` or `beginABP()` function with the same keys
// or call `restore()` where it will restore any existing session
// `restore()` returns the active mode if it succeeded (OTAA or ABP)
Serial.print(F("[LoRaWAN] Resuming previous session ... "));
state = node.restore();
if(state == RADIOLIB_ERR_NONE) {
if(state >= RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
Serial.print(F("Restored an "));
if(state == RADIOLIB_LORAWAN_MODE_OTAA)
StevenCellist marked this conversation as resolved.
Show resolved Hide resolved
Serial.println(F("OTAA session."));
else {
Serial.println(F("ABP session."));
}
} else {
Serial.print(F("failed, code "));
Serial.println(state);
Expand Down Expand Up @@ -141,5 +140,9 @@ void loop() {
// 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);
uint32_t minimumDelay = 60000; // try to send once every minute
uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per law!)
uint32_t delayMs = max(interval, minimumDelay); // cannot send faster than duty cycle allows
StevenCellist marked this conversation as resolved.
Show resolved Hide resolved

delay(delayMs);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
// include the library
#include <RadioLib.h>

// SX1278 has the following connections:
// NSS pin: 10
// DIO0 pin: 2
// RESET pin: 9
// DIO1 pin: 3
// SX1262 has the following pin order:
// Module(NSS/CS, DIO1, RESET, BUSY)
// SX1262 radio = new Module(8, 14, 12, 13);

// SX1278 has the following pin order:
// Module(NSS/CS, DIO0, RESET, DIO1)
SX1278 radio = new Module(10, 2, 9, 3);

// create the node instance on the EU-868 band
Expand Down Expand Up @@ -106,9 +107,11 @@ void setup() {
while(true);
}

// after the device has been activated,
// on EEPROM-enabled boards, after the device has been activated,
// the session can be restored without rejoining after device power cycle
// on EEPROM-enabled boards by calling "restore"
// this is intrinsically done when calling `beginOTAA()` with the same keys
// or if you 'lost' the keys or don't want them included in your sketch
// you can call `restore()`
/*
Serial.print(F("[LoRaWAN] Resuming previous session ... "));
state = node.restore();
Expand All @@ -126,11 +129,28 @@ void setup() {

// set a fixed datarate
node.setDatarate(5);
// in order to save the datarate persistent across reboot/deepsleep, use the following:
/*
node.setDatarate(5, true);
*/

// enable CSMA
// this tries to minimize packet loss by searching for a free channel
// before actually sending an uplink
node.setCSMA(6, 2, true);

// enable or disable the dutycycle
// the second argument specific allowed airtime per hour in milliseconds
// 1250 = TTN FUP (30 seconds / 24 hours)
// if not called, this corresponds to setDutyCycle(true, 0)
// setting this to 0 corresponds to the band's maximum allowed dutycycle by law
node.setDutyCycle(true, 1250);

// enable or disable the dwell time limits
// the second argument specific allowed airtime per uplink in milliseconds
// if not called, this corresponds to setDwellTime(true, 0)
// setting this to 0 corresponds to the band's maximum allowed dwell time by law
node.setDwellTime(true, 1000);
}

void loop() {
Expand All @@ -152,8 +172,11 @@ void loop() {
String strUp = "Hello World! #" + String(fcntUp);

// send a confirmed uplink to port 10 every 64th frame
// and also request the LinkCheck and DeviceTime MAC commands
if(fcntUp % 64 == 0) {
state = node.uplink(strUp, 10, true);
node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_LINK_CHECK);
node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_DEVICE_TIME);
} else {
state = node.uplink(strUp, 10);
}
Expand Down Expand Up @@ -228,6 +251,24 @@ void loop() {
Serial.println(event.port);

Serial.print(radio.getFrequencyError());

uint8_t margin = 0;
uint8_t gwCnt = 0;
if(node.getMacLinkCheckAns(&margin, &gwCnt)) {
Serial.print(F("[LoRaWAN] LinkCheck margin:\t"));
Serial.println(margin);
Serial.print(F("[LoRaWAN] LinkCheck count:\t"));
Serial.println(gwCnt);
}

uint32_t networkTime = 0;
uint8_t fracSecond = 0;
if(node.getMacDeviceTimeAns(&networkTime, &fracSecond, true)) {
Serial.print(F("[LoRaWAN] DeviceTime Unix:\t"));
Serial.println(networkTime);
Serial.print(F("[LoRaWAN] LinkCheck second:\t1/"));
Serial.println(fracSecond);
}

} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
Serial.println(F("timeout!"));
Expand All @@ -244,5 +285,9 @@ void loop() {
*/

// wait before sending another packet
delay(30000);
uint32_t minimumDelay = 60000; // try to send once every minute
uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per law!)
uint32_t delayMs = max(interval, minimumDelay); // cannot send faster than duty cycle allows

delay(delayMs);
}
9 changes: 9 additions & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -295,18 +295,27 @@ restore KEYWORD2
beginOTAA KEYWORD2
beginABP KEYWORD2
saveSession KEYWORD2
sendMacCommandReq KEYWORD2
uplink KEYWORD2
downlink KEYWORD2
sendReceive KEYWORD2
setDeviceStatus KEYWORD2
getFcntUp KEYWORD2
getNFcntDown KEYWORD2
getAFcntDown KEYWORD2
resetFcntDown KEYWORD2
setDatarate KEYWORD2
setADR KEYWORD2
setDutyCycle KEYWORD2
dutyCycleInterval KEYWORD2
timeUntilUplink KEYWORD2
setDwellTime KEYWORD2
maxPayloadDwellTime KEYWORD2
setTxPower KEYWORD2
selectSubband KEYWORD2
setCSMA KEYWORD2
getMacLinkCheckAns KEYWORD2
getMacDeviceTimeAns KEYWORD2

#######################################
# Constants (LITERAL1)
Expand Down
2 changes: 1 addition & 1 deletion src/BuildOpt.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@

// the amount of space allocated to the persistent storage
#if !defined(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE)
#define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x0180)
#define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x01C0)
#endif

/*
Expand Down