kopia lustrzana https://github.com/jgromes/RadioLib
[LoRaWAN] Provide support for TSxxx packages (including TS009 reference implementation) (#1528)
* [LoRaWAN] Fix for incorrect use of dutycycle function * [LoRaWAN] Implement TSxxx package support And fix an MIC problem for >16-bit downlink FCnt * Revert unused error code * [LoRaWAN] Add TS009 example * [LoRaWAN] Comment a platform dependency in TS009 header * [LoRaWAN] Make TSxxx example platform independent * [LoRaWAN] Remove unused variable * [LoRaWAN] Remove printf from example * [LoRaWAN] Fix scope of variables * [LoRaWAN] Remove printf from example * [LoRaWAN] Fix cppcheck issue * [LoRaWAN] Feedback improvementspull/1535/head
rodzic
83fbedb833
commit
f2fbd73044
|
@ -0,0 +1,239 @@
|
|||
#include <Arduino.h>
|
||||
#include <RadioLib.h>
|
||||
// #include <RadioBoards.h>
|
||||
|
||||
#warning "The variables below must match your main code. Please check your radio type!"
|
||||
extern SX1278 radio; // this can be any LoRaWAN-compatible type (e.g. SX1262)
|
||||
extern LoRaWANNode node;
|
||||
extern uint32_t periodicity;
|
||||
extern bool isConfirmed;
|
||||
extern bool reply;
|
||||
extern uint8_t dataUp[255];
|
||||
extern size_t lenUp;
|
||||
extern uint8_t fPort;
|
||||
|
||||
#define RADIOLIB_LORAWAN_TS009_PACKAGE_VERSION (0x00)
|
||||
#define RADIOLIB_LORAWAN_TS009_DUT_RESET (0x01)
|
||||
#define RADIOLIB_LORAWAN_TS009_DUT_JOIN (0x02)
|
||||
#define RADIOLIB_LORAWAN_TS009_SWITCH_CLASS (0x03)
|
||||
#define RADIOLIB_LORAWAN_TS009_ADR_BIT_CHANGE (0x04)
|
||||
#define RADIOLIB_LORAWAN_TS009_REGIONAL_DUTY_CYCLE (0x05)
|
||||
#define RADIOLIB_LORAWAN_TS009_TX_PERIODICITY_CHANGE (0x06)
|
||||
#define RADIOLIB_LORAWAN_TS009_TX_FRAMES_CTRL (0x07)
|
||||
#define RADIOLIB_LORAWAN_TS009_ECHO_PAYLOAD (0x08)
|
||||
#define RADIOLIB_LORAWAN_TS009_RX_APP_CNT (0x09)
|
||||
#define RADIOLIB_LORAWAN_TS009_RX_APP_CNT_RESET (0x0A)
|
||||
#define RADIOLIB_LORAWAN_TS009_LINK_CHECK (0x20)
|
||||
#define RADIOLIB_LORAWAN_TS009_DEVICE_TIME (0x21)
|
||||
#define RADIOLIB_LORAWAN_TS009_PING_SLOT_INFO (0x22)
|
||||
#define RADIOLIB_LORAWAN_TS009_TX_CW (0x7D)
|
||||
#define RADIOLIB_LORAWAN_TS009_DUT_FPORT224_DISABLE (0x7E)
|
||||
#define RADIOLIB_LORAWAN_TS009_DUT_VERSIONS (0x7F)
|
||||
|
||||
/*!
|
||||
\brief This function implements the TS009 specification.
|
||||
To enable this package, add this to your setup:
|
||||
`node.addAppPackage(RADIOLIB_LORAWAN_PACKAGE_TS009, handleTS009)`
|
||||
Make sure that all `extern` variables are handled in your user code!
|
||||
*/
|
||||
void handleTS009(uint8_t* dataDown, size_t lenDown) {
|
||||
if(lenDown == 0 || dataDown == NULL) {
|
||||
return;
|
||||
}
|
||||
RADIOLIB_DEBUG_PRINTLN("CID = %02x, len = %d", dataDown[0], lenDown - 1);
|
||||
|
||||
switch(dataDown[0]) {
|
||||
case(RADIOLIB_LORAWAN_TS009_PACKAGE_VERSION): {
|
||||
lenUp = 3;
|
||||
dataUp[1] = 5; // PackageIdentifier
|
||||
dataUp[2] = 1; // PackageVersion
|
||||
fPort = RADIOLIB_LORAWAN_FPORT_TS009;
|
||||
RADIOLIB_DEBUG_PRINTLN("PackageIdentifier: %d, PackageVersion: %d", dataUp[1], dataUp[2]);
|
||||
|
||||
reply = true;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_DUT_RESET): {
|
||||
RADIOLIB_DEBUG_PRINTLN("Restarting...");
|
||||
|
||||
#warning "Please implement this reset function yourself!"
|
||||
|
||||
// the function to reset the MCU is platform-dependent
|
||||
// for ESP32 for example, this would be:
|
||||
// ESP.restart();
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_DUT_JOIN): {
|
||||
RADIOLIB_DEBUG_PRINTLN("Reverting to Join state");
|
||||
node.clearSession();
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_SWITCH_CLASS): {
|
||||
uint8_t classType = dataDown[1];
|
||||
node.setClass(classType);
|
||||
RADIOLIB_DEBUG_PRINTLN("Switching to class: %s", classType == 0 ? "A" : (classType == 1 ? "B" : "C"));
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_ADR_BIT_CHANGE): {
|
||||
bool adr = (bool)dataDown[1];
|
||||
node.setADR(adr);
|
||||
RADIOLIB_DEBUG_PRINTLN("ADR: %d", adr);
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_REGIONAL_DUTY_CYCLE): {
|
||||
bool dutycycle = (bool)dataDown[1];
|
||||
node.setDutyCycle(dutycycle, 36000);
|
||||
RADIOLIB_DEBUG_PRINTLN("Dutycycle: %d", dutycycle);
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_TX_PERIODICITY_CHANGE): {
|
||||
uint32_t defaultIntervalSecs = 30;
|
||||
uint32_t intervals[11] = {defaultIntervalSecs, 5, 10, 20, 30, 40, 50, 60, 120, 240, 480};
|
||||
periodicity = intervals[dataDown[1]];
|
||||
|
||||
RADIOLIB_DEBUG_PRINTLN("Tx Periodicity: %d", periodicity);
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_TX_FRAMES_CTRL): {
|
||||
switch(dataDown[1]) {
|
||||
case(0):
|
||||
// no change
|
||||
// isConfirmed = isConfirmed;
|
||||
break;
|
||||
case(1):
|
||||
isConfirmed = false;
|
||||
break;
|
||||
case(2):
|
||||
isConfirmed = true;
|
||||
break;
|
||||
}
|
||||
RADIOLIB_DEBUG_PRINTLN("Confirmed: %d", isConfirmed);
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_ECHO_PAYLOAD): {
|
||||
lenUp = lenDown;
|
||||
for (int i = 1; i < lenDown; i++) {
|
||||
dataUp[i] = dataDown[i] + 1;
|
||||
}
|
||||
fPort = RADIOLIB_LORAWAN_FPORT_TS009;
|
||||
RADIOLIB_DEBUG_PRINTLN("Echoing payload");
|
||||
|
||||
reply = true;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_RX_APP_CNT): {
|
||||
lenUp = 3;
|
||||
uint16_t aFcntDown16 = (uint16_t)node.getAFCntDown();
|
||||
dataUp[1] = aFcntDown16 & 0xFF;
|
||||
dataUp[2] = aFcntDown16 >> 8;
|
||||
fPort = RADIOLIB_LORAWAN_FPORT_TS009;
|
||||
RADIOLIB_DEBUG_PRINTLN("aFCntDown16: %d", aFcntDown16);
|
||||
|
||||
reply = true;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_RX_APP_CNT_RESET): {
|
||||
RADIOLIB_DEBUG_PRINTLN("Resetting Application Frame count");
|
||||
node.resetFCntDown();
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_LINK_CHECK): {
|
||||
lenUp = 0;
|
||||
fPort = RADIOLIB_LORAWAN_FPORT_MAC_COMMAND;
|
||||
node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_LINK_CHECK);
|
||||
RADIOLIB_DEBUG_PRINTLN("Requesting LinkCheck");
|
||||
|
||||
reply = true;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_DEVICE_TIME): {
|
||||
lenUp = 0;
|
||||
fPort = RADIOLIB_LORAWAN_FPORT_MAC_COMMAND;
|
||||
node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_DEVICE_TIME);
|
||||
RADIOLIB_DEBUG_PRINTLN("Requesting DeviceTime");
|
||||
|
||||
reply = true;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_PING_SLOT_INFO): {
|
||||
lenUp = 0;
|
||||
RADIOLIB_DEBUG_PRINTLN("Requesting PingSlotInfo not implemented");
|
||||
// send PingSlotInfo MAC command which is not implemented
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_TX_CW): {
|
||||
// not implemented
|
||||
uint16_t timeout = (dataDown[2] << 8) | dataDown[1];
|
||||
uint32_t freqRaw = (dataDown[5] << 16) | (dataDown[4] << 8) | (dataDown[3]);
|
||||
float freq = (float)freqRaw/10000.0;
|
||||
uint8_t txPower = dataDown[6];
|
||||
RADIOLIB_DEBUG_PRINTLN("Continuous wave: %7.3f MHz, %d dBm, %d s", freq, txPower, timeout);
|
||||
radio.setFrequency(freq);
|
||||
radio.setOutputPower(txPower);
|
||||
radio.transmitDirect();
|
||||
delay(timeout * 1000);
|
||||
radio.standby();
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_DUT_FPORT224_DISABLE): {
|
||||
RADIOLIB_DEBUG_PRINTLN("Disabling FPort 224");
|
||||
node.removePackage(RADIOLIB_LORAWAN_PACKAGE_TS009);
|
||||
|
||||
reply = false;
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_TS009_DUT_VERSIONS): {
|
||||
lenUp = 13;
|
||||
// firmware version - this is RadioLib's version as an example
|
||||
dataUp[1] = RADIOLIB_VERSION_MAJOR;
|
||||
dataUp[2] = RADIOLIB_VERSION_MINOR;
|
||||
dataUp[3] = RADIOLIB_VERSION_PATCH;
|
||||
dataUp[4] = RADIOLIB_VERSION_EXTRA;
|
||||
|
||||
// lorawan version
|
||||
dataUp[5] = 1;
|
||||
#if (LORAWAN_VERSION == 1)
|
||||
dataUp[6] = 1;
|
||||
dataUp[7] = 0;
|
||||
#else
|
||||
dataUp[6] = 0;
|
||||
dataUp[7] = 4;
|
||||
#endif
|
||||
dataUp[8] = 0;
|
||||
|
||||
// regional parameters version
|
||||
dataUp[9] = 1;
|
||||
dataUp[10] = 0;
|
||||
dataUp[11] = 4;
|
||||
dataUp[12] = 0;
|
||||
fPort = RADIOLIB_LORAWAN_FPORT_TS009;
|
||||
RADIOLIB_DEBUG_PRINTLN("Requested DUT versions");
|
||||
|
||||
reply = true;
|
||||
} break;
|
||||
}
|
||||
|
||||
// if we must reply, copy the command ID into the uplink buffer
|
||||
if(reply) {
|
||||
dataUp[0] = dataDown[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
RadioLib LoRaWAN Packages Example
|
||||
|
||||
This example shows how the TS009 package can be used
|
||||
and is the sketch used for passing pre-certification testing.
|
||||
This scheme can also be used for other future packages
|
||||
such as TS003/004/005/006/007 which, combined, build FUOTA.
|
||||
|
||||
PLEASE NOTE that this is a highly customized sketch with
|
||||
settings that likely violate laws & regulations, and it is
|
||||
intended to be used with RF blocking materials and attenuators.
|
||||
|
||||
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/
|
||||
|
||||
For LoRaWAN details, see the wiki page
|
||||
https://github.com/jgromes/RadioLib/wiki/LoRaWAN
|
||||
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <RadioLib.h>
|
||||
|
||||
#include "LoRaWAN_TS009.h"
|
||||
#include "config.h"
|
||||
#include "lorawan.h"
|
||||
|
||||
uint32_t periodicity = uplinkIntervalSeconds;
|
||||
bool isConfirmed = false;
|
||||
bool reply = false;
|
||||
|
||||
uint8_t fPort = 1;
|
||||
uint8_t dataUp[255];
|
||||
uint8_t dataDown[255];
|
||||
size_t lenUp = 0;
|
||||
size_t lenDown = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(3000);
|
||||
|
||||
Serial.print(F("Initialise the radio ... "));
|
||||
int state = radio.begin();
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
|
||||
// setup, restore and join the network
|
||||
lwBegin();
|
||||
lwRestore();
|
||||
lwActivate();
|
||||
|
||||
// add TS009 package
|
||||
node.addAppPackage(RADIOLIB_LORAWAN_PACKAGE_TS009, handleTS009);
|
||||
|
||||
// LCTT (TS009 testing) has a huge timing problem on the JoinAccept Rx2 window...
|
||||
node.scanGuard = 100;
|
||||
|
||||
// these settings are totally not recommended
|
||||
// but unfortunately they are the default settings for TS009 testing
|
||||
node.setDutyCycle(false);
|
||||
node.setADR(false);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while(!node.isActivated()) {
|
||||
lwActivate();
|
||||
// this 5s interval is way too short for normal use!
|
||||
// but you'd be waiting around for long minutes during TS009 testing otherwise
|
||||
if(!node.isActivated()) {
|
||||
delay(5000);
|
||||
}
|
||||
node.setDutyCycle(false);
|
||||
}
|
||||
|
||||
int state = RADIOLIB_ERR_NONE;
|
||||
LoRaWANEvent_t eventUp;
|
||||
LoRaWANEvent_t eventDown;
|
||||
|
||||
uint32_t start = millis();
|
||||
|
||||
Serial.println("--------------------");
|
||||
Serial.println("[LoRaWAN] Sending uplink packet ... ");
|
||||
if (!reply) {
|
||||
memset(dataUp, 0, 255);
|
||||
lenUp = 4;
|
||||
fPort = 1;
|
||||
sprintf((char*)dataUp, "%04d", node.getFCntUp());
|
||||
state = node.sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, &eventUp, &eventDown);
|
||||
} else {
|
||||
reply = false;
|
||||
state = node.sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, &eventUp, &eventDown);
|
||||
}
|
||||
|
||||
if(state >= RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("[LoRaWAN] success!"));
|
||||
}
|
||||
|
||||
if(state > 0) {
|
||||
// print data of the packet (if there are any)
|
||||
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||
if(lenDown > 0) {
|
||||
arrayDump(dataDown, lenDown);
|
||||
} else {
|
||||
Serial.println(F("<MAC / package commands only>"));
|
||||
}
|
||||
|
||||
Serial.print(F("[LoRaWAN] FPort:\t"));
|
||||
Serial.print(eventDown.fPort);
|
||||
|
||||
// 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"));
|
||||
|
||||
uint8_t margin = 0;
|
||||
uint8_t gwCnt = 0;
|
||||
if(node.getMacLinkCheckAns(&margin, &gwCnt) == RADIOLIB_ERR_NONE) {
|
||||
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) == RADIOLIB_ERR_NONE) {
|
||||
Serial.print(F("[LoRaWAN] DeviceTime Unix:\t"));
|
||||
Serial.println(networkTime);
|
||||
Serial.print(F("[LoRaWAN] DeviceTime second:\t"));
|
||||
Serial.print(fracSecond);
|
||||
Serial.println(F("/256"));
|
||||
}
|
||||
|
||||
} else if(state == 0) {
|
||||
Serial.println(F("No downlink!"));
|
||||
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
|
||||
uint32_t end = millis();
|
||||
|
||||
uint32_t delayDc = node.timeUntilUplink();
|
||||
uint32_t delayMs = periodicity*1000;
|
||||
if(delayMs > end - start) {
|
||||
delayMs -= (end - start);
|
||||
} else {
|
||||
delayMs = 1;
|
||||
}
|
||||
delayMs += 50;
|
||||
Serial.print(F("Delay: "));
|
||||
Serial.print(max(delayDc, delayMs));
|
||||
Serial.println(F(" ms"));
|
||||
|
||||
// wait before sending another packet
|
||||
delay(max(delayDc, delayMs));
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
#ifndef _CONFIG_H
|
||||
#define _CONFIG_H
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
// first you have to set your radio model and pin configuration
|
||||
// this is provided just as a default example
|
||||
SX1278 radio = new Module(10, 2, 9, 3);
|
||||
|
||||
// if you have RadioBoards (https://github.com/radiolib-org/RadioBoards)
|
||||
// and are using one of the supported boards, you can do the following:
|
||||
/*
|
||||
#define RADIO_BOARD_AUTO
|
||||
#include <RadioBoards.h>
|
||||
|
||||
Radio radio = new RadioModule();
|
||||
*/
|
||||
|
||||
// how often to send an uplink - consider legal & FUP constraints
|
||||
const uint32_t uplinkIntervalSeconds = 1UL * 60UL; // minutes x seconds
|
||||
|
||||
#define LORAWAN_VERSION (0) // use version 1.LORAWAN_VERSION when joining
|
||||
#define LORAWAN_OTAA (1) // use OTAA (1) or ABP (0)
|
||||
|
||||
#if (LORAWAN_OTAA == 1)
|
||||
// joinEUI - previous versions of LoRaWAN called this AppEUI
|
||||
// for development purposes you can use all zeros - see wiki for details
|
||||
#define RADIOLIB_LORAWAN_JOIN_EUI 0x0000000000000000
|
||||
|
||||
// the Device EUI & two keys can be generated on the TTN console
|
||||
#ifndef RADIOLIB_LORAWAN_DEV_EUI // Replace with your Device EUI
|
||||
#define RADIOLIB_LORAWAN_DEV_EUI 0x----------------
|
||||
#endif
|
||||
#ifndef RADIOLIB_LORAWAN_APP_KEY // Replace with your App Key
|
||||
#define RADIOLIB_LORAWAN_APP_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
||||
#endif
|
||||
|
||||
// copy over the EUI's & keys in to the something that will not compile if incorrectly formatted
|
||||
uint64_t joinEUI = RADIOLIB_LORAWAN_JOIN_EUI;
|
||||
uint64_t devEUI = RADIOLIB_LORAWAN_DEV_EUI;
|
||||
uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY };
|
||||
|
||||
#if (LORAWAN_VERSION == 1)
|
||||
#ifndef RADIOLIB_LORAWAN_NWK_KEY // Put your Nwk Key here
|
||||
#define RADIOLIB_LORAWAN_NWK_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
||||
#endif
|
||||
uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY }; // LW v1.1 only
|
||||
#endif
|
||||
|
||||
#else // ABP
|
||||
|
||||
#ifndef RADIOLIB_LORAWAN_DEV_ADDR // Replace with your DevAddr
|
||||
#define RADIOLIB_LORAWAN_DEV_ADDR 0x------
|
||||
#endif
|
||||
|
||||
#ifndef RADIOLIB_LORAWAN_NWKSENC_KEY // Replace with your NwkSEnc Key
|
||||
#define RADIOLIB_LORAWAN_NWKSENC_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
||||
#endif
|
||||
#ifndef RADIOLIB_LORAWAN_APPS_KEY // Replace with your AppS Key
|
||||
#define RADIOLIB_LORAWAN_APPS_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
||||
#endif
|
||||
|
||||
// copy over the keys in to the something that will not compile if incorrectly formatted
|
||||
uint32_t devAddr = RADIOLIB_LORAWAN_DEV_ADDR;
|
||||
uint8_t sNwkSEncKey[] = { RADIOLIB_LORAWAN_NWKSENC_KEY };
|
||||
uint8_t appSKey[] = { RADIOLIB_LORAWAN_APPS_KEY };
|
||||
|
||||
#if (LORAWAN_VERSION == 1)
|
||||
#ifndef RADIOLIB_LORAWAN_FNWKSINT_KEY // Replace with your FNwkSInt Key
|
||||
#define RADIOLIB_LORAWAN_FNWKSINT_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
||||
#endif
|
||||
#ifndef RADIOLIB_LORAWAN_SNWKSINT_KEY // Replace with your SNwkSInt Key
|
||||
#define RADIOLIB_LORAWAN_SNWKSINT_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
||||
#endif
|
||||
uint8_t fNwkSIntKey[] = { RADIOLIB_LORAWAN_FNWKSINT_KEY }; // LW v1.1 only
|
||||
uint8_t sNwkSIntKey[] = { RADIOLIB_LORAWAN_SNWKSINT_KEY }; // LW v1.1 only
|
||||
#endif
|
||||
|
||||
#endif // OTAA/ABP
|
||||
|
||||
// for the curious, the #ifndef blocks allow for automated testing &/or you can
|
||||
// put your EUI & keys in to your platformio.ini - see wiki for more tips
|
||||
|
||||
// regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500
|
||||
const LoRaWANBand_t Region = EU868;
|
||||
const uint8_t subBand = 0; // For US915, change this to 2, otherwise leave on 0
|
||||
|
||||
// ============================================================================
|
||||
// Below is to support the sketch - only make changes if the notes say so ...
|
||||
|
||||
// create the LoRaWAN node
|
||||
LoRaWANNode node(&radio, &Region, subBand);
|
||||
|
||||
// helper function to display any issues
|
||||
void debug(bool isFail, const __FlashStringHelper* message, int state, bool Freeze) {
|
||||
if (isFail) {
|
||||
Serial.print(message);
|
||||
Serial.print("(");
|
||||
Serial.print(state);
|
||||
Serial.println(")");
|
||||
while (Freeze);
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to display a byte array
|
||||
void arrayDump(uint8_t *buffer, uint16_t len) {
|
||||
for(uint16_t c = 0; c < len; c++) {
|
||||
char b = buffer[c];
|
||||
if(b < 0x10) { Serial.print('0'); }
|
||||
Serial.print(b, HEX);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef _LORAWAN_H
|
||||
#define _LORAWAN_H
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "config.h"
|
||||
|
||||
#warning "You are required to implement persistence here! (ESP32 example provided in comments)"
|
||||
|
||||
// #include <Preferences.h>
|
||||
// Preferences store;
|
||||
// uint8_t LWnonces[RADIOLIB_LORAWAN_NONCES_BUF_SIZE];
|
||||
|
||||
bool lwBegin() {
|
||||
#if (LORAWAN_OTAA == 1)
|
||||
#if (LORAWAN_VERSION == 1)
|
||||
node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
|
||||
#else
|
||||
node.beginOTAA(joinEUI, devEUI, NULL, appKey);
|
||||
#endif
|
||||
#else
|
||||
#if (LORAWAN_VERSION == 1)
|
||||
node.beginABP(devAddr, fNwkSIntKey, sNwkSIntKey, sNwkSEncKey, appSKey);
|
||||
#else
|
||||
node.beginABP(devAddr, NULL, NULL, sNwkSEncKey, appSKey);
|
||||
#endif
|
||||
#endif
|
||||
return(true);
|
||||
}
|
||||
|
||||
int16_t lwRestore() {
|
||||
int16_t state = RADIOLIB_ERR_UNKNOWN;
|
||||
|
||||
// store.begin("radiolib");
|
||||
// if (store.isKey("nonces")) {
|
||||
// radio.standby();
|
||||
|
||||
// store.getBytes("nonces", LWnonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);
|
||||
// state = node.setBufferNonces(LWnonces);
|
||||
// }
|
||||
// store.end();
|
||||
|
||||
return(state);
|
||||
}
|
||||
|
||||
void lwActivate() {
|
||||
int16_t state = RADIOLIB_ERR_NETWORK_NOT_JOINED;
|
||||
Serial.println(F("[LoRaWAN] Attempting network join ... "));
|
||||
|
||||
radio.standby();
|
||||
|
||||
#if (LORAWAN_OTAA == 1)
|
||||
state = node.activateOTAA();
|
||||
#else
|
||||
state = node.activateABP();
|
||||
#endif
|
||||
|
||||
if(state == RADIOLIB_LORAWAN_SESSION_RESTORED) {
|
||||
Serial.println(F("[LoRaWAN] Session restored!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// store.begin("radiolib");
|
||||
// uint8_t *persist = node.getBufferNonces();
|
||||
// store.putBytes("nonces", persist, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);
|
||||
// store.end();
|
||||
|
||||
if(state == RADIOLIB_LORAWAN_NEW_SESSION) {
|
||||
Serial.println(F("[LoRaWAN] Successfully started new session!"));
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print(F("[LoRaWAN] Failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -549,9 +549,9 @@
|
|||
#define RADIOLIB_ERR_JOIN_NONCE_INVALID (-1111)
|
||||
|
||||
/*!
|
||||
\brief The downlink frame counter is slightly lower than expected, looks like a replay attack.
|
||||
\brief The downlink MIC could not be verified (incorrect key or invalid FCnt)
|
||||
*/
|
||||
#define RADIOLIB_ERR_DOWNLINK_FCNT_INVALID (-1112)
|
||||
#define RADIOLIB_ERR_MIC_MISMATCH (-1112)
|
||||
|
||||
/*!
|
||||
\brief Multicast frame counter is invalid (outside bounds).
|
||||
|
@ -598,11 +598,6 @@
|
|||
*/
|
||||
#define RADIOLIB_ERR_INVALID_MODE (-1121)
|
||||
|
||||
/*!
|
||||
\brief The downlink MIC could not be verified (incorrect key or invalid FCnt)
|
||||
*/
|
||||
#define RADIOLIB_ERR_MIC_MISMATCH (-1122)
|
||||
|
||||
// LR11x0-specific status codes
|
||||
|
||||
/*!
|
||||
|
|
|
@ -14,6 +14,9 @@ LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t
|
|||
this->txPowerMax = this->band->powerMax;
|
||||
this->subBand = subBand;
|
||||
memset(this->channelPlan, 0, sizeof(this->channelPlan));
|
||||
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES; i++) {
|
||||
this->packages[i] = RADIOLIB_LORAWAN_PACKAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(RADIOLIB_BUILD_ARDUINO)
|
||||
|
@ -228,10 +231,6 @@ int16_t LoRaWANNode::sendReceive(const uint8_t* dataUp, size_t lenUp, uint8_t fP
|
|||
LoRaWANNode::clearMacCommands(this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK);
|
||||
return(rxWindow);
|
||||
}
|
||||
|
||||
// a downlink was received, so we can clear the whole MAC uplink buffer
|
||||
memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
|
||||
this->fOptsUpLen = 0;
|
||||
|
||||
state = this->parseDownlink(dataDown, lenDown, rxWindow, eventDown);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
@ -1143,40 +1142,30 @@ void LoRaWANNode::stopMulticastSession() {
|
|||
|
||||
int16_t LoRaWANNode::isValidUplink(size_t len, uint8_t fPort) {
|
||||
// check destination fPort
|
||||
switch(fPort) {
|
||||
case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: {
|
||||
// MAC FPort only good if internally overruled (no application payload)
|
||||
if (!this->isMACPayload) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort);
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
} break;
|
||||
case RADIOLIB_LORAWAN_FPORT_TS009: {
|
||||
// TS009 FPort only good if overruled during verification testing
|
||||
if(!this->TS009) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort);
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
} break;
|
||||
case RADIOLIB_LORAWAN_FPORT_TS011: {
|
||||
// TS011 FPort only good if overruled during relay exchange
|
||||
if(!this->TS011) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort);
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
if((fPort >= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN) && (fPort <= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX)) {
|
||||
// user payload ports, all good
|
||||
bool ok = false;
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && this->isMACPayload) {
|
||||
ok = true;
|
||||
}
|
||||
if(fPort >= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN && fPort <= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX) {
|
||||
ok = true;
|
||||
}
|
||||
if(fPort >= RADIOLIB_LORAWAN_FPORT_RESERVED) {
|
||||
for(int id = 0; id < RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES; id++) {
|
||||
if(this->packages[id].enabled && fPort == this->packages[id].packFPort) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!ok) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort);
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
|
||||
// check maximum payload len as defined in band
|
||||
uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr];
|
||||
if(this->TS011) {
|
||||
if(this->packages[RADIOLIB_LORAWAN_PACKAGE_TS011].enabled) {
|
||||
maxPayLen = RADIOLIB_MIN(maxPayLen, 222); // payload length is limited to 222 if under repeater
|
||||
}
|
||||
|
||||
|
@ -1340,9 +1329,16 @@ void LoRaWANNode::composeUplink(const uint8_t* in, uint8_t lenIn, uint8_t* out,
|
|||
|
||||
// select encryption key based on the target fPort
|
||||
uint8_t* encKey = this->appSKey;
|
||||
if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) {
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
|
||||
encKey = this->nwkSEncKey;
|
||||
}
|
||||
// check if any of the packages uses this FPort
|
||||
for(int id = 0; id < RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES; id++) {
|
||||
if(this->packages[id].enabled && fPort == this->packages[id].packFPort) {
|
||||
encKey = this->packages[id].isAppPack ? this->appSKey : this->nwkSEncKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt the frame payload
|
||||
processAES(in, lenIn, encKey, &out[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(this->fOptsUpLen)], this->devAddr, this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x00, true);
|
||||
|
@ -1496,7 +1492,7 @@ int16_t LoRaWANNode::receiveClassA(uint8_t dir, const LoRaWANChannel_t* dlChanne
|
|||
|
||||
// get the maximum allowed Time-on-Air of a packet given the current datarate
|
||||
uint8_t maxPayLen = this->band->payloadLenMax[dlChannel->dr];
|
||||
if(this->TS011) {
|
||||
if(this->packages[RADIOLIB_LORAWAN_PACKAGE_TS011].enabled) {
|
||||
maxPayLen = RADIOLIB_MIN(maxPayLen, 222); // payload length is limited to 222 if under repeater
|
||||
}
|
||||
RadioLibTime_t tMax = this->phyLayer->getTimeOnAir(maxPayLen + 13) / 1000; // mandatory FHDR is 12/13 bytes
|
||||
|
@ -1669,7 +1665,7 @@ int16_t LoRaWANNode::receiveClassC(RadioLibTime_t timeout) {
|
|||
// the specified maximum length M over the data rate used to receive the frame
|
||||
// SHALL be silently discarded.
|
||||
uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_RX_BC].dr];
|
||||
if(this->TS011) {
|
||||
if(this->packages[RADIOLIB_LORAWAN_PACKAGE_TS011].enabled) {
|
||||
maxPayLen = RADIOLIB_MIN(maxPayLen, 222); // payload length is limited to 222 if under repeater
|
||||
}
|
||||
if(this->phyLayer->getPacketLength() > (size_t)(maxPayLen + 13)) { // mandatory FHDR is 12/13 bytes
|
||||
|
@ -1779,12 +1775,12 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
|
||||
// calculate length of piggy-backed FOpts
|
||||
uint8_t fOptsPbLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
|
||||
// calculate length of (piggy-backed) FOpts
|
||||
uint8_t fOptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
|
||||
|
||||
// MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOptsPb - Payload - MIC(4)
|
||||
// MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOpts - Payload - MIC(4)
|
||||
// potentially also an FPort, will find out next
|
||||
uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsPbLen - 4;
|
||||
uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsLen - 4;
|
||||
|
||||
// in LoRaWAN v1.1, a frame is a Network frame if there is no Application payload
|
||||
// i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload
|
||||
|
@ -1795,74 +1791,62 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
}
|
||||
if(payLen > 0) {
|
||||
payLen -= 1; // subtract one as fPort is set
|
||||
fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsPbLen)];
|
||||
fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsLen)];
|
||||
|
||||
// check if fPort value is actually allowed
|
||||
switch(fPort) {
|
||||
case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: {
|
||||
// payload consists of all MAC commands (or is empty)
|
||||
// check destination fPort
|
||||
bool ok = false;
|
||||
|
||||
// LoRaWAN v1.0.4 only: A Class B/C downlink SHALL NOT transport any MAC command.
|
||||
// (...) it SHALL silently discard the entire frame.
|
||||
// However, we also enforce this for LoRaWAN v1.1 (TTS does not allow this anyway).
|
||||
if(window == RADIOLIB_LORAWAN_RX_BC) {
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
} break;
|
||||
case RADIOLIB_LORAWAN_FPORT_TS009: {
|
||||
// TS009 FPort only good if overruled during verification testing
|
||||
if(!this->TS009) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort);
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
isAppDownlink = true;
|
||||
} break;
|
||||
case RADIOLIB_LORAWAN_FPORT_TS011: {
|
||||
// TS011 FPort only good if overruled during relay exchange
|
||||
if(!this->TS011) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort);
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
isAppDownlink = true;
|
||||
} break;
|
||||
default: {
|
||||
if((fPort >= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN) && (fPort <= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX)) {
|
||||
// payload is user-defined (or empty) - may carry piggybacked MAC commands
|
||||
isAppDownlink = true;
|
||||
// LoRaWAN v1.0.4 only: A Class B/C downlink SHALL NOT transport any MAC command.
|
||||
// (...) it SHALL silently discard the entire frame.
|
||||
// However, we also enforce this for LoRaWAN v1.1 (TTS does not allow this anyway).
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && window < RADIOLIB_LORAWAN_RX_BC) {
|
||||
// payload consists of all MAC commands (or is empty)
|
||||
ok = true;
|
||||
}
|
||||
if(fPort >= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN && fPort <= RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX) {
|
||||
ok = true;
|
||||
isAppDownlink = true;
|
||||
}
|
||||
// check if any of the packages uses this FPort
|
||||
if(fPort >= RADIOLIB_LORAWAN_FPORT_RESERVED) {
|
||||
for(int id = 0; id < RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES; id++) {
|
||||
if(this->packages[id].enabled && fPort == this->packages[id].packFPort) {
|
||||
ok = true;
|
||||
isAppDownlink = this->packages[id].isAppPack;
|
||||
break;
|
||||
}
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is reserved.", fPort);
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!ok) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Received downlink at FPort %d - rejected! This FPort is reserved.", fPort);
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
// MAC commands SHALL NOT be present in the payload field and the frame options field simultaneously.
|
||||
// Should this occur, the end-device SHALL silently discard the frame.
|
||||
if(fOptsPbLen > 0 && payLen > 0 && fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
// handle FOpts in uplink with FPort = 0 (or absent which means 0)
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
|
||||
if(fOptsLen > 0 && payLen > 0) {
|
||||
// MAC commands SHALL NOT be present in the payload field and the frame options field simultaneously.
|
||||
// Should this occur, the end-device SHALL silently discard the frame.
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
// if FOpts are in the payload, use this instead
|
||||
if(payLen > 0) {
|
||||
fOptsLen = payLen;
|
||||
}
|
||||
}
|
||||
|
||||
// LoRaWAN v1.0.4 only: A Class B/C downlink SHALL NOT transport any MAC command.
|
||||
// (...) it SHALL silently discard the entire frame.
|
||||
// However, we also enforce this for LoRaWAN v1.1 (TTS does not allow this anyway).
|
||||
if(fOptsPbLen > 0 && window == RADIOLIB_LORAWAN_RX_BC) {
|
||||
if(fOptsLen > 0 && window == RADIOLIB_LORAWAN_RX_BC) {
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
|
@ -1886,17 +1870,11 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
}
|
||||
}
|
||||
|
||||
// if the downlink FCnt16 value is 'slightly' lower than expected by the device, assume a replay attack
|
||||
uint16_t devFCnt16 = (uint16_t)devFCnt32;
|
||||
if(devFCnt16 > 0 && (uint16_t)(devFCnt16 - payFCnt16) < RADIOLIB_LORAWAN_MIN_ROLLOVER_FCNT_GAP) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FCnt rejected: %d -> %d", devFCnt16, payFCnt16);
|
||||
return(RADIOLIB_ERR_DOWNLINK_FCNT_INVALID);
|
||||
}
|
||||
|
||||
// assume a rollover if the FCnt16 in the payload is smaller than the previous FCnt16 known by device
|
||||
// assume a rollover if the FCnt16 in the payload is equal to / smaller
|
||||
// than the previous FCnt16 known by device
|
||||
// (MAX_FCNT_GAP is deprecated for 1.0.4 / 1.1, TTS and CS both apply a 16-bit rollover)
|
||||
if(payFCnt16 < devFCnt16) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FCnt rollover: %d -> %d", devFCnt16, payFCnt16);
|
||||
if(devFCnt32 > 0 && payFCnt16 <= (uint16_t)devFCnt32) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FCnt rollover: %d -> %d", (uint16_t)devFCnt32, payFCnt16);
|
||||
devFCnt32 += 0x10000; // add 16-bit value
|
||||
}
|
||||
devFCnt32 &= ~0xFFFF; // clear lower 16 bits known by device
|
||||
|
@ -1928,7 +1906,7 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
}
|
||||
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_DOWNLINK;
|
||||
LoRaWANNode::hton<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], addr);
|
||||
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], devFCnt32);
|
||||
LoRaWANNode::hton<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], devFCnt32);
|
||||
downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t);
|
||||
|
||||
// check the MIC
|
||||
|
@ -1943,8 +1921,15 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
#endif
|
||||
return(RADIOLIB_ERR_MIC_MISMATCH);
|
||||
}
|
||||
|
||||
// save current FCnt to respective frame counter
|
||||
|
||||
// all checks passed, so dump its contents and start processing
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %lu) encoded:",
|
||||
(this->multicast && window == RADIOLIB_LORAWAN_RX_BC) ? "M" :
|
||||
(isAppDownlink ? "A" : "N"),
|
||||
(unsigned long)devFCnt32);
|
||||
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
|
||||
|
||||
// save new FCnt to respective frame counter
|
||||
if(this->multicast && window == RADIOLIB_LORAWAN_RX_BC) {
|
||||
// multicast: McApp downlink
|
||||
this->mcAFCnt = devFCnt32;
|
||||
|
@ -1957,26 +1942,24 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
}
|
||||
}
|
||||
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %lu) encoded:",
|
||||
(this->multicast && window == RADIOLIB_LORAWAN_RX_BC) ? "M" :
|
||||
(isAppDownlink ? "A" : "N"),
|
||||
(unsigned long)devFCnt32);
|
||||
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
|
||||
|
||||
// if this is a confirmed frame, save the downlink number (only app frames can be confirmed)
|
||||
bool isConfirmedDown = false;
|
||||
if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) {
|
||||
this->confFCntDown = this->aFCntDown;
|
||||
isConfirmedDown = true;
|
||||
}
|
||||
|
||||
// if this downlink is on FPort 0, the FOptsLen is the length of the payload
|
||||
// in any other case, the payload (length) is user accessible
|
||||
uint8_t fOptsLen = fOptsPbLen;
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && payLen > 0) {
|
||||
fOptsLen = payLen;
|
||||
} else {
|
||||
*len = payLen;
|
||||
|
||||
// do some housekeeping for normal Class A downlinks (not allowed for Class B/C)
|
||||
if(window < RADIOLIB_LORAWAN_RX_BC) {
|
||||
// if this is a confirmed frame, save the downlink number (only app frames can be confirmed)
|
||||
if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) {
|
||||
this->confFCntDown = this->aFCntDown;
|
||||
isConfirmedDown = true;
|
||||
}
|
||||
|
||||
// a Class A downlink was received, so restart the ADR counter with the next uplink
|
||||
this->adrFCnt = this->getFCntUp() + 1;
|
||||
|
||||
// a Class A downlink was received, so we can clear the MAC uplink and downlink buffers
|
||||
memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
|
||||
this->fOptsUpLen = 0;
|
||||
memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
|
||||
this->fOptsDownLen = 0;
|
||||
}
|
||||
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
|
@ -1985,26 +1968,162 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#endif
|
||||
|
||||
// figure out if the payload should end up in user data or internal FOpts buffer
|
||||
uint8_t* dest;
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
|
||||
dest = fOpts;
|
||||
} else {
|
||||
dest = data;
|
||||
// decrypt any FOpts on FPort = 0, in which case FOptsLen is the length of the payload
|
||||
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && payLen > 0) {
|
||||
payLen = 0;
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], fOptsLen, this->nwkSEncKey, fOpts, addr, devFCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true);
|
||||
|
||||
// decrypt any piggy-backed FOpts
|
||||
} else if(fOptsLen > 0) {
|
||||
// the decryption depends on the LoRaWAN version
|
||||
if(this->rev == 1) {
|
||||
// in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey
|
||||
uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsLen, this->nwkSEncKey, fOpts, this->devAddr, devFCnt32, RADIOLIB_LORAWAN_DOWNLINK, ctrId, true);
|
||||
} else {
|
||||
// in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted
|
||||
memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsLen);
|
||||
}
|
||||
}
|
||||
|
||||
// process any FOpts
|
||||
if(fOptsLen > 0) {
|
||||
uint8_t* mPtr = fOpts;
|
||||
uint8_t procLen = 0;
|
||||
uint8_t fOptsRe[RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE] = { 0 };
|
||||
uint8_t fOptsReLen = 0;
|
||||
|
||||
// indication whether LinkAdr MAC command has been processed
|
||||
bool mAdr = false;
|
||||
|
||||
while(procLen < fOptsLen) {
|
||||
uint8_t cid = *mPtr; // MAC id is the first byte
|
||||
|
||||
// fetch length of MAC downlink command and uplink response
|
||||
uint8_t fLen = 1;
|
||||
uint8_t fLenRe = 1;
|
||||
state = this->getMacLen(cid, &fLen, RADIOLIB_LORAWAN_DOWNLINK, true, mPtr + 1);
|
||||
if(state != RADIOLIB_ERR_NONE) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Unknown MAC CID %02x", cid);
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Skipping remaining MAC commands");
|
||||
fOptsLen = procLen; // truncate to last processed MAC command
|
||||
break;
|
||||
}
|
||||
(void)this->getMacLen(cid, &fLenRe, RADIOLIB_LORAWAN_UPLINK, true);
|
||||
|
||||
// check whether the complete payload is present
|
||||
if(procLen + fLen > fOptsLen) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Incomplete MAC command %02x (%d bytes, expected %d)", cid, fOptsLen - procLen, fLen);
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Skipping remaining MAC commands");
|
||||
fOptsLen = procLen; // truncate to last processed MAC command
|
||||
break;
|
||||
}
|
||||
|
||||
bool reply = false;
|
||||
|
||||
// if this is a LinkAdr MAC command, pre-process contiguous commands into one atomic block
|
||||
if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) {
|
||||
// if there was any LinkAdr command before, set NACK and continue without processing
|
||||
if(mAdr) {
|
||||
reply = true;
|
||||
fOptsRe[fOptsReLen + 1] = 0x00;
|
||||
|
||||
// if this is the first LinkAdr command, do some special treatment:
|
||||
} else {
|
||||
mAdr = true;
|
||||
uint8_t fAdrLen = 5;
|
||||
uint8_t mAdrOpt[14] = { 0 };
|
||||
|
||||
// retrieve all contiguous LinkAdr commands
|
||||
while(procLen + fLen + fAdrLen < fOptsLen + 1 && *(mPtr + fLen) == RADIOLIB_LORAWAN_MAC_LINK_ADR) {
|
||||
fLen += 5; // ADR command is 5 bytes
|
||||
fLenRe += 2; // ADR response is 2 bytes
|
||||
}
|
||||
|
||||
// pre-process them into a single complete channel mask (stored in mAdrOpt)
|
||||
LoRaWANNode::preprocessMacLinkAdr(mPtr, fLen, mAdrOpt);
|
||||
|
||||
// execute like a normal MAC command (but pointing to mAdrOpt instead)
|
||||
reply = this->execMacCommand(cid, mAdrOpt, 14, &fOptsRe[fOptsReLen + 1]);
|
||||
|
||||
// in LoRaWAN v1.0.x, all ACK bytes should have equal status - fix in post-processing
|
||||
if(this->rev == 0) {
|
||||
LoRaWANNode::postprocessMacLinkAdr(&fOptsRe[fOptsReLen], fLen);
|
||||
|
||||
// in LoRaWAN v1.1, just provide one ACK, so no post-processing but cut off reply length
|
||||
} else {
|
||||
fLenRe = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// MAC command other than LinkAdr, just process the payload
|
||||
} else {
|
||||
reply = this->execMacCommand(cid, mPtr + 1, fLen - 1, &fOptsRe[fOptsReLen + 1]);
|
||||
}
|
||||
|
||||
// if there is a reply, only add it to the reply if maximum payload size allows
|
||||
if(reply && (fOptsReLen + fLenRe <= this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr])) {
|
||||
fOptsRe[fOptsReLen] = cid;
|
||||
fOptsReLen += fLenRe;
|
||||
}
|
||||
|
||||
procLen += fLen;
|
||||
mPtr += fLen;
|
||||
}
|
||||
|
||||
// remove all MAC commands except those whose payload can be requested by the user
|
||||
// (which are LinkCheck and DeviceTime)
|
||||
LoRaWANNode::clearMacCommands(fOpts, &fOptsLen, RADIOLIB_LORAWAN_DOWNLINK);
|
||||
memcpy(this->fOptsDown, fOpts, fOptsLen);
|
||||
this->fOptsDownLen = fOptsLen;
|
||||
|
||||
// if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink
|
||||
if(fOptsReLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) {
|
||||
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("! Sending MAC-only uplink (%d bytes):", fOptsReLen);
|
||||
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsRe, fOptsReLen);
|
||||
|
||||
this->isMACPayload = true;
|
||||
|
||||
// temporarily lift dutyCycle restrictions to allow immediate MAC response
|
||||
bool prevDC = this->dutyCycleEnabled;
|
||||
this->dutyCycleEnabled = false;
|
||||
this->sendReceive(fOptsRe, fOptsReLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
|
||||
this->dutyCycleEnabled = prevDC;
|
||||
|
||||
} else { // fOptsReLen <= 15
|
||||
memcpy(this->fOptsUp, fOptsRe, fOptsReLen);
|
||||
this->fOptsUpLen = fOptsReLen;
|
||||
}
|
||||
}
|
||||
|
||||
// figure out which key to use to decrypt the payload
|
||||
uint8_t* encKey = this->appSKey;
|
||||
if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) {
|
||||
encKey = this->nwkSEncKey;
|
||||
}
|
||||
if(this->multicast && window == RADIOLIB_LORAWAN_RX_BC) {
|
||||
encKey = this->mcAppSKey;
|
||||
}
|
||||
for(int id = 0; id < RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES; id++) {
|
||||
if(this->packages[id].enabled && fPort == this->packages[id].packFPort) {
|
||||
encKey = this->packages[id].isAppPack ? this->appSKey : this->nwkSEncKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt the frame payload
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsPbLen)], payLen, encKey, dest, addr, devFCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true);
|
||||
|
||||
// by default, the data and length are user-accessible
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsLen)], payLen, encKey, data, addr, devFCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true);
|
||||
*len = payLen;
|
||||
|
||||
// however, if this frame belongs to a package, redirect instead and 'hide' contents from the user
|
||||
// just to be sure that it doesn't get re-interpreted...
|
||||
for(int id = 0; id < RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES; id++) {
|
||||
if(this->packages[id].enabled && fPort == this->packages[id].packFPort) {
|
||||
this->packages[id].callback(data, *len);
|
||||
memset(data, 0, *len);
|
||||
*len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// pass the event info if requested
|
||||
if(event) {
|
||||
event->dir = RADIOLIB_LORAWAN_DOWNLINK;
|
||||
|
@ -2019,151 +2138,6 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
|
|||
event->multicast = (bool)this->multicast;
|
||||
}
|
||||
|
||||
// for RxBC downlinks, return already, we aren't allowed to do any FOpts stuff
|
||||
if(window == RADIOLIB_LORAWAN_RX_BC) {
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] fOpts;
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
// a downlink was received, so restart the ADR counter with the next uplink
|
||||
this->adrFCnt = this->getFCntUp() + 1;
|
||||
|
||||
// decrypt any piggy-backed FOpts
|
||||
if(fOptsPbLen > 0) {
|
||||
// the decryption depends on the LoRaWAN version
|
||||
if(this->rev == 1) {
|
||||
// in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey
|
||||
uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen, this->nwkSEncKey, fOpts, this->devAddr, devFCnt32, RADIOLIB_LORAWAN_DOWNLINK, ctrId, true);
|
||||
} else {
|
||||
// in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted
|
||||
memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the previous MAC commands, if any
|
||||
memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
|
||||
|
||||
// process FOpts (if there are any)
|
||||
uint8_t cid;
|
||||
uint8_t fLen = 1;
|
||||
uint8_t* mPtr = fOpts;
|
||||
uint8_t procLen = 0;
|
||||
uint8_t fOptsRe[RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE] = { 0 };
|
||||
uint8_t fOptsReLen = 0;
|
||||
|
||||
// indication whether LinkAdr MAC command has been processed
|
||||
bool mAdr = false;
|
||||
|
||||
while(procLen < fOptsLen) {
|
||||
cid = *mPtr; // MAC id is the first byte
|
||||
|
||||
// fetch length of MAC downlink payload
|
||||
state = this->getMacLen(cid, &fLen, RADIOLIB_LORAWAN_DOWNLINK, true, mPtr + 1);
|
||||
if(state != RADIOLIB_ERR_NONE) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Unknown MAC CID %02x", cid);
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Skipping remaining MAC commands");
|
||||
fOptsLen = procLen; // truncate to last processed MAC command
|
||||
break;
|
||||
}
|
||||
|
||||
// already fetch length of MAC answer payload (if any), include CID
|
||||
uint8_t fLenRe = 0;
|
||||
(void)this->getMacLen(cid, &fLenRe, RADIOLIB_LORAWAN_UPLINK, true);
|
||||
// don't care about return value: the previous getMacLen() would have failed anyway
|
||||
|
||||
if(procLen + fLen > fOptsLen) {
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Incomplete MAC command %02x (%d bytes, expected %d)", cid, fOptsLen - procLen, fLen);
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("WARNING: Skipping remaining MAC commands");
|
||||
fOptsLen = procLen; // truncate to last processed MAC command
|
||||
break;
|
||||
}
|
||||
|
||||
bool reply = false;
|
||||
|
||||
// if this is a LinkAdr MAC command, pre-process contiguous commands into one atomic block
|
||||
if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) {
|
||||
// if there was any LinkAdr command before, set NACK and continue without processing
|
||||
if(mAdr) {
|
||||
reply = true;
|
||||
fOptsRe[fOptsReLen + 1] = 0x00;
|
||||
|
||||
// if this is the first LinkAdr command, do some special treatment:
|
||||
} else {
|
||||
mAdr = true;
|
||||
uint8_t fAdrLen = 5;
|
||||
uint8_t mAdrOpt[14] = { 0 };
|
||||
|
||||
// retrieve all contiguous LinkAdr commands
|
||||
while(procLen + fLen + fAdrLen < fOptsLen + 1 && *(mPtr + fLen) == RADIOLIB_LORAWAN_MAC_LINK_ADR) {
|
||||
fLen += 5; // ADR command is 5 bytes
|
||||
fLenRe += 2; // ADR response is 2 bytes
|
||||
}
|
||||
|
||||
// pre-process them into a single complete channel mask (stored in mAdrOpt)
|
||||
LoRaWANNode::preprocessMacLinkAdr(mPtr, fLen, mAdrOpt);
|
||||
|
||||
// execute like a normal MAC command (but pointing to mAdrOpt instead)
|
||||
reply = this->execMacCommand(cid, mAdrOpt, 14, &fOptsRe[fOptsReLen + 1]);
|
||||
|
||||
// in LoRaWAN v1.0.x, all ACK bytes should have equal status - fix in post-processing
|
||||
if(this->rev == 0) {
|
||||
LoRaWANNode::postprocessMacLinkAdr(&fOptsRe[fOptsReLen], fLen);
|
||||
|
||||
// in LoRaWAN v1.1, just provide one ACK, so no post-processing but cut off reply length
|
||||
} else {
|
||||
fLenRe = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// MAC command other than LinkAdr, just process the payload
|
||||
} else {
|
||||
reply = this->execMacCommand(cid, mPtr + 1, fLen - 1, &fOptsRe[fOptsReLen + 1]);
|
||||
}
|
||||
|
||||
// if there is a reply, only add it to the reply if maximum payload size allows
|
||||
if(reply && (fOptsReLen + fLenRe <= this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr])) {
|
||||
fOptsRe[fOptsReLen] = cid;
|
||||
fOptsReLen += fLenRe;
|
||||
}
|
||||
|
||||
procLen += fLen;
|
||||
mPtr += fLen;
|
||||
}
|
||||
|
||||
// remove all MAC commands except those whose payload can be requested by the user
|
||||
// (which are LinkCheck and DeviceTime)
|
||||
if(fOptsLen > 0) {
|
||||
LoRaWANNode::clearMacCommands(fOpts, &fOptsLen, RADIOLIB_LORAWAN_DOWNLINK);
|
||||
memcpy(this->fOptsDown, fOpts, fOptsLen);
|
||||
}
|
||||
this->fOptsDownLen = fOptsLen;
|
||||
|
||||
// if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink
|
||||
if(fOptsReLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) {
|
||||
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC-only payload (%d bytes):", fOptsReLen);
|
||||
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsRe, fOptsReLen);
|
||||
|
||||
this->isMACPayload = true;
|
||||
// temporarily lift dutyCycle restrictions to allow immediate MAC response
|
||||
bool prevDC = this->dutyCycleEnabled;
|
||||
this->dutyCycleEnabled = false;
|
||||
|
||||
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. ");
|
||||
|
||||
this->sendReceive(fOptsRe, fOptsReLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
|
||||
|
||||
this->dutyCycleEnabled = prevDC;
|
||||
|
||||
} else { // fOptsReLen <= 15
|
||||
memcpy(this->fOptsUp, fOptsRe, fOptsReLen);
|
||||
this->fOptsUpLen = fOptsReLen;
|
||||
}
|
||||
|
||||
#if !RADIOLIB_STATIC_ONLY
|
||||
delete[] fOpts;
|
||||
delete[] downlinkMsg;
|
||||
|
@ -3101,6 +3075,7 @@ void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) {
|
|||
this->dutyCycleEnabled = enable;
|
||||
if(!enable) {
|
||||
this->dutyCycle = 0;
|
||||
return;
|
||||
}
|
||||
if(msPerHour == 0) {
|
||||
this->dutyCycle = this->band->dutyCycle;
|
||||
|
@ -3662,7 +3637,7 @@ uint8_t LoRaWANNode::getMaxPayloadLen() {
|
|||
|
||||
uint8_t minLen = 0;
|
||||
uint8_t maxLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr];
|
||||
if(this->TS011) {
|
||||
if(this->packages[RADIOLIB_LORAWAN_PACKAGE_TS011].enabled) {
|
||||
maxLen = RADIOLIB_MIN(maxLen, 222); // payload length is limited to N=222 if under repeater
|
||||
}
|
||||
maxLen += 13; // mandatory FHDR is 12/13 bytes
|
||||
|
@ -3697,6 +3672,58 @@ void LoRaWANNode::setSleepFunction(SleepCb_t cb) {
|
|||
this->sleepCb = cb;
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::addAppPackage(uint8_t packageId, PackageCb_t callback) {
|
||||
if(packageId >= RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES) {
|
||||
return(RADIOLIB_ERR_INVALID_MODE);
|
||||
}
|
||||
return(this->addAppPackage(packageId, callback, PackageTable[packageId].packFPort));
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::addAppPackage(uint8_t packageId, PackageCb_t callback, uint8_t fPort) {
|
||||
if(packageId >= RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES) {
|
||||
return(RADIOLIB_ERR_INVALID_MODE);
|
||||
}
|
||||
if(PackageTable[packageId].isAppPack == false) {
|
||||
return(RADIOLIB_ERR_INVALID_MODE);
|
||||
}
|
||||
if(PackageTable[packageId].fixedFPort && fPort != PackageTable[packageId].packFPort) {
|
||||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
if(callback == NULL) {
|
||||
return(RADIOLIB_ERR_NULL_POINTER);
|
||||
}
|
||||
this->packages[packageId] = PackageTable[packageId];
|
||||
this->packages[packageId].packFPort = fPort;
|
||||
this->packages[packageId].callback = callback;
|
||||
this->packages[packageId].enabled = true;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::addNwkPackage(uint8_t packageId, PackageCb_t callback) {
|
||||
if(packageId >= RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES) {
|
||||
return(RADIOLIB_ERR_INVALID_MODE);
|
||||
}
|
||||
if(PackageTable[packageId].isAppPack == true) {
|
||||
return(RADIOLIB_ERR_INVALID_MODE);
|
||||
}
|
||||
if(callback == NULL) {
|
||||
return(RADIOLIB_ERR_NULL_POINTER);
|
||||
}
|
||||
this->packages[packageId] = PackageTable[packageId];
|
||||
this->packages[packageId].callback = callback;
|
||||
this->packages[packageId].enabled = true;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
void LoRaWANNode::removePackage(uint8_t packageId) {
|
||||
// silently ignore, assume that the user supplies decent index
|
||||
if(packageId >= RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES) {
|
||||
return;
|
||||
}
|
||||
this->packages[packageId].enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) {
|
||||
int16_t state = RADIOLIB_ERR_NONE;
|
||||
|
||||
|
|
|
@ -44,8 +44,6 @@
|
|||
#define RADIOLIB_LORAWAN_FPORT_MAC_COMMAND (0x00 << 0) // 7 0 payload contains MAC commands only
|
||||
#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN (0x01 << 0) // 7 0 start of user-allowed fPort range
|
||||
#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX (0xDF << 0) // 7 0 end of user-allowed fPort range
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS009 (0xE0 << 0) // 7 0 fPort used for TS009 testing
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS011 (0xE2 << 0) // 7 0 fPort used for TS011 Forwarding
|
||||
#define RADIOLIB_LORAWAN_FPORT_RESERVED (0xE0 << 0) // 7 0 fPort values equal to and larger than this are reserved
|
||||
|
||||
// data rate encoding
|
||||
|
@ -97,9 +95,6 @@
|
|||
#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
|
||||
|
||||
// developer recommended default setting (not specified)
|
||||
#define RADIOLIB_LORAWAN_MIN_ROLLOVER_FCNT_GAP (16384) // equal to deprecated MaxFCntGap
|
||||
|
||||
// join request message layout
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_LEN (23)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1)
|
||||
|
@ -202,6 +197,27 @@
|
|||
#define RADIOLIB_LORAWAN_MAC_DEVICE_MODE (0x20)
|
||||
#define RADIOLIB_LORAWAN_MAC_PROPRIETARY (0x80)
|
||||
|
||||
// number of supported LoRaWAN TS packages
|
||||
#define RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES (7)
|
||||
|
||||
// Package ID numbering as per TS008 (TS011 ID is made up)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS007 (0)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS003 (1)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS005 (2)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS004 (3)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS006 (4)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS009 (5)
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_TS011 (6)
|
||||
|
||||
// Package FPort (specified or recommended)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS007 (225)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS003 (202)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS005 (200)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS004 (201)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS006 (203)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS009 (224)
|
||||
#define RADIOLIB_LORAWAN_FPORT_TS011 (226)
|
||||
|
||||
// the length of internal MAC command queue - hopefully this is enough for most use cases
|
||||
#define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (9)
|
||||
|
||||
|
@ -261,6 +277,45 @@ constexpr LoRaWANMacCommand_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS] = {
|
|||
{ RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, false, true },
|
||||
};
|
||||
|
||||
/*! \brief A user-supplied callback for LoRaWAN Application Packages (TSxxx) */
|
||||
typedef void (*PackageCb_t)(uint8_t* dataDown, size_t lenDown);
|
||||
|
||||
/*!
|
||||
\struct LoRaWANPackage_t
|
||||
\brief LoRaWAN Packages structure (for TSxxx documents).
|
||||
*/
|
||||
struct LoRaWANPackage_t {
|
||||
/*! \brief Package ID as per TS008 */
|
||||
uint8_t packId;
|
||||
|
||||
/*! \brief Package default FPort (specified or recommended) */
|
||||
uint8_t packFPort;
|
||||
|
||||
/*! \brief Whether the package runs through the Application layer */
|
||||
bool isAppPack;
|
||||
|
||||
/*! \brief Whether the FPort value is fixed or may be modified */
|
||||
bool fixedFPort;
|
||||
|
||||
/*! \brief Whether the package is currently in use */
|
||||
bool enabled;
|
||||
|
||||
/*! \brief User-provided callback for handling package downlinks */
|
||||
PackageCb_t callback;
|
||||
};
|
||||
|
||||
constexpr LoRaWANPackage_t PackageTable[RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES] = {
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS007, RADIOLIB_LORAWAN_FPORT_TS007, true, false, false },
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS003, RADIOLIB_LORAWAN_FPORT_TS003, true, true, false },
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS005, RADIOLIB_LORAWAN_FPORT_TS005, true, true, false },
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS004, RADIOLIB_LORAWAN_FPORT_TS004, true, true, false },
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS006, RADIOLIB_LORAWAN_FPORT_TS006, true, true, false },
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS009, RADIOLIB_LORAWAN_FPORT_TS009, true, false, false },
|
||||
{ RADIOLIB_LORAWAN_PACKAGE_TS011, RADIOLIB_LORAWAN_FPORT_TS011, false, false, false }
|
||||
};
|
||||
|
||||
#define RADIOLIB_LORAWAN_PACKAGE_NONE { .packId = 0, .packFPort = 0, .isAppPack = false, .fixedFPort = false, .enabled = false, .callback = NULL }
|
||||
|
||||
#define RADIOLIB_LORAWAN_NONCES_VERSION_VAL (0x0001)
|
||||
|
||||
enum LoRaWANSchemeBase_t {
|
||||
|
@ -886,11 +941,37 @@ class LoRaWANNode {
|
|||
*/
|
||||
void setSleepFunction(SleepCb_t cb);
|
||||
|
||||
/*!
|
||||
\brief TS009 Protocol Specification Verification switch
|
||||
(allows FPort 224 and cuts off uplink payload instead of rejecting if maximum length exceeded).
|
||||
/*!
|
||||
\brief Add a LoRaWAN Application Package as defined in one of the TSxxx documents.
|
||||
Any downlinks that occur on the corresponding FPort will be redirected to
|
||||
a supplied callback that implements this package. These downlink contents will be
|
||||
hidden from the user as the downlink buffer will be empty and the length zero.
|
||||
The package may need to overrule the behaviour of your device - refer to the examples.
|
||||
Advanced users only!
|
||||
\param packageId The ID of the package (one of RADIOLIB_LORAWAN_PACKAGE_TSxxx).
|
||||
\param callback The downlink handler for this package of type (uint8_t* dataDown, size_t lenDown).
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
bool TS009 = false;
|
||||
int16_t addAppPackage(uint8_t packageId, PackageCb_t callback);
|
||||
|
||||
/*!
|
||||
\brief Add a LoRaWAN Application Package as defined in one of the TSxxx documents.
|
||||
Any downlinks that occur on the corresponding FPort will be redirected to
|
||||
a supplied callback that implements this package. These downlink contents will be
|
||||
hidden from the user as the downlink buffer will be empty and the length zero.
|
||||
The package may need to overrule the behaviour of your device - refer to the examples.
|
||||
Advanced users only!
|
||||
\param packageId The ID of the package (one of RADIOLIB_LORAWAN_PACKAGE_TSxxx).
|
||||
\param callback The downlink handler for this package of type (uint8_t* dataDown, size_t lenDown).
|
||||
\param fPort A custom FPort for packages that have a default FPort < 224.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t addAppPackage(uint8_t packageId, PackageCb_t callback, uint8_t fPort);
|
||||
|
||||
/*!
|
||||
\brief Disable a package that was previously added.
|
||||
*/
|
||||
void removePackage(uint8_t packageId);
|
||||
|
||||
/*!
|
||||
\brief Rx window padding in milliseconds
|
||||
|
@ -983,6 +1064,9 @@ class LoRaWANNode {
|
|||
uint32_t mcAFCnt = 0;
|
||||
uint32_t mcAFCntMax = 0;
|
||||
|
||||
// enabled TS packages
|
||||
LoRaWANPackage_t packages[RADIOLIB_LORAWAN_NUM_SUPPORTED_PACKAGES];
|
||||
|
||||
// enable/disable CSMA for LoRaWAN
|
||||
bool csmaEnabled = false;
|
||||
|
||||
|
@ -1032,9 +1116,7 @@ class LoRaWANNode {
|
|||
// save the selected sub-band in case this must be restored in ADR control
|
||||
uint8_t subBand = 0;
|
||||
|
||||
// allow port 226 for devices implementing TS011
|
||||
bool TS011 = false;
|
||||
|
||||
// user-provided sleep callback
|
||||
SleepCb_t sleepCb = nullptr;
|
||||
|
||||
// this will reset the device credentials, so the device starts completely new
|
||||
|
@ -1079,6 +1161,10 @@ class LoRaWANNode {
|
|||
// extract downlink payload and process MAC commands
|
||||
int16_t parseDownlink(uint8_t* data, size_t* len, uint8_t window, LoRaWANEvent_t* event = NULL);
|
||||
|
||||
// add a LoRaWAN package that runs through the network layer
|
||||
// (not available to users, they are only allowed to add application packages)
|
||||
int16_t addNwkPackage(uint8_t packageId, PackageCb_t callback);
|
||||
|
||||
// execute mac command, return the number of processed bytes for sequential processing
|
||||
bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn);
|
||||
bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut);
|
||||
|
|
Ładowanie…
Reference in New Issue