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

This commit is contained in:
StevenCellist 2024-01-05 11:06:24 +01:00
parent 797b7a4323
commit 574555ca09
11 changed files with 1258 additions and 554 deletions

View file

@ -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
@ -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):
@ -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
delay(delayMs);
}

View file

@ -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
@ -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
@ -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 {
@ -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
delay(delayMs);
}

View file

@ -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
@ -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
@ -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)
Serial.println(F("OTAA session."));
else {
Serial.println(F("ABP session."));
}
} else {
Serial.print(F("failed, code "));
Serial.println(state);
@ -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
delay(delayMs);
}

View file

@ -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
@ -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();
@ -131,6 +134,19 @@ void setup() {
// 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() {
@ -152,8 +168,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);
}
@ -228,6 +247,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!"));
@ -244,5 +281,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);
}

View file

@ -295,6 +295,7 @@ restore KEYWORD2
beginOTAA KEYWORD2
beginABP KEYWORD2
saveSession KEYWORD2
sendMacCommandReq KEYWORD2
uplink KEYWORD2
downlink KEYWORD2
sendReceive KEYWORD2
@ -302,11 +303,19 @@ 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)

View file

@ -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
/*

128
src/Hal.h
View file

@ -6,60 +6,86 @@
#include "BuildOpt.h"
#define RADIOLIB_EEPROM_TABLE_VERSION (0x0002)
// list of persistent parameters
#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)
enum RADIOLIB_EEPROM_PARAMS {
RADIOLIB_EEPROM_TABLE_VERSION_ID, // table layout version
RADIOLIB_EEPROM_LORAWAN_CLASS_ID, // class A, B or C
RADIOLIB_EEPROM_LORAWAN_MODE_ID, // none, OTAA or ABP
RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID, // checksum of keys used for device activation
RADIOLIB_EEPROM_LORAWAN_VERSION_ID, // LoRaWAN version
RADIOLIB_EEPROM_LORAWAN_LAST_TIME_ID, // last heard time through DeviceTimeReq or Beacon
RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID,
RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID,
RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID,
RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID,
RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID,
RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID,
RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID,
RADIOLIB_EEPROM_LORAWAN_HOME_NET_ID,
RADIOLIB_EEPROM_LORAWAN_A_FCNT_DOWN_ID,
RADIOLIB_EEPROM_LORAWAN_N_FCNT_DOWN_ID,
RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_UP_ID,
RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_DOWN_ID,
RADIOLIB_EEPROM_LORAWAN_ADR_FCNT_ID,
RADIOLIB_EEPROM_LORAWAN_RJ_COUNT0_ID,
RADIOLIB_EEPROM_LORAWAN_RJ_COUNT1_ID,
RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID,
RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID,
RADIOLIB_EEPROM_LORAWAN_DUTY_CYCLE_ID,
RADIOLIB_EEPROM_LORAWAN_RX_PARAM_SETUP_ID,
RADIOLIB_EEPROM_LORAWAN_RX_TIMING_SETUP_ID,
RADIOLIB_EEPROM_LORAWAN_TX_PARAM_SETUP_ID,
RADIOLIB_EEPROM_LORAWAN_ADR_PARAM_SETUP_ID,
RADIOLIB_EEPROM_LORAWAN_REJOIN_PARAM_SETUP_ID,
RADIOLIB_EEPROM_LORAWAN_BEACON_FREQ_ID,
RADIOLIB_EEPROM_LORAWAN_PING_SLOT_CHANNEL_ID,
RADIOLIB_EEPROM_LORAWAN_PERIODICITY_ID,
RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID,
RADIOLIB_EEPROM_LORAWAN_MAC_QUEUE_UL_ID,
RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID,
RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID
};
static const uint32_t RadioLibPersistentParamTable[] = {
0x00, // RADIOLIB_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_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
0x00, // RADIOLIB_EEPROM_LORAWAN_TABLE_VERSION_ID
0x02, // RADIOLIB_EEPROM_LORAWAN_CLASS_ID
0x03, // RADIOLIB_EEPROM_LORAWAN_MODE_ID
0x05, // RADIOLIB_EEPROM_LORAWAN_CHECKSUM_ID
0x07, // RADIOLIB_EEPROM_LORAWAN_VERSION_ID
0x08, // RADIOLIB_EEPROM_LORAWAN_LAST_TIME_ID
0x0C, // RADIOLIB_EEPROM_LORAWAN_DEV_ADDR_ID
0x10, // RADIOLIB_EEPROM_LORAWAN_APP_S_KEY_ID
0x20, // RADIOLIB_EEPROM_LORAWAN_FNWK_SINT_KEY_ID
0x30, // RADIOLIB_EEPROM_LORAWAN_SNWK_SINT_KEY_ID
0x40, // RADIOLIB_EEPROM_LORAWAN_NWK_SENC_KEY_ID
0x50, // RADIOLIB_EEPROM_LORAWAN_DEV_NONCE_ID
0x54, // RADIOLIB_EEPROM_LORAWAN_JOIN_NONCE_ID
0x58, // RADIOLIB_EEPROM_LORAWAN_HOME_NET_ID
0x5C, // RADIOLIB_EEPROM_LORAWAN_A_FCNT_DOWN_ID
0x60, // RADIOLIB_EEPROM_LORAWAN_N_FCNT_DOWN_ID
0x64, // RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_UP_ID
0x68, // RADIOLIB_EEPROM_LORAWAN_CONF_FCNT_DOWN_ID
0x6C, // RADIOLIB_EEPROM_LORAWAN_ADR_FCNT_ID
0x70, // RADIOLIB_EEPROM_LORAWAN_RJ_COUNT0_ID
0x72, // RADIOLIB_EEPROM_LORAWAN_RJ_COUNT1_ID
0x74, // RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID
0xA0, // RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID - NOTE change upon ADR Ack Req
0xA4, // RADIOLIB_EEPROM_LORAWAN_DUTY_CYCLE_ID
0xA5, // RADIOLIB_EEPROM_LORAWAN_RX_PARAM_SETUP_ID
0xA9, // RADIOLIB_EEPROM_LORAWAN_RX_TIMING_SETUP_ID
0xAA, // RADIOLIB_EEPROM_LORAWAN_TX_PARAM_SETUP_ID
0xAB, // RADIOLIB_EEPROM_LORAWAN_ADR_PARAM_SETUP_ID
0xAC, // RADIOLIB_EEPROM_LORAWAN_REJOIN_PARAM_SETUP_ID
0xAD, // RADIOLIB_EEPROM_LORAWAN_BEACON_FREQ_ID
0xB0, // RADIOLIB_EEPROM_LORAWAN_PING_SLOT_CHANNEL_ID
0xB4, // RADIOLIB_EEPROM_LORAWAN_PERIODICITY_ID
0xB5, // RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID
0xB6, // RADIOLIB_EEPROM_LORAWAN_MAC_QUEUE_UL_ID
0x0100, // RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID
0x0180, // RADIOLIB_EEPROM_LORAWAN_DL_CHANNELS_ID
0x01C0, // end
};
/*!

View file

@ -519,7 +519,7 @@
#define RADIOLIB_ERR_INVALID_CID (-1107)
/*!
\brief User requested to start uplink while still inside RX window.
\brief User requested to start uplink while still inside RX window or under dutycycle.
*/
#define RADIOLIB_ERR_UPLINK_UNAVAILABLE (-1108)

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,15 @@
#include "../PhysicalLayer/PhysicalLayer.h"
#include "../../utils/Cryptography.h"
// version of NVM table layout (NOT the LoRaWAN version)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION (0x01)
// activation mode
#define RADIOLIB_LORAWAN_MODE_OTAA (0x01AA)
#define RADIOLIB_LORAWAN_MODE_ABP (0x0AB9)
#define RADIOLIB_LORAWAN_MODE_NONE (0x0000)
// operation mode
#define RADIOLIB_LORAWAN_CLASS_A (0x00)
#define RADIOLIB_LORAWAN_CLASS_B (0x01)
#define RADIOLIB_LORAWAN_CLASS_C (0x02)
// preamble format
#define RADIOLIB_LORAWAN_LORA_SYNC_WORD (0x34)
@ -77,7 +84,6 @@
// recommended default settings
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000)
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS ((RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS) + 1000)
#define RADIOLIB_LORAWAN_RX_WINDOW_LEN_MS (500)
#define RADIOLIB_LORAWAN_RX1_DR_OFFSET (0)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
@ -87,6 +93,8 @@
#define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MIN_MS (1000)
#define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MAX_MS (3000)
#define RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM (-2)
#define RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N (10) // send rejoin request 16384 uplinks
#define RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N (15) // once every year, not actually implemented
// join request message layout
#define RADIOLIB_LORAWAN_JOIN_REQUEST_LEN (23)
@ -155,32 +163,61 @@
#define RADIOLIB_LORAWAN_MAGIC (0x39EA)
// MAC commands
#define RADIOLIB_LORAWAN_MAC_CMD_RESET (0x01)
#define RADIOLIB_LORAWAN_MAC_CMD_LINK_CHECK (0x02)
#define RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR (0x03)
#define RADIOLIB_LORAWAN_MAC_CMD_DUTY_CYCLE (0x04)
#define RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP (0x05)
#define RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS (0x06)
#define RADIOLIB_LORAWAN_MAC_CMD_NEW_CHANNEL (0x07)
#define RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP (0x08)
#define RADIOLIB_LORAWAN_MAC_CMD_TX_PARAM_SETUP (0x09)
#define RADIOLIB_LORAWAN_MAC_CMD_DL_CHANNEL (0x0A)
#define RADIOLIB_LORAWAN_MAC_CMD_REKEY (0x0B)
#define RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP (0x0C)
#define RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME (0x0D)
#define RADIOLIB_LORAWAN_MAC_CMD_FORCE_REJOIN (0x0E)
#define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP (0x0F)
#define RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY (0x80)
#define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (16)
#define RADIOLIB_LORAWAN_MAC_RESET (0x01)
#define RADIOLIB_LORAWAN_MAC_LINK_CHECK (0x02)
#define RADIOLIB_LORAWAN_MAC_LINK_ADR (0x03)
#define RADIOLIB_LORAWAN_MAC_DUTY_CYCLE (0x04)
#define RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP (0x05)
#define RADIOLIB_LORAWAN_MAC_DEV_STATUS (0x06)
#define RADIOLIB_LORAWAN_MAC_NEW_CHANNEL (0x07)
#define RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP (0x08)
#define RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP (0x09)
#define RADIOLIB_LORAWAN_MAC_DL_CHANNEL (0x0A)
#define RADIOLIB_LORAWAN_MAC_REKEY (0x0B)
#define RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP (0x0C)
#define RADIOLIB_LORAWAN_MAC_DEVICE_TIME (0x0D)
#define RADIOLIB_LORAWAN_MAC_FORCE_REJOIN (0x0E)
#define RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP (0x0F)
#define RADIOLIB_LORAWAN_MAC_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)
#define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (9)
// the maximum number of simultaneously available channels
#define RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS (16)
struct LoRaWANMacSpec_t {
const uint8_t cid;
const uint8_t lenDn;
const uint8_t lenUp;
const bool user; // whether this MAC command can be issued by a user or not
};
const LoRaWANMacSpec_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS + 1] = {
{ 0x00, 0, 0, false }, // not an actual MAC command, exists for offsetting
{ 0x01, 1, 1, false }, // RADIOLIB_LORAWAN_MAC_RESET
{ 0x02, 2, 0, true }, // RADIOLIB_LORAWAN_MAC_LINK_CHECK
{ 0x03, 4, 1, false }, // RADIOLIB_LORAWAN_MAC_LINK_ADR
{ 0x04, 1, 0, false }, // RADIOLIB_LORAWAN_MAC_DUTY_CYCLE
{ 0x05, 4, 1, false }, // RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP
{ 0x06, 0, 2, false }, // RADIOLIB_LORAWAN_MAC_DEV_STATUS
{ 0x07, 5, 1, false }, // RADIOLIB_LORAWAN_MAC_NEW_CHANNEL
{ 0x08, 1, 0, false }, // RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP
{ 0x09, 1, 0, false }, // RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP
{ 0x0A, 4, 1, false }, // RADIOLIB_LORAWAN_MAC_DL_CHANNEL
{ 0x0B, 1, 1, false }, // RADIOLIB_LORAWAN_MAC_REKEY
{ 0x0C, 1, 0, false }, // RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP
{ 0x0D, 5, 0, true }, // RADIOLIB_LORAWAN_MAC_DEVICE_TIME
{ 0x0E, 2, 0, false }, // RADIOLIB_LORAWAN_MAC_FORCE_REJOIN
{ 0x0F, 1, 1, false }, // RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP
{ 0x80, 5, 0, true } // RADIOLIB_LORAWAN_MAC_PROPRIETARY
};
/*!
\struct LoRaWANChannelSpan_t
\brief Structure to save information about LoRaWAN channels.
@ -251,6 +288,13 @@ struct LoRaWANBand_t {
/*! \brief Number of power steps in this band */
int8_t powerNumSteps;
/*! \brief Number of milliseconds per hour of allowed Time-on-Air */
uint32_t dutyCycle;
/*! \brief Maximum dwell time per message in milliseconds */
uint32_t dwellTimeUp;
uint32_t dwellTimeDn;
/*! \brief A set of default uplink (TX) channels for frequency-type bands */
LoRaWANChannel_t txFreqs[3];
@ -371,7 +415,8 @@ class LoRaWANNode {
/*!
\brief Restore session by loading information from persistent storage.
\returns \ref status_codes
\returns \ref status_codes in case of error,
else LoRaWAN session mode (0 = no active session, 0xAA / 170 = OTAA, 0xAB / 171 = ABP)
*/
int16_t restore();
#endif
@ -413,6 +458,15 @@ class LoRaWANNode {
*/
int16_t saveSession();
/*!
\brief Add a MAC command to the uplink queue.
Only LinkCheck and DeviceTime are available to the user.
Other commands are ignored; duplicate MAC commands are discarded.
\param cid ID of the MAC command
\returns Whether or not the MAC command was added to the queue.
*/
bool sendMacCommandReq(uint8_t cid);
#if defined(RADIOLIB_BUILD_ARDUINO)
/*!
\brief Send a message to the server.
@ -533,6 +587,12 @@ class LoRaWANNode {
/*! \brief Returns the last application downlink's frame counter */
uint32_t getAFcntDown();
/*! \brief Reset the downlink frame counters (application and network)
This is unsafe and can possibly allow replay attacks using downlinks.
It mainly exists as part of the TS008 Specification Verification protocol.
*/
void resetFcntDown();
/*!
\brief Set uplink datarate. This should not be used when ADR is enabled.
\param dr Datarate to use for uplinks.
@ -546,6 +606,41 @@ class LoRaWANNode {
*/
void setADR(bool enable = true);
/*!
\brief Toggle adherence to dutyCycle limits to on or off.
\param enable Whether to adhere to dutyCycle limits or not (default true).
\param msPerHour The maximum allowed Time-on-Air per hour in milliseconds
(default 0 = maximum allowed for configured band).
*/
void setDutyCycle(bool enable = true, uint32_t msPerHour = 0);
/*!
\brief Calculate the minimum interval to adhere to a certain dutyCycle.
This interval is based on the ToA of one uplink and does not actually keep track of total airtime.
\param msPerHour The maximum allowed dutycyle (in milliseconds per hour).
\param airtime The airtime of the uplink.
\returns Required interval (delay) in milliseconds between consecutive uplinks.
*/
uint32_t dutyCycleInterval(uint32_t msPerHour, uint32_t airtime);
/*! \brief Returns time in milliseconds until next uplink is available under dutyCycle limits */
uint32_t timeUntilUplink();
/*!
\brief Toggle adherence to dwellTime limits to on or off.
\param enable Whether to adhere to dwellTime limits or not (default true).
\param msPerHour The maximum allowed Time-on-Air per uplink in milliseconds
(default 0 = maximum allowed for configured band).
*/
void setDwellTime(bool enable, uint32_t msPerUplink = 0);
/*!
\brief Returns the maximum payload given the currently present dwelltime limits.
WARNING: the addition of MAC commands may cause uplink errors;
if you want to be sure that your payload fits within dwelltime limits, subtract 16 from the result!
*/
uint8_t maxPayloadDwellTime();
/*!
\brief Configure TX power of the radio module.
\param txPower Output power during TX mode to be set in dBm.
@ -578,12 +673,35 @@ class LoRaWANNode {
*/
void setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA = false);
/*!
\brief Returns the quality of connectivity after requesting a LinkCheck MAC command.
Returns 'true' if a network response was succesfully parsed.
Returns 'false' if there was no network response / parsing failed.
\param margin Link margin in dB of LinkCheckReq demodulation at gateway side.
\param gwCnt Number of gateways that received the LinkCheckReq.
\returns Whether the parameters where succesfully parsed.
*/
bool getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt);
/*!
\brief Returns the network time after requesting a DeviceTime MAC command.
Returns 'true' if a network response was succesfully parsed.
Returns 'false' if there was no network response / parsing failed.
\param gpsEpoch Number of seconds since GPS epoch (Jan. 6th 1980)
\param fraction Fractional-second, in 1/256-second steps
\param returnUnix If true, returns Unix timestamp instead of GPS (default true)
\returns Whether the parameters where succesfully parsed.
*/
bool getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix = true);
#if !RADIOLIB_GODMODE
private:
#endif
PhysicalLayer* phyLayer = NULL;
const LoRaWANBand_t* band = NULL;
void beginCommon();
LoRaWANMacCommandQueue_t commandsUp = {
.numCommands = 0,
.len = 0,
@ -613,7 +731,8 @@ class LoRaWANNode {
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;
uint8_t txPowerCur = 0;
uint8_t txPowerMax = 0;
uint32_t fcntUp = 0;
uint32_t aFcntDown = 0;
uint32_t nFcntDown = 0;
@ -624,11 +743,21 @@ class LoRaWANNode {
// whether the current configured channel is in FSK mode
bool FSK = false;
// flag that shows whether the device is joined and there is an ongoing session
bool isJoinedFlag = false;
// flag that shows whether the device is joined and there is an ongoing session (none, ABP or OTAA)
uint16_t activeMode = 0;
// ADR is enabled by default
bool adrEnabled = true;
// duty cycle is set upon initialization and activated in regions that impose this
bool dutyCycleEnabled = false;
uint32_t dutyCycle = 0;
// dwell time is set upon initialization and activated in regions that impose this
bool dwellTimeEnabledUp = false;
uint16_t dwellTimeUp = 0;
bool dwellTimeEnabledDn = false;
uint16_t dwellTimeDn = 0;
// enable/disable CSMA for LoRaWAN
bool enableCSMA;
@ -653,6 +782,9 @@ class LoRaWANNode {
// LoRaWAN revision (1.0 vs 1.1)
uint8_t rev = 0;
// Time on Air of last uplink
uint32_t lastToA = 0;
// timestamp to measure the RX1/2 delay (from uplink end)
uint32_t rxDelayStart = 0;
@ -714,9 +846,6 @@ class LoRaWANNode {
// configure channel based on cached data rate ID and frequency
int16_t configureChannel(uint8_t dir);
// save all available channels to persistent storage
int16_t saveChannels();
// restore all available channels from persistent storage
int16_t restoreChannels();
@ -724,10 +853,14 @@ class LoRaWANNode {
int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue);
// delete a specific MAC command from queue, indicated by the command ID
int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue);
// if a payload pointer is supplied, this returns the payload of the MAC command
int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue, uint8_t payload[5] = NULL);
// execute mac command, return the number of processed bytes for sequential processing
size_t execMacCommand(LoRaWANMacCommand_t* cmd);
bool execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom = true);
// get the payload length for a specific MAC command
uint8_t getMacPayloadLength(uint8_t cid);
// Performs CSMA as per LoRa Alliance Technical Reccomendation 13 (TR-013).
void performCSMA();

View file

@ -7,6 +7,9 @@ const LoRaWANBand_t EU868 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 16,
.powerNumSteps = 7,
.dutyCycle = 36000,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
{ .enabled = true, .idx = 0, .freq = 868.100, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 868.300, .drMin = 0, .drMax = 5},
@ -49,6 +52,9 @@ const LoRaWANBand_t US915 = {
.payloadLenMax = { 19, 61, 133, 250, 250, 0, 0, 0, 41, 117, 230, 230, 230, 230, 0 },
.powerMax = 30,
.powerNumSteps = 10,
.dutyCycle = 0,
.dwellTimeUp = 400,
.dwellTimeDn = 0,
.txFreqs = {
RADIOLIB_LORAWAN_CHANNEL_NONE,
RADIOLIB_LORAWAN_CHANNEL_NONE,
@ -112,6 +118,9 @@ const LoRaWANBand_t CN780 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 250, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 12,
.powerNumSteps = 5,
.dutyCycle = 3600,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
{ .enabled = true, .idx = 0, .freq = 779.500, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 779.700, .drMin = 0, .drMax = 5},
@ -154,6 +163,9 @@ const LoRaWANBand_t EU433 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 12,
.powerNumSteps = 5,
.dutyCycle = 36000,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
{ .enabled = true, .idx = 0, .freq = 433.175, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 433.375, .drMin = 0, .drMax = 5},
@ -196,6 +208,9 @@ const LoRaWANBand_t AU915 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 0, 41, 117, 230, 230, 230, 230, 0 },
.powerMax = 30,
.powerNumSteps = 10,
.dutyCycle = 0,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
RADIOLIB_LORAWAN_CHANNEL_NONE,
RADIOLIB_LORAWAN_CHANNEL_NONE,
@ -259,6 +274,9 @@ const LoRaWANBand_t CN500 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 19,
.powerNumSteps = 7,
.dutyCycle = 0,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
RADIOLIB_LORAWAN_CHANNEL_NONE,
RADIOLIB_LORAWAN_CHANNEL_NONE,
@ -315,6 +333,9 @@ const LoRaWANBand_t AS923 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 16,
.powerNumSteps = 7,
.dutyCycle = 36000,
.dwellTimeUp = 400,
.dwellTimeDn = 400,
.txFreqs = {
{ .enabled = true, .idx = 0, .freq = 923.200, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 923.400, .drMin = 0, .drMax = 5},
@ -357,6 +378,9 @@ const LoRaWANBand_t KR920 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 14,
.powerNumSteps = 7,
.dutyCycle = 0,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
{ .enabled = true, .idx = 0, .freq = 922.100, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 922.300, .drMin = 0, .drMax = 5},
@ -399,6 +423,9 @@ const LoRaWANBand_t IN865 = {
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 30,
.powerNumSteps = 10,
.dutyCycle = 0,
.dwellTimeUp = 0,
.dwellTimeDn = 0,
.txFreqs = {
{ .enabled = true, .idx = 0, .freq = 865.0625, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 865.4025, .drMin = 0, .drMax = 5},