Merge pull request #1 from nicklasb/nicklasb-patch-narrowing
Fix narrowing conversion error on ESP-IDF
This commit is contained in:
commit
f8ae458185
17 changed files with 1286 additions and 209 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -47,7 +47,7 @@ jobs:
|
||||||
# platform-dependent settings - extra board options, board index URLs, skip patterns etc.
|
# platform-dependent settings - extra board options, board index URLs, skip patterns etc.
|
||||||
include:
|
include:
|
||||||
- id: arduino:avr:uno
|
- id: arduino:avr:uno
|
||||||
run: echo "skip-pattern=(STM32WL|SSTV)" >> $GITHUB_OUTPUT
|
run: echo "skip-pattern=(STM32WL|SSTV|LoRaWAN)" >> $GITHUB_OUTPUT
|
||||||
- id: arduino:avr:mega
|
- id: arduino:avr:mega
|
||||||
run: echo "options=':cpu=atmega2560'" >> $GITHUB_OUTPUT
|
run: echo "options=':cpu=atmega2560'" >> $GITHUB_OUTPUT
|
||||||
- id: arduino:avr:leonardo
|
- id: arduino:avr:leonardo
|
||||||
|
|
|
@ -60,10 +60,10 @@ void setup() {
|
||||||
// the end device in TTN and perform the join procedure again!
|
// the end device in TTN and perform the join procedure again!
|
||||||
//node.wipe();
|
//node.wipe();
|
||||||
|
|
||||||
// application identifier - in LoRaWAN 1.1, it is also called joinEUI
|
// application identifier - pre-LoRaWAN 1.1.0, this was called appEUI
|
||||||
// when adding new end device in TTN, you will have to enter this number
|
// when adding new end device in TTN, you will have to enter this number
|
||||||
// you can pick any number you want, but it has to be unique
|
// you can pick any number you want, but it has to be unique
|
||||||
uint64_t appEUI = 0x12AD1011B0C0FFEE;
|
uint64_t joinEUI = 0x12AD1011B0C0FFEE;
|
||||||
|
|
||||||
// device identifier - this number can be anything
|
// device identifier - this number can be anything
|
||||||
// when adding new end device in TTN, you can generate this number,
|
// when adding new end device in TTN, you can generate this number,
|
||||||
|
@ -76,10 +76,14 @@ void setup() {
|
||||||
const char nwkKey[] = "topSecretKey1234";
|
const char nwkKey[] = "topSecretKey1234";
|
||||||
const char appKey[] = "aDifferentKeyABC";
|
const char appKey[] = "aDifferentKeyABC";
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
// now we can start the activation
|
// now we can start the activation
|
||||||
// this can take up to 20 seconds, and requires a LoRaWAN gateway in range
|
// this can take up to 20 seconds, and requires a LoRaWAN gateway in range
|
||||||
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
|
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
|
||||||
state = node.beginOTAA(appEUI, devEUI, (uint8_t*)nwkKey, (uint8_t*)appKey);
|
state = node.beginOTAA(joinEUI, devEUI, (uint8_t*)nwkKey, (uint8_t*)appKey);
|
||||||
if(state == RADIOLIB_ERR_NONE) {
|
if(state == RADIOLIB_ERR_NONE) {
|
||||||
Serial.println(F("success!"));
|
Serial.println(F("success!"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,8 +114,8 @@ int count = 0;
|
||||||
void loop() {
|
void loop() {
|
||||||
// send uplink to port 10
|
// send uplink to port 10
|
||||||
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
|
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
|
||||||
String str = "Hello World! #" + String(count++);
|
String strUp = "Hello World! #" + String(count++);
|
||||||
int state = node.uplink(str, 10);
|
int state = node.uplink(strUp, 10);
|
||||||
if(state == RADIOLIB_ERR_NONE) {
|
if(state == RADIOLIB_ERR_NONE) {
|
||||||
Serial.println(F("success!"));
|
Serial.println(F("success!"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,6 +123,47 @@ void loop() {
|
||||||
Serial.println(state);
|
Serial.println(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait before sending another one
|
// after uplink, you can call downlink(),
|
||||||
|
// to receive any possible reply from the server
|
||||||
|
// this function must be called within a few seconds
|
||||||
|
// after uplink to receive the downlink!
|
||||||
|
Serial.print(F("[LoRaWAN] Waiting for downlink ... "));
|
||||||
|
String strDown;
|
||||||
|
state = node.downlink(strDown);
|
||||||
|
if(state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.println(F("success!"));
|
||||||
|
|
||||||
|
// print data of the packet (if there are any)
|
||||||
|
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||||
|
if(strDown.length() > 0) {
|
||||||
|
Serial.println(strDown);
|
||||||
|
} else {
|
||||||
|
Serial.println(F("<MAC commands only>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// print RSSI (Received Signal Strength Indicator)
|
||||||
|
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
||||||
|
Serial.print(radio.getRSSI());
|
||||||
|
Serial.println(F(" dBm"));
|
||||||
|
|
||||||
|
// print SNR (Signal-to-Noise Ratio)
|
||||||
|
Serial.print(F("[LoRaWAN] SNR:\t\t"));
|
||||||
|
Serial.print(radio.getSNR());
|
||||||
|
Serial.println(F(" dB"));
|
||||||
|
|
||||||
|
// print frequency error
|
||||||
|
Serial.print(F("[LoRaWAN] Frequency error:\t"));
|
||||||
|
Serial.print(radio.getFrequencyError());
|
||||||
|
Serial.println(F(" Hz"));
|
||||||
|
|
||||||
|
} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
|
||||||
|
Serial.println(F("timeout!"));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Serial.print(F("failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait before sending another packet
|
||||||
delay(10000);
|
delay(10000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,10 @@ void setup() {
|
||||||
const char nwkSKey[] = "topSecretKey1234";
|
const char nwkSKey[] = "topSecretKey1234";
|
||||||
const char appSKey[] = "aDifferentKeyABC";
|
const char appSKey[] = "aDifferentKeyABC";
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
// start the device by directly providing the encryption keys and device address
|
// start the device by directly providing the encryption keys and device address
|
||||||
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
|
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
|
||||||
state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey);
|
state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey);
|
||||||
|
@ -105,8 +109,8 @@ int count = 0;
|
||||||
void loop() {
|
void loop() {
|
||||||
// send uplink to port 10
|
// send uplink to port 10
|
||||||
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
|
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
|
||||||
String str = "Hello World! #" + String(count++);
|
String strUp = "Hello World! #" + String(count++);
|
||||||
int state = node.uplink(str, 10);
|
int state = node.uplink(strUp, 10);
|
||||||
if(state == RADIOLIB_ERR_NONE) {
|
if(state == RADIOLIB_ERR_NONE) {
|
||||||
Serial.println(F("success!"));
|
Serial.println(F("success!"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,6 +118,47 @@ void loop() {
|
||||||
Serial.println(state);
|
Serial.println(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait before sending another one
|
// after uplink, you can call downlink(),
|
||||||
|
// to receive any possible reply from the server
|
||||||
|
// this function must be called within a few seconds
|
||||||
|
// after uplink to receive the downlink!
|
||||||
|
Serial.print(F("[LoRaWAN] Waiting for downlink ... "));
|
||||||
|
String strDown;
|
||||||
|
state = node.downlink(strDown);
|
||||||
|
if(state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.println(F("success!"));
|
||||||
|
|
||||||
|
// print data of the packet (if there are any)
|
||||||
|
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||||
|
if(strDown.length() > 0) {
|
||||||
|
Serial.println(strDown);
|
||||||
|
} else {
|
||||||
|
Serial.println(F("<MAC commands only>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// print RSSI (Received Signal Strength Indicator)
|
||||||
|
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
||||||
|
Serial.print(radio.getRSSI());
|
||||||
|
Serial.println(F(" dBm"));
|
||||||
|
|
||||||
|
// print SNR (Signal-to-Noise Ratio)
|
||||||
|
Serial.print(F("[LoRaWAN] SNR:\t\t"));
|
||||||
|
Serial.print(radio.getSNR());
|
||||||
|
Serial.println(F(" dB"));
|
||||||
|
|
||||||
|
// print frequency error
|
||||||
|
Serial.print(F("[LoRaWAN] Frequency error:\t"));
|
||||||
|
Serial.print(radio.getFrequencyError());
|
||||||
|
Serial.println(F(" Hz"));
|
||||||
|
|
||||||
|
} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
|
||||||
|
Serial.println(F("timeout!"));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Serial.print(F("failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait before sending another packet
|
||||||
delay(10000);
|
delay(10000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
RadioLib SX126x Receive after Channel Activity Detection Example
|
||||||
|
|
||||||
|
This example uses SX1262 to scan the current LoRa
|
||||||
|
channel and detect ongoing LoRa transmissions.
|
||||||
|
Unlike SX127x CAD, SX126x can detect any part
|
||||||
|
of LoRa transmission, not just the preamble.
|
||||||
|
If a packet is detected, the module will switch
|
||||||
|
to receive mode and receive the packet.
|
||||||
|
|
||||||
|
Other modules from SX126x family can also be used.
|
||||||
|
|
||||||
|
For default module settings, see the wiki page
|
||||||
|
https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx126x---lora-modem
|
||||||
|
|
||||||
|
For full API reference, see the GitHub Pages
|
||||||
|
https://jgromes.github.io/RadioLib/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// include the library
|
||||||
|
#include <RadioLib.h>
|
||||||
|
|
||||||
|
// SX1262 has the following connections:
|
||||||
|
// NSS pin: 10
|
||||||
|
// DIO1 pin: 2
|
||||||
|
// NRST pin: 3
|
||||||
|
// BUSY pin: 9
|
||||||
|
SX1262 radio = new Module(10, 2, 3, 9);
|
||||||
|
|
||||||
|
// or using RadioShield
|
||||||
|
// https://github.com/jgromes/RadioShield
|
||||||
|
//SX1262 radio = RadioShield.ModuleA;
|
||||||
|
|
||||||
|
// or using CubeCell
|
||||||
|
//SX1262 radio = new Module(RADIOLIB_BUILTIN_MODULE);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
// initialize SX1262 with default settings
|
||||||
|
Serial.print(F("[SX1262] Initializing ... "));
|
||||||
|
int state = radio.begin();
|
||||||
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.println(F("success!"));
|
||||||
|
} else {
|
||||||
|
Serial.print(F("failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the function that will be called
|
||||||
|
// when LoRa packet or timeout is detected
|
||||||
|
radio.setDio1Action(setFlag);
|
||||||
|
|
||||||
|
// start scanning the channel
|
||||||
|
Serial.print(F("[SX1262] Starting scan for LoRa preamble ... "));
|
||||||
|
state = radio.startChannelScan();
|
||||||
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.println(F("success!"));
|
||||||
|
} else {
|
||||||
|
Serial.print(F("failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flag to indicate that a packet was detected or CAD timed out
|
||||||
|
volatile bool scanFlag = false;
|
||||||
|
|
||||||
|
bool receiving = false;
|
||||||
|
|
||||||
|
// this function is called when a complete packet
|
||||||
|
// is received by the module
|
||||||
|
// IMPORTANT: this function MUST be 'void' type
|
||||||
|
// and MUST NOT have any arguments!
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
ICACHE_RAM_ATTR
|
||||||
|
#endif
|
||||||
|
void setFlag(void) {
|
||||||
|
// something happened, set the flag
|
||||||
|
scanFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// check if the flag is set
|
||||||
|
if(scanFlag) {
|
||||||
|
int state = RADIOLIB_ERR_NONE;
|
||||||
|
|
||||||
|
// reset flag
|
||||||
|
scanFlag = false;
|
||||||
|
|
||||||
|
// check ongoing reception
|
||||||
|
if(receiving) {
|
||||||
|
// DIO triggered while reception is ongoing
|
||||||
|
// that means we got a packet
|
||||||
|
|
||||||
|
// you can read received data as an Arduino String
|
||||||
|
String str;
|
||||||
|
state = radio.readData(str);
|
||||||
|
|
||||||
|
// you can also read received data as byte array
|
||||||
|
/*
|
||||||
|
byte byteArr[8];
|
||||||
|
state = radio.readData(byteArr, 8);
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
|
// packet was successfully received
|
||||||
|
Serial.println(F("[SX1262] Received packet!"));
|
||||||
|
|
||||||
|
// print data of the packet
|
||||||
|
Serial.print(F("[SX1262] Data:\t\t"));
|
||||||
|
Serial.println(str);
|
||||||
|
|
||||||
|
// print RSSI (Received Signal Strength Indicator)
|
||||||
|
Serial.print(F("[SX1262] RSSI:\t\t"));
|
||||||
|
Serial.print(radio.getRSSI());
|
||||||
|
Serial.println(F(" dBm"));
|
||||||
|
|
||||||
|
// print SNR (Signal-to-Noise Ratio)
|
||||||
|
Serial.print(F("[SX1262] SNR:\t\t"));
|
||||||
|
Serial.print(radio.getSNR());
|
||||||
|
Serial.println(F(" dB"));
|
||||||
|
|
||||||
|
// print frequency error
|
||||||
|
Serial.print(F("[SX1262] Frequency error:\t"));
|
||||||
|
Serial.print(radio.getFrequencyError());
|
||||||
|
Serial.println(F(" Hz"));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// some other error occurred
|
||||||
|
Serial.print(F("[SX1262] Failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// reception is done now
|
||||||
|
receiving = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// check CAD result
|
||||||
|
state = radio.getChannelScanResult();
|
||||||
|
|
||||||
|
if (state == RADIOLIB_LORA_DETECTED) {
|
||||||
|
// LoRa packet was detected
|
||||||
|
Serial.print(F("[SX1262] Packet detected, starting reception ... "));
|
||||||
|
state = radio.startReceive();
|
||||||
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.println(F("success!"));
|
||||||
|
} else {
|
||||||
|
Serial.print(F("failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the flag for ongoing reception
|
||||||
|
receiving = true;
|
||||||
|
|
||||||
|
} else if (state == RADIOLIB_CHANNEL_FREE) {
|
||||||
|
// channel is free
|
||||||
|
Serial.println(F("[SX1262] Channel is free!"));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// some other error occurred
|
||||||
|
Serial.print(F("[SX1262] Failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're not receiving, start scanning again
|
||||||
|
if(!receiving) {
|
||||||
|
Serial.print(F("[SX1262] Starting scan for LoRa preamble ... "));
|
||||||
|
state = radio.startChannelScan();
|
||||||
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.println(F("success!"));
|
||||||
|
} else {
|
||||||
|
Serial.print(F("failed, code "));
|
||||||
|
Serial.println(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
keywords.txt
11
keywords.txt
|
@ -292,6 +292,7 @@ wipe KEYWORD2
|
||||||
beginOTAA KEYWORD2
|
beginOTAA KEYWORD2
|
||||||
beginAPB KEYWORD2
|
beginAPB KEYWORD2
|
||||||
uplink KEYWORD2
|
uplink KEYWORD2
|
||||||
|
downlink KEYWORD2
|
||||||
configureChannel KEYWORD2
|
configureChannel KEYWORD2
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
|
@ -390,3 +391,13 @@ RADIOLIB_ERR_RANGING_TIMEOUT LITERAL1
|
||||||
RADIOLIB_ERR_INVALID_PAYLOAD LITERAL1
|
RADIOLIB_ERR_INVALID_PAYLOAD LITERAL1
|
||||||
RADIOLIB_ERR_ADDRESS_NOT_FOUND LITERAL1
|
RADIOLIB_ERR_ADDRESS_NOT_FOUND LITERAL1
|
||||||
RADIOLIB_ERR_INVALID_FUNCTION LITERAL1
|
RADIOLIB_ERR_INVALID_FUNCTION LITERAL1
|
||||||
|
|
||||||
|
RADIOLIB_ERR_NETWORK_NOT_JOINED LITERAL1
|
||||||
|
RADIOLIB_ERR_DOWNLINK_MALFORMED LITERAL1
|
||||||
|
RADIOLIB_ERR_INVALID_REVISION LITERAL1
|
||||||
|
RADIOLIB_ERR_INVALID_PORT LITERAL1
|
||||||
|
RADIOLIB_ERR_NO_RX_WINDOW LITERAL1
|
||||||
|
RADIOLIB_ERR_INVALID_CHANNEL LITERAL1
|
||||||
|
RADIOLIB_ERR_INVALID_CID LITERAL1
|
||||||
|
RADIOLIB_ERR_COMMAND_QUEUE_FULL LITERAL1
|
||||||
|
RADIOLIB_ERR_COMMAND_QUEUE_EMPTY LITERAL1
|
||||||
|
|
|
@ -44,6 +44,10 @@ void Module::init() {
|
||||||
this->hal->init();
|
this->hal->init();
|
||||||
this->hal->pinMode(csPin, this->hal->GpioModeOutput);
|
this->hal->pinMode(csPin, this->hal->GpioModeOutput);
|
||||||
this->hal->digitalWrite(csPin, this->hal->GpioLevelHigh);
|
this->hal->digitalWrite(csPin, this->hal->GpioLevelHigh);
|
||||||
|
RADIOLIB_DEBUG_PRINTLN("\nRadioLib Debug Info");
|
||||||
|
RADIOLIB_DEBUG_PRINTLN("Version: %d.%d.%d.%d", RADIOLIB_VERSION_MAJOR, RADIOLIB_VERSION_MINOR, RADIOLIB_VERSION_PATCH, RADIOLIB_VERSION_EXTRA);
|
||||||
|
RADIOLIB_DEBUG_PRINTLN("Platform: " RADIOLIB_PLATFORM);
|
||||||
|
RADIOLIB_DEBUG_PRINTLN("Compiled: " __DATE__ " " __TIME__ "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Module::term() {
|
void Module::term() {
|
||||||
|
@ -166,11 +170,11 @@ void Module::SPItransfer(uint8_t cmd, uint16_t reg, uint8_t* dataOut, uint8_t* d
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the transfer
|
// do the transfer
|
||||||
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
|
|
||||||
this->hal->spiBeginTransaction();
|
this->hal->spiBeginTransaction();
|
||||||
|
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
|
||||||
this->hal->spiTransfer(buffOut, buffLen, buffIn);
|
this->hal->spiTransfer(buffOut, buffLen, buffIn);
|
||||||
this->hal->spiEndTransaction();
|
|
||||||
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh);
|
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh);
|
||||||
|
this->hal->spiEndTransaction();
|
||||||
|
|
||||||
// copy the data
|
// copy the data
|
||||||
if(cmd == SPIreadCommand) {
|
if(cmd == SPIreadCommand) {
|
||||||
|
@ -298,11 +302,11 @@ int16_t Module::SPItransferStream(uint8_t* cmd, uint8_t cmdLen, bool write, uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the transfer
|
// do the transfer
|
||||||
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
|
|
||||||
this->hal->spiBeginTransaction();
|
this->hal->spiBeginTransaction();
|
||||||
|
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
|
||||||
this->hal->spiTransfer(buffOut, buffLen, buffIn);
|
this->hal->spiTransfer(buffOut, buffLen, buffIn);
|
||||||
this->hal->spiEndTransaction();
|
|
||||||
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh);
|
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh);
|
||||||
|
this->hal->spiEndTransaction();
|
||||||
|
|
||||||
// wait for GPIO to go high and then low
|
// wait for GPIO to go high and then low
|
||||||
if(waitForGpio) {
|
if(waitForGpio) {
|
||||||
|
@ -327,7 +331,7 @@ int16_t Module::SPItransferStream(uint8_t* cmd, uint8_t cmdLen, bool write, uint
|
||||||
|
|
||||||
// parse status
|
// parse status
|
||||||
int16_t state = RADIOLIB_ERR_NONE;
|
int16_t state = RADIOLIB_ERR_NONE;
|
||||||
if(this->SPIparseStatusCb != nullptr) {
|
if((this->SPIparseStatusCb != nullptr) && (numBytes > 0)) {
|
||||||
state = this->SPIparseStatusCb(buffIn[cmdLen]);
|
state = this->SPIparseStatusCb(buffIn[cmdLen]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,18 +51,6 @@
|
||||||
#warning "God mode active, I hope it was intentional. Buckle up, lads."
|
#warning "God mode active, I hope it was intentional. Buckle up, lads."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// print debug info
|
|
||||||
#if defined(RADIOLIB_DEBUG)
|
|
||||||
#define RADIOLIB_VALUE_TO_STRING(x) #x
|
|
||||||
#define RADIOLIB_VALUE(x) RADIOLIB_VALUE_TO_STRING(x)
|
|
||||||
#define RADIOLIB_VAR_NAME_VALUE(var) #var "=" RADIOLIB_VALUE(var)
|
|
||||||
#pragma message(RADIOLIB_VAR_NAME_VALUE(RADIOLIB_PLATFORM))
|
|
||||||
#pragma message(RADIOLIB_VAR_NAME_VALUE(RADIOLIB_VERSION_MAJOR))
|
|
||||||
#pragma message(RADIOLIB_VAR_NAME_VALUE(RADIOLIB_VERSION_MINOR))
|
|
||||||
#pragma message(RADIOLIB_VAR_NAME_VALUE(RADIOLIB_VERSION_PATCH))
|
|
||||||
#pragma message(RADIOLIB_VAR_NAME_VALUE(RADIOLIB_VERSION_EXTRA))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// check unknown/unsupported platform
|
// check unknown/unsupported platform
|
||||||
#if defined(RADIOLIB_UNKNOWN_PLATFORM)
|
#if defined(RADIOLIB_UNKNOWN_PLATFORM)
|
||||||
#warning "RadioLib might not be compatible with this Arduino board - check supported platforms at https://github.com/jgromes/RadioLib!"
|
#warning "RadioLib might not be compatible with this Arduino board - check supported platforms at https://github.com/jgromes/RadioLib!"
|
||||||
|
|
|
@ -481,6 +481,58 @@
|
||||||
*/
|
*/
|
||||||
#define RADIOLIB_ERR_INVALID_FUNCTION (-1003)
|
#define RADIOLIB_ERR_INVALID_FUNCTION (-1003)
|
||||||
|
|
||||||
|
// LoRaWAN-specific status codes
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Unable to restore existing LoRaWAN session because this node did not join any network yet.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_NETWORK_NOT_JOINED (-1101)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Malformed downlink packet received from network server.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_DOWNLINK_MALFORMED (-1102)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Network server requested switch to unsupported LoRaWAN revision.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_INVALID_REVISION (-1103)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Invalid LoRaWAN uplink port requested by user.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_INVALID_PORT (-1104)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief User did not enable downlink in time.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_NO_RX_WINDOW (-1105)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief No valid channel for the currently active LoRaWAN band was found.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_INVALID_CHANNEL (-1106)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Invalid LoRaWAN MAC command ID.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_INVALID_CID (-1107)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief User requested to start uplink while still inside RX window.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_UPLINK_UNAVAILABLE (-1108)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Unable to push new MAC command because the queue is full.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_COMMAND_QUEUE_FULL (-1109)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Unable to pop existing MAC command because the queue is empty.
|
||||||
|
*/
|
||||||
|
#define RADIOLIB_ERR_COMMAND_QUEUE_EMPTY (-1110)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\}
|
\}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -91,6 +91,9 @@ int16_t SX126x::begin(uint8_t cr, uint8_t syncWord, uint16_t preambleLength, flo
|
||||||
state = setCRC(2);
|
state = setCRC(2);
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
state = invertIQ(false);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
return(state);
|
return(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,6 +507,15 @@ void SX126x::clearPacketSentAction() {
|
||||||
this->clearDio1Action();
|
this->clearDio1Action();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SX126x::setChannelScanAction(void (*func)(void)) {
|
||||||
|
this->setDio1Action(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SX126x::clearChannelScanAction() {
|
||||||
|
this->clearDio1Action();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int16_t SX126x::startTransmit(uint8_t* data, size_t len, uint8_t addr) {
|
int16_t SX126x::startTransmit(uint8_t* data, size_t len, uint8_t addr) {
|
||||||
// suppress unused variable warning
|
// suppress unused variable warning
|
||||||
(void)addr;
|
(void)addr;
|
||||||
|
@ -726,6 +738,10 @@ int16_t SX126x::readData(uint8_t* data, size_t len) {
|
||||||
return(state);
|
return(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t SX126x::startChannelScan() {
|
||||||
|
return(this->startChannelScan(RADIOLIB_SX126X_CAD_PARAM_DEFAULT, RADIOLIB_SX126X_CAD_PARAM_DEFAULT, RADIOLIB_SX126X_CAD_PARAM_DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
int16_t SX126x::startChannelScan(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) {
|
int16_t SX126x::startChannelScan(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) {
|
||||||
// check active modem
|
// check active modem
|
||||||
if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
|
if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
|
||||||
|
@ -762,11 +778,9 @@ int16_t SX126x::getChannelScanResult() {
|
||||||
uint16_t cadResult = getIrqStatus();
|
uint16_t cadResult = getIrqStatus();
|
||||||
if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DETECTED) {
|
if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DETECTED) {
|
||||||
// detected some LoRa activity
|
// detected some LoRa activity
|
||||||
clearIrqStatus();
|
|
||||||
return(RADIOLIB_LORA_DETECTED);
|
return(RADIOLIB_LORA_DETECTED);
|
||||||
} else if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DONE) {
|
} else if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DONE) {
|
||||||
// channel is free
|
// channel is free
|
||||||
clearIrqStatus();
|
|
||||||
return(RADIOLIB_CHANNEL_FREE);
|
return(RADIOLIB_CHANNEL_FREE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1718,7 +1732,7 @@ int16_t SX126x::setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) {
|
||||||
data[2] = detMin;
|
data[2] = detMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure paramaters
|
// configure parameters
|
||||||
int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data, 7);
|
int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data, 7);
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
|
|
@ -601,6 +601,17 @@ class SX126x: public PhysicalLayer {
|
||||||
*/
|
*/
|
||||||
void clearPacketSentAction();
|
void clearPacketSentAction();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Sets interrupt service routine to call when a channel scan is finished.
|
||||||
|
\param func ISR to call.
|
||||||
|
*/
|
||||||
|
void setChannelScanAction(void (*func)(void));
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Clears interrupt service routine to call when a channel scan is finished.
|
||||||
|
*/
|
||||||
|
void clearChannelScanAction();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Interrupt-driven binary transmit method.
|
\brief Interrupt-driven binary transmit method.
|
||||||
Overloads for string-based transmissions are implemented in PhysicalLayer.
|
Overloads for string-based transmissions are implemented in PhysicalLayer.
|
||||||
|
@ -685,22 +696,29 @@ class SX126x: public PhysicalLayer {
|
||||||
\returns \ref status_codes
|
\returns \ref status_codes
|
||||||
*/
|
*/
|
||||||
int16_t readData(uint8_t* data, size_t len) override;
|
int16_t readData(uint8_t* data, size_t len) override;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Interrupt-driven channel activity detection method. DIO0 will be activated
|
\brief Interrupt-driven channel activity detection method. DIO1 will be activated
|
||||||
when LoRa preamble is detected, or upon timeout.
|
when LoRa preamble is detected, or upon timeout. Defaults to CAD parameter values recommended by AN1200.48.
|
||||||
\param symbolNum Number of symbols for CAD detection. Defaults to the value recommended by AN1200.48.
|
|
||||||
\param detPeak Peak value for CAD detection. Defaults to the value recommended by AN1200.48.
|
|
||||||
\param detMin Minimum value for CAD detection. Defaults to the value recommended by AN1200.48.
|
|
||||||
\returns \ref status_codes
|
\returns \ref status_codes
|
||||||
*/
|
*/
|
||||||
int16_t startChannelScan(uint8_t symbolNum = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, uint8_t detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, uint8_t detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT);
|
int16_t startChannelScan() override;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Interrupt-driven channel activity detection method. DIO1 will be activated
|
||||||
|
when LoRa preamble is detected, or upon timeout.
|
||||||
|
\param symbolNum Number of symbols for CAD detection.
|
||||||
|
\param detPeak Peak value for CAD detection.
|
||||||
|
\param detMin Minimum value for CAD detection.
|
||||||
|
\returns \ref status_codes
|
||||||
|
*/
|
||||||
|
int16_t startChannelScan(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Read the channel scan result
|
\brief Read the channel scan result
|
||||||
\returns \ref status_codes
|
\returns \ref status_codes
|
||||||
*/
|
*/
|
||||||
int16_t getChannelScanResult();
|
int16_t getChannelScanResult() override;
|
||||||
|
|
||||||
// configuration methods
|
// configuration methods
|
||||||
|
|
||||||
|
@ -929,7 +947,7 @@ class SX126x: public PhysicalLayer {
|
||||||
\param len Payload length in bytes.
|
\param len Payload length in bytes.
|
||||||
\returns Expected time-on-air in microseconds.
|
\returns Expected time-on-air in microseconds.
|
||||||
*/
|
*/
|
||||||
uint32_t getTimeOnAir(size_t len);
|
uint32_t getTimeOnAir(size_t len) override;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Set implicit header mode for future reception/transmission.
|
\brief Set implicit header mode for future reception/transmission.
|
||||||
|
|
|
@ -51,6 +51,10 @@ int16_t SX127x::begin(uint8_t chipVersion, uint8_t syncWord, uint16_t preambleLe
|
||||||
state = SX127x::setPreambleLength(preambleLength);
|
state = SX127x::setPreambleLength(preambleLength);
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
// disable IQ inversion
|
||||||
|
state = SX127x::invertIQ(false);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
// initialize internal variables
|
// initialize internal variables
|
||||||
this->dataRate = 0.0;
|
this->dataRate = 0.0;
|
||||||
|
|
||||||
|
@ -468,6 +472,14 @@ void SX127x::clearPacketSentAction() {
|
||||||
this->clearDio0Action();
|
this->clearDio0Action();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SX127x::setChannelScanAction(void (*func)(void)) {
|
||||||
|
this->setDio0Action(func, this->mod->hal->GpioInterruptRising);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SX127x::clearChannelScanAction() {
|
||||||
|
this->clearDio0Action();
|
||||||
|
}
|
||||||
|
|
||||||
void SX127x::setFifoEmptyAction(void (*func)(void)) {
|
void SX127x::setFifoEmptyAction(void (*func)(void)) {
|
||||||
// set DIO1 to the FIFO empty event (the register setting is done in startTransmit)
|
// set DIO1 to the FIFO empty event (the register setting is done in startTransmit)
|
||||||
setDio1Action(func, this->mod->hal->GpioInterruptRising);
|
setDio1Action(func, this->mod->hal->GpioInterruptRising);
|
||||||
|
@ -697,6 +709,13 @@ int16_t SX127x::startChannelScan() {
|
||||||
return(state);
|
return(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t SX127x::getChannelScanResult() {
|
||||||
|
if((this->getIRQFlags() & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) == RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) {
|
||||||
|
return(RADIOLIB_PREAMBLE_DETECTED);
|
||||||
|
}
|
||||||
|
return(RADIOLIB_CHANNEL_FREE);
|
||||||
|
}
|
||||||
|
|
||||||
int16_t SX127x::setSyncWord(uint8_t syncWord) {
|
int16_t SX127x::setSyncWord(uint8_t syncWord) {
|
||||||
// check active modem
|
// check active modem
|
||||||
if(getActiveModem() != RADIOLIB_SX127X_LORA) {
|
if(getActiveModem() != RADIOLIB_SX127X_LORA) {
|
||||||
|
|
|
@ -738,6 +738,17 @@ class SX127x: public PhysicalLayer {
|
||||||
*/
|
*/
|
||||||
void clearPacketSentAction();
|
void clearPacketSentAction();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Sets interrupt service routine to call when a channel scan is finished.
|
||||||
|
\param func ISR to call.
|
||||||
|
*/
|
||||||
|
void setChannelScanAction(void (*func)(void));
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Clears interrupt service routine to call when a channel scan is finished.
|
||||||
|
*/
|
||||||
|
void clearChannelScanAction();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Set interrupt service routine function to call when FIFO is empty.
|
\brief Set interrupt service routine function to call when FIFO is empty.
|
||||||
\param func Pointer to interrupt service routine.
|
\param func Pointer to interrupt service routine.
|
||||||
|
@ -834,7 +845,13 @@ class SX127x: public PhysicalLayer {
|
||||||
DIO1 will be activated if there's no preamble detected before timeout.
|
DIO1 will be activated if there's no preamble detected before timeout.
|
||||||
\returns \ref status_codes
|
\returns \ref status_codes
|
||||||
*/
|
*/
|
||||||
int16_t startChannelScan();
|
int16_t startChannelScan() override;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Read the channel scan result.
|
||||||
|
\returns \ref status_codes
|
||||||
|
*/
|
||||||
|
int16_t getChannelScanResult() override;
|
||||||
|
|
||||||
// configuration methods
|
// configuration methods
|
||||||
|
|
||||||
|
@ -1016,11 +1033,11 @@ class SX127x: public PhysicalLayer {
|
||||||
int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK);
|
int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Get expected time-on-air for a given size of payload
|
\brief Get expected time-on-air for a given size of payload.
|
||||||
\param len Payload length in bytes.
|
\param len Payload length in bytes.
|
||||||
\returns Expected time-on-air in microseconds.
|
\returns Expected time-on-air in microseconds.
|
||||||
*/
|
*/
|
||||||
uint32_t getTimeOnAir(size_t len);
|
uint32_t getTimeOnAir(size_t len) override;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Enable CRC filtering and generation.
|
\brief Enable CRC filtering and generation.
|
||||||
|
|
|
@ -24,13 +24,10 @@ int16_t APRSClient::begin(char sym, char* callsign, uint8_t ssid, bool alt) {
|
||||||
table = '/';
|
table = '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
if((!src) && (this->phyLayer != nullptr)) {
|
|
||||||
return(RADIOLIB_ERR_INVALID_CALLSIGN);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(strlen(callsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) {
|
if(strlen(callsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) {
|
||||||
return(RADIOLIB_ERR_INVALID_CALLSIGN);
|
return(RADIOLIB_ERR_INVALID_CALLSIGN);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(this->src, callsign, strlen(callsign));
|
memcpy(this->src, callsign, strlen(callsign));
|
||||||
this->id = ssid;
|
this->id = ssid;
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,17 @@ static void LoRaWANNodeOnDownlink(void) {
|
||||||
downlinkReceived = true;
|
downlinkReceived = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flag to indicate whether channel scan operation is complete
|
||||||
|
static volatile bool scanFlag = false;
|
||||||
|
|
||||||
|
// interrupt service routine to handle downlinks automatically
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
IRAM_ATTR
|
||||||
|
#endif
|
||||||
|
static void LoRaWANNodeOnChannelScan(void) {
|
||||||
|
scanFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
|
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
|
||||||
this->phyLayer = phy;
|
this->phyLayer = phy;
|
||||||
this->band = band;
|
this->band = band;
|
||||||
|
@ -34,12 +45,11 @@ int16_t LoRaWANNode::begin() {
|
||||||
Module* mod = this->phyLayer->getMod();
|
Module* mod = this->phyLayer->getMod();
|
||||||
if(mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) {
|
if(mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) {
|
||||||
// the magic value is not set, user will have to do perform the join procedure
|
// the magic value is not set, user will have to do perform the join procedure
|
||||||
return(RADIOLIB_ERR_CHIP_NOT_FOUND);
|
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pull all needed information from persistent storage
|
// pull all needed information from persistent storage
|
||||||
this->devAddr = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID);
|
this->devAddr = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID);
|
||||||
RADIOLIB_DEBUG_PRINTLN("devAddr = 0x%08x", this->devAddr);
|
|
||||||
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
|
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
@ -47,7 +57,7 @@ int16_t LoRaWANNode::begin() {
|
||||||
return(RADIOLIB_ERR_NONE);
|
return(RADIOLIB_ERR_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) {
|
int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) {
|
||||||
// check if we actually need to send the join request
|
// check if we actually need to send the join request
|
||||||
Module* mod = this->phyLayer->getMod();
|
Module* mod = this->phyLayer->getMod();
|
||||||
if(!force && (mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) {
|
if(!force && (mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) {
|
||||||
|
@ -62,14 +72,13 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
||||||
// get dev nonce from persistent storage and increment it
|
// get dev nonce from persistent storage and increment it
|
||||||
uint16_t devNonce = mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID);
|
uint16_t devNonce = mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID);
|
||||||
mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1);
|
mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1);
|
||||||
RADIOLIB_DEBUG_PRINTLN("devNonce = %d", devNonce);
|
|
||||||
|
|
||||||
// build the join-request message
|
// build the join-request message
|
||||||
uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
|
uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
|
||||||
|
|
||||||
// set the packet fields
|
// set the packet fields
|
||||||
joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
||||||
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], appEUI);
|
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI);
|
||||||
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI);
|
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI);
|
||||||
LoRaWANNode::hton<uint16_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce);
|
LoRaWANNode::hton<uint16_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce);
|
||||||
|
|
||||||
|
@ -85,6 +94,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
||||||
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
|
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
|
||||||
|
|
||||||
// downlink messages are sent with inverted IQ
|
// downlink messages are sent with inverted IQ
|
||||||
|
// TODO use downlink() for this
|
||||||
if(!this->FSK) {
|
if(!this->FSK) {
|
||||||
state = this->phyLayer->invertIQ(true);
|
state = this->phyLayer->invertIQ(true);
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
@ -122,7 +132,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
||||||
size_t lenRx = this->phyLayer->getPacketLength(true);
|
size_t lenRx = this->phyLayer->getPacketLength(true);
|
||||||
if((lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) && (lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) {
|
if((lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) && (lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) {
|
||||||
RADIOLIB_DEBUG_PRINTLN("joinAccept reply length mismatch, expected %luB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, lenRx);
|
RADIOLIB_DEBUG_PRINTLN("joinAccept reply length mismatch, expected %luB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, lenRx);
|
||||||
return(RADIOLIB_ERR_RX_TIMEOUT);
|
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the packet
|
// read the packet
|
||||||
|
@ -136,30 +146,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
||||||
// check reply message type
|
// check reply message type
|
||||||
if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) {
|
if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) {
|
||||||
RADIOLIB_DEBUG_PRINTLN("joinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]);
|
RADIOLIB_DEBUG_PRINTLN("joinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]);
|
||||||
return(RADIOLIB_ERR_RX_TIMEOUT);
|
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt the join accept message
|
// decrypt the join accept message
|
||||||
// this is done by encrypting again in ECB mode
|
// this is done by encrypting again in ECB mode
|
||||||
// the first byte is the MAC header which is not encrpyted
|
// the first byte is the MAC header which is not encrypted
|
||||||
uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
|
uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
|
||||||
joinAcceptMsg[0] = joinAcceptMsgEnc[0];
|
joinAcceptMsg[0] = joinAcceptMsgEnc[0];
|
||||||
RadioLibAES128Instance.init(nwkKey);
|
RadioLibAES128Instance.init(nwkKey);
|
||||||
RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]);
|
RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]);
|
||||||
|
|
||||||
//Module::hexdump(joinAcceptMsg, RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN);
|
//Module::hexdump(joinAcceptMsg, lenRx);
|
||||||
|
|
||||||
|
// check LoRaWAN revision (the MIC verification depends on this)
|
||||||
|
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
|
||||||
|
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
|
||||||
|
// 1.1 version, first we need to derive the join accept integrity key
|
||||||
|
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||||
|
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY;
|
||||||
|
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[1], devEUI);
|
||||||
|
RadioLibAES128Instance.init(nwkKey);
|
||||||
|
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->jSIntKey);
|
||||||
|
|
||||||
|
// prepare the buffer for MIC calculation
|
||||||
|
uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||||
|
micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE;
|
||||||
|
LoRaWANNode::hton<uint64_t>(&micBuff[1], joinEUI);
|
||||||
|
LoRaWANNode::hton<uint16_t>(&micBuff[9], devNonce);
|
||||||
|
memcpy(&micBuff[11], joinAcceptMsg, lenRx);
|
||||||
|
|
||||||
|
//Module::hexdump(micBuff, lenRx + 11);
|
||||||
|
|
||||||
|
if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) {
|
||||||
|
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 1.0 version
|
||||||
|
if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) {
|
||||||
|
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||||
|
}
|
||||||
|
|
||||||
// verify MIC
|
|
||||||
if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) {
|
|
||||||
return(RADIOLIB_ERR_CRC_MISMATCH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the contents
|
// parse the contents
|
||||||
uint32_t joinNonce = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
|
uint32_t joinNonce = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
|
||||||
uint32_t homeNetId = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3);
|
uint32_t homeNetId = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3);
|
||||||
this->devAddr = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]);
|
this->devAddr = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]);
|
||||||
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
|
this->rxDelays[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]*1000;
|
||||||
this->rxDelay = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS];
|
if(this->rxDelays[0] == 0) {
|
||||||
|
this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS;
|
||||||
|
}
|
||||||
|
this->rxDelays[1] = this->rxDelays[0] + 1000;
|
||||||
|
|
||||||
// process CFlist if present
|
// process CFlist if present
|
||||||
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
|
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
|
||||||
|
@ -174,35 +213,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
||||||
} else {
|
} else {
|
||||||
// TODO list of masks
|
// TODO list of masks
|
||||||
RADIOLIB_DEBUG_PRINTLN("CFlist masks not supported (yet)");
|
RADIOLIB_DEBUG_PRINTLN("CFlist masks not supported (yet)");
|
||||||
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
|
return(RADIOLIB_ERR_UNSUPPORTED);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RADIOLIB_DEBUG_PRINTLN("joinNonce = %lu", joinNonce);
|
|
||||||
RADIOLIB_DEBUG_PRINTLN("homeNetId = %lu", homeNetId);
|
|
||||||
RADIOLIB_DEBUG_PRINTLN("devAddr = 0x%08x", devAddr);
|
|
||||||
RADIOLIB_DEBUG_PRINTLN("dlSettings = 0x%02x", dlSettings);
|
|
||||||
RADIOLIB_DEBUG_PRINTLN("rxDelay = %d", this->rxDelay);
|
|
||||||
|
|
||||||
// prepare buffer for key derivation
|
// prepare buffer for key derivation
|
||||||
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||||
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3);
|
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3);
|
||||||
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
|
|
||||||
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
|
|
||||||
|
|
||||||
// check protocol version (1.0 vs 1.1)
|
// check protocol version (1.0 vs 1.1)
|
||||||
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
|
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
|
||||||
// TODO implement 1.1
|
// 1.1 version, derive the keys
|
||||||
|
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI);
|
||||||
|
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce);
|
||||||
|
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
|
||||||
|
//Module::hexdump(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
|
||||||
|
RadioLibAES128Instance.init(appKey);
|
||||||
|
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
|
||||||
|
//Module::hexdump(this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
|
||||||
|
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY;
|
||||||
|
RadioLibAES128Instance.init(nwkKey);
|
||||||
|
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey);
|
||||||
|
//Module::hexdump(this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
|
||||||
|
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY;
|
||||||
|
RadioLibAES128Instance.init(nwkKey);
|
||||||
|
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->sNwkSIntKey);
|
||||||
|
//Module::hexdump(this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
|
||||||
|
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY;
|
||||||
|
RadioLibAES128Instance.init(nwkKey);
|
||||||
|
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey);
|
||||||
|
//Module::hexdump(this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
|
||||||
|
// enqueue the RekeyInd MAC command to be sent in the next uplink
|
||||||
this->rev = 1;
|
this->rev = 1;
|
||||||
RADIOLIB_DEBUG_PRINTLN("LoRaWAN 1.1 not supported (yet)");
|
LoRaWANMacCommand_t cmd = {
|
||||||
(void)appKey;
|
.cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND,
|
||||||
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
|
.len = sizeof(uint8_t),
|
||||||
|
.payload = { this->rev },
|
||||||
|
.repeat = RADIOLIB_LORAWAN_ADR_ACK_LIMIT,
|
||||||
|
};
|
||||||
|
state = pushMacCommand(&cmd, &this->commandsUp);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 1.0 version, just derive the keys
|
// 1.0 version, just derive the keys
|
||||||
this->rev = 0;
|
this->rev = 0;
|
||||||
|
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
|
||||||
|
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
|
||||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
|
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
|
||||||
RadioLibAES128Instance.init(nwkKey);
|
RadioLibAES128Instance.init(nwkKey);
|
||||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
|
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
|
||||||
|
@ -262,18 +325,32 @@ int16_t LoRaWANNode::uplink(const char* str, uint8_t port) {
|
||||||
|
|
||||||
int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
||||||
// check destination port
|
// check destination port
|
||||||
RADIOLIB_CHECK_RANGE(port, 0x01, 0xDF, RADIOLIB_ERR_INVALID_PAYLOAD);
|
if(port > 0xDF) {
|
||||||
|
return(RADIOLIB_ERR_INVALID_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
// check maximum payload len as defiend in phy
|
// check if there are some MAC commands to piggyback
|
||||||
// TODO implement Fopts
|
size_t foptsLen = 0;
|
||||||
uint8_t foptsLen = 0;
|
if(this->commandsUp.numCommands > 0) {
|
||||||
|
// there are, assume the maximum possible FOpts len for buffer allocation
|
||||||
|
foptsLen = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check maximum payload len as defined in phy
|
||||||
if(len > this->band->payloadLenMax[this->dataRate]) {
|
if(len > this->band->payloadLenMax[this->dataRate]) {
|
||||||
return(RADIOLIB_ERR_PACKET_TOO_LONG);
|
return(RADIOLIB_ERR_PACKET_TOO_LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if sufficient time has elapsed since the last uplink
|
||||||
|
Module* mod = this->phyLayer->getMod();
|
||||||
|
if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) {
|
||||||
|
// not enough time elapsed since the last uplink, we may still be in an RX window
|
||||||
|
return(RADIOLIB_ERR_UPLINK_UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
// build the uplink message
|
// build the uplink message
|
||||||
// the first 16 bytes are reserved for MIC calculation blocks
|
// the first 16 bytes are reserved for MIC calculation blocks
|
||||||
size_t uplinkMsgLen = RADIOLIB_LORAWAN_UPLINK_LEN(len, foptsLen);
|
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
|
||||||
#if defined(RADIOLIB_STATIC_ONLY)
|
#if defined(RADIOLIB_STATIC_ONLY)
|
||||||
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
|
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||||
#else
|
#else
|
||||||
|
@ -281,62 +358,47 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// set the packet fields
|
// set the packet fields
|
||||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
||||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS], this->devAddr);
|
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr);
|
||||||
|
|
||||||
// TODO implement adaptive data rate
|
// TODO implement adaptive data rate
|
||||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCTRL_POS] = 0x00;
|
// foptslen will be added later
|
||||||
|
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00;
|
||||||
|
|
||||||
// get frame counter from persistent storage
|
// get frame counter from persistent storage
|
||||||
Module* mod = this->phyLayer->getMod();
|
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1;
|
||||||
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID);
|
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt);
|
||||||
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt + 1);
|
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt);
|
||||||
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCNT_POS], (uint16_t)fcnt);
|
|
||||||
|
|
||||||
// TODO implement FOpts
|
// check if we have some MAC command to append
|
||||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FPORT_POS(foptsLen)] = port;
|
// TODO implement appending multiple MAC commands
|
||||||
|
LoRaWANMacCommand_t cmd = { 0 };
|
||||||
|
if(popMacCommand(&cmd, &this->commandsUp) == RADIOLIB_ERR_NONE) {
|
||||||
|
// we do, add it to fopts
|
||||||
|
uint8_t foptsBuff[RADIOLIB_AES128_BLOCK_SIZE];
|
||||||
|
foptsBuff[0] = cmd.cid;
|
||||||
|
for(size_t i = 1; i < cmd.len; i++) {
|
||||||
|
foptsBuff[i] = cmd.payload[i];
|
||||||
|
}
|
||||||
|
foptsLen = 1 + cmd.len;
|
||||||
|
uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
|
||||||
|
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen;
|
||||||
|
|
||||||
|
// encrypt it
|
||||||
|
processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the port
|
||||||
|
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(foptsLen)] = port;
|
||||||
|
|
||||||
// select encryption key based on the target port
|
// select encryption key based on the target port
|
||||||
uint8_t* encKey = this->appSKey;
|
uint8_t* encKey = this->appSKey;
|
||||||
if(port == 0) {
|
if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
|
||||||
encKey = this->nwkSEncKey;
|
encKey = this->nwkSEncKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// figure out how many encryption blocks are there
|
// encrypt the frame payload
|
||||||
size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE;
|
processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true);
|
||||||
if(len % RADIOLIB_AES128_BLOCK_SIZE) {
|
|
||||||
numBlocks++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate the encryption blocks
|
|
||||||
uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
|
||||||
uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
|
||||||
encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC;
|
|
||||||
encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK;
|
|
||||||
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
|
|
||||||
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
|
|
||||||
|
|
||||||
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
|
||||||
|
|
||||||
// now encrypt the payload
|
|
||||||
size_t remLen = len;
|
|
||||||
for(size_t i = 0; i < numBlocks; i++) {
|
|
||||||
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
|
|
||||||
|
|
||||||
// encrypt the buffer
|
|
||||||
RadioLibAES128Instance.init(encKey);
|
|
||||||
RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer);
|
|
||||||
|
|
||||||
// now xor the buffer with the payload
|
|
||||||
size_t xorLen = remLen;
|
|
||||||
if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) {
|
|
||||||
xorLen = RADIOLIB_AES128_BLOCK_SIZE;
|
|
||||||
}
|
|
||||||
for(uint8_t j = 0; j < xorLen; j++) {
|
|
||||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(foptsLen) + i*RADIOLIB_AES128_BLOCK_SIZE + j] = data[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j];
|
|
||||||
}
|
|
||||||
remLen -= xorLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create blocks for MIC calculation
|
// create blocks for MIC calculation
|
||||||
uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||||
|
@ -362,7 +424,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
||||||
|
|
||||||
// check LoRaWAN revision
|
// check LoRaWAN revision
|
||||||
if(this->rev == 1) {
|
if(this->rev == 1) {
|
||||||
uint32_t mic = ((uint32_t)(micS & 0x00FF) << 24) | ((uint32_t)(micS & 0xFF00) << 8) | ((uint32_t)(micF & 0xFF00) >> 8) | ((uint32_t)(micF & 0x00FF) << 8);
|
uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0);
|
||||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic);
|
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic);
|
||||||
} else {
|
} else {
|
||||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF);
|
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF);
|
||||||
|
@ -371,17 +433,327 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
||||||
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
||||||
|
|
||||||
// send it (without the MIC calculation blocks)
|
// send it (without the MIC calculation blocks)
|
||||||
int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS);
|
uint32_t txStart = mod->hal->millis();
|
||||||
|
uint32_t timeOnAir = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000;
|
||||||
|
int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS);
|
||||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
delete[] uplinkMsg;
|
delete[] uplinkMsg;
|
||||||
#endif
|
#endif
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
// TODO implement listening for downlinks in RX1/RX2 slots
|
// set the timestamp so that we can measure when to start receiving
|
||||||
|
this->rxDelayStart = txStart + timeOnAir;
|
||||||
return(RADIOLIB_ERR_NONE);
|
return(RADIOLIB_ERR_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(RADIOLIB_BUILD_ARDUINO)
|
||||||
|
int16_t LoRaWANNode::downlink(String& str) {
|
||||||
|
int16_t state = RADIOLIB_ERR_NONE;
|
||||||
|
|
||||||
|
// build a temporary buffer
|
||||||
|
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
|
||||||
|
size_t length = 0;
|
||||||
|
uint8_t data[251];
|
||||||
|
|
||||||
|
// wait for downlink
|
||||||
|
state = this->downlink(data, &length);
|
||||||
|
if(state == RADIOLIB_ERR_NONE) {
|
||||||
|
// add null terminator
|
||||||
|
data[length] = '\0';
|
||||||
|
|
||||||
|
// initialize Arduino String class
|
||||||
|
str = String((char*)data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(state);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
|
||||||
|
// check if there are any upcoming Rx windows
|
||||||
|
Module* mod = this->phyLayer->getMod();
|
||||||
|
const uint32_t scanGuard = 500;
|
||||||
|
if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) {
|
||||||
|
// time since last Tx is greater than RX2 delay + some guard period
|
||||||
|
// we have nothing to downlink
|
||||||
|
return(RADIOLIB_ERR_NO_RX_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// downlink messages are sent with inverted IQ
|
||||||
|
int16_t state = RADIOLIB_ERR_UNKNOWN;
|
||||||
|
if(!this->FSK) {
|
||||||
|
state = this->phyLayer->invertIQ(true);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the channel scanning timeout
|
||||||
|
// according to the spec, this must be at least enough time to effectively detect a preamble
|
||||||
|
uint32_t scanTimeout = this->phyLayer->getTimeOnAir(0)/1000;
|
||||||
|
|
||||||
|
// set up everything for channel scan
|
||||||
|
downlinkReceived = false;
|
||||||
|
scanFlag = false;
|
||||||
|
bool packetDetected = false;
|
||||||
|
this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan);
|
||||||
|
|
||||||
|
// perform listening in the two Rx windows
|
||||||
|
for(uint8_t i = 0; i < 2; i++) {
|
||||||
|
// wait for the start of the Rx window
|
||||||
|
// the waiting duration is shortened a bit to cover any possible timing errors
|
||||||
|
uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart);
|
||||||
|
if(waitLen > scanGuard) {
|
||||||
|
waitLen -= scanGuard;
|
||||||
|
}
|
||||||
|
mod->hal->delay(waitLen);
|
||||||
|
|
||||||
|
// wait until we get a preamble
|
||||||
|
uint32_t scanStart = mod->hal->millis();
|
||||||
|
while((mod->hal->millis() - scanStart) < (scanTimeout + scanGuard)) {
|
||||||
|
// check channel detection timeout
|
||||||
|
state = this->phyLayer->startChannelScan();
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
// wait with some timeout, though it should not be hit
|
||||||
|
uint32_t cadStart = mod->hal->millis();
|
||||||
|
while(!scanFlag) {
|
||||||
|
mod->hal->yield();
|
||||||
|
if(mod->hal->millis() - cadStart >= 3000) {
|
||||||
|
// timed out, stop waiting
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the scan result
|
||||||
|
scanFlag = false;
|
||||||
|
state = this->phyLayer->getChannelScanResult();
|
||||||
|
if((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) {
|
||||||
|
packetDetected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have a packet
|
||||||
|
if(packetDetected) {
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else if(i == 0) {
|
||||||
|
// nothing in the first window, configure for the second
|
||||||
|
state = this->phyLayer->setFrequency(this->band->backupChannel.freqStart);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
DataRate_t datr;
|
||||||
|
findDataRate(RADIOLIB_LORAWAN_DATA_RATE_UNUSED, &datr, &this->band->backupChannel);
|
||||||
|
state = this->phyLayer->setDataRate(datr);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we received a packet at all
|
||||||
|
if(!packetDetected) {
|
||||||
|
this->phyLayer->standby();
|
||||||
|
if(!this->FSK) {
|
||||||
|
this->phyLayer->invertIQ(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore the original uplink channel
|
||||||
|
this->configureChannel(0, this->dataRate);
|
||||||
|
|
||||||
|
return(RADIOLIB_ERR_RX_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel scan is finished, swap the actions
|
||||||
|
this->phyLayer->clearChannelScanAction();
|
||||||
|
downlinkReceived = false;
|
||||||
|
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
|
||||||
|
|
||||||
|
// start receiving
|
||||||
|
state = this->phyLayer->startReceive();
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
// wait for reception with some timeout
|
||||||
|
uint32_t rxStart = mod->hal->millis();
|
||||||
|
while(!downlinkReceived) {
|
||||||
|
mod->hal->yield();
|
||||||
|
// let's hope 30 seconds is long enough timeout
|
||||||
|
if(mod->hal->millis() - rxStart >= 30000) {
|
||||||
|
// timed out
|
||||||
|
this->phyLayer->standby();
|
||||||
|
if(!this->FSK) {
|
||||||
|
this->phyLayer->invertIQ(false);
|
||||||
|
}
|
||||||
|
return(RADIOLIB_ERR_RX_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have a message, clear actions, go to standby and reset the IQ inversion
|
||||||
|
downlinkReceived = false;
|
||||||
|
this->phyLayer->standby();
|
||||||
|
this->phyLayer->clearPacketReceivedAction();
|
||||||
|
if(!this->FSK) {
|
||||||
|
state = this->phyLayer->invertIQ(false);
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the packet length
|
||||||
|
size_t downlinkMsgLen = this->phyLayer->getPacketLength();
|
||||||
|
|
||||||
|
// check the minimum required frame length
|
||||||
|
// an extra byte is subtracted because downlink frames may not have a port
|
||||||
|
if(downlinkMsgLen < RADIOLIB_LORAWAN_FRAME_LEN(0, 0) - 1 - RADIOLIB_AES128_BLOCK_SIZE) {
|
||||||
|
RADIOLIB_DEBUG_PRINTLN("Downlink message too short (%lu bytes)", downlinkMsgLen);
|
||||||
|
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the buffer for the downlink message
|
||||||
|
// the first 16 bytes are reserved for MIC calculation block
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen];
|
||||||
|
#else
|
||||||
|
uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// set the MIC calculation block
|
||||||
|
// TODO implement confirmed frames
|
||||||
|
memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE);
|
||||||
|
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC;
|
||||||
|
LoRaWANNode::hton<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
|
||||||
|
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK;
|
||||||
|
downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t);
|
||||||
|
|
||||||
|
// read the data
|
||||||
|
state = this->phyLayer->readData(&downlinkMsg[RADIOLIB_AES128_BLOCK_SIZE], downlinkMsgLen);
|
||||||
|
// downlink frames are sent without CRC, which will raise error on SX127x
|
||||||
|
// we can ignore that error
|
||||||
|
if(state == RADIOLIB_ERR_LORA_HEADER_DAMAGED) {
|
||||||
|
state = RADIOLIB_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the frame counter and set it to the MIC calculation block
|
||||||
|
// TODO this will not handle overflow into 32-bits!
|
||||||
|
// TODO cache the ADR bit?
|
||||||
|
uint16_t fcnt = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
|
||||||
|
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
|
||||||
|
|
||||||
|
//Module::hexdump(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
|
||||||
|
|
||||||
|
if(state != RADIOLIB_ERR_NONE) {
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] downlinkMsg;
|
||||||
|
#endif
|
||||||
|
return(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the MIC
|
||||||
|
if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) {
|
||||||
|
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the address
|
||||||
|
uint32_t addr = LoRaWANNode::ntoh<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]);
|
||||||
|
if(addr != this->devAddr) {
|
||||||
|
RADIOLIB_DEBUG_PRINTLN("Device address mismatch, expected 0x%08X, got 0x%08X", this->devAddr, addr);
|
||||||
|
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check fopts len
|
||||||
|
uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
|
||||||
|
if(foptsLen > 0) {
|
||||||
|
// there are some Fopts, decrypt them
|
||||||
|
uint8_t fopts[RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK];
|
||||||
|
|
||||||
|
// according to the specification, the last two arguments should be 0x00 and false,
|
||||||
|
// but that will fail even for LoRaWAN 1.1.0 server
|
||||||
|
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true);
|
||||||
|
|
||||||
|
//Module::hexdump(fopts, foptsLen);
|
||||||
|
|
||||||
|
// process the MAC command(s)
|
||||||
|
int8_t remLen = foptsLen;
|
||||||
|
uint8_t* foptsPtr = fopts;
|
||||||
|
while(remLen > 0) {
|
||||||
|
LoRaWANMacCommand_t cmd = {
|
||||||
|
.cid = *foptsPtr,
|
||||||
|
.len = (uint8_t)(remLen - 1),
|
||||||
|
.payload = { 0 },
|
||||||
|
};
|
||||||
|
memcpy(cmd.payload, foptsPtr + 1, cmd.len);
|
||||||
|
|
||||||
|
// try to process the mac command
|
||||||
|
// TODO how to handle incomplete commands?
|
||||||
|
size_t processedLen = execMacCommand(&cmd) + 1;
|
||||||
|
|
||||||
|
// processing succeeded, move in the buffer to the next command
|
||||||
|
remLen -= processedLen;
|
||||||
|
foptsPtr += processedLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fopts are processed or not present, check if there is payload
|
||||||
|
int payLen = downlinkMsgLen - 8 - foptsLen - sizeof(uint32_t);
|
||||||
|
if(payLen <= 0) {
|
||||||
|
// no payload
|
||||||
|
*len = 0;
|
||||||
|
return(RADIOLIB_ERR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is payload, and so there should be a port too
|
||||||
|
// TODO pass the port?
|
||||||
|
*len = payLen - 1;
|
||||||
|
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
|
||||||
|
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] downlinkMsg;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoRaWANNode::setDeviceStatus(uint8_t battLevel) {
|
||||||
|
this->battLevel = battLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span) {
|
||||||
|
uint8_t dataRateBand = span->dataRates[dr];
|
||||||
|
this->dataRate = dr;
|
||||||
|
if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
||||||
|
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) {
|
||||||
|
if(span->dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
||||||
|
dataRateBand = span->dataRates[i];
|
||||||
|
this->dataRate = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) {
|
||||||
|
datr->fsk.bitRate = 50;
|
||||||
|
datr->fsk.freqDev = 25;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
uint8_t bw = dataRateBand & 0x03;
|
||||||
|
switch(bw) {
|
||||||
|
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
|
||||||
|
datr->lora.bandwidth = 125.0;
|
||||||
|
break;
|
||||||
|
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
|
||||||
|
datr->lora.bandwidth = 250.0;
|
||||||
|
break;
|
||||||
|
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
|
||||||
|
datr->lora.bandwidth = 500.0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
datr->lora.bandwidth = 125.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
datr->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
|
int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
|
||||||
// find the span based on the channel ID
|
// find the span based on the channel ID
|
||||||
uint8_t span = 0;
|
uint8_t span = 0;
|
||||||
|
@ -401,58 +773,19 @@ int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!found) {
|
if(!found) {
|
||||||
return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS);
|
return(RADIOLIB_ERR_INVALID_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->chIndex = chan;
|
this->chIndex = chan;
|
||||||
RADIOLIB_DEBUG_PRINTLN("Channel span %d, channel %d", span, spanChannelId);
|
|
||||||
|
|
||||||
// set the frequency
|
// set the frequency
|
||||||
float freq = this->band->defaultChannels[span].freqStart + this->band->defaultChannels[span].freqStep * (float)spanChannelId;
|
float freq = this->band->defaultChannels[span].freqStart + this->band->defaultChannels[span].freqStep * (float)spanChannelId;
|
||||||
RADIOLIB_DEBUG_PRINTLN("Frequency %f MHz", freq);
|
|
||||||
int state = this->phyLayer->setFrequency(freq);
|
int state = this->phyLayer->setFrequency(freq);
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
// set the data rate
|
// set the data rate
|
||||||
uint8_t dataRateBand = this->band->defaultChannels[span].dataRates[dr];
|
|
||||||
this->dataRate = dr;
|
|
||||||
if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
|
||||||
// find the first usable data rate
|
|
||||||
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) {
|
|
||||||
if(this->band->defaultChannels[span].dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
|
||||||
dataRateBand = this->band->defaultChannels[span].dataRates[i];
|
|
||||||
this->dataRate = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RADIOLIB_DEBUG_PRINTLN("Data rate %02x", dataRateBand);
|
|
||||||
DataRate_t datr;
|
DataRate_t datr;
|
||||||
if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) {
|
findDataRate(dr, &datr, &this->band->defaultChannels[span]);
|
||||||
datr.fsk.bitRate = 50;
|
|
||||||
datr.fsk.freqDev = 25;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
uint8_t bw = dataRateBand & 0x03;
|
|
||||||
switch(bw) {
|
|
||||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
|
|
||||||
datr.lora.bandwidth = 125.0;
|
|
||||||
break;
|
|
||||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
|
|
||||||
datr.lora.bandwidth = 250.0;
|
|
||||||
break;
|
|
||||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
|
|
||||||
datr.lora.bandwidth = 500.0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return(RADIOLIB_ERR_INVALID_BANDWIDTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
datr.lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
state = this->phyLayer->setDataRate(datr);
|
state = this->phyLayer->setDataRate(datr);
|
||||||
|
|
||||||
return(state);
|
return(state);
|
||||||
|
@ -480,7 +813,7 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) {
|
||||||
// calculate the expected value and compare
|
// calculate the expected value and compare
|
||||||
uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key);
|
uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key);
|
||||||
if(micCalculated != micReceived) {
|
if(micCalculated != micReceived) {
|
||||||
RADIOLIB_DEBUG_PRINTLN("MIC mismatch, expected 0x%08x, got 0x%08x", micCalculated, micReceived);
|
RADIOLIB_DEBUG_PRINTLN("MIC mismatch, expected %08x, got %08x", micCalculated, micReceived);
|
||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,8 +841,14 @@ int16_t LoRaWANNode::setPhyProperties() {
|
||||||
state = this->configureChannel(channelId, this->band->defaultChannels[0].joinRequestDataRate);
|
state = this->configureChannel(channelId, this->band->defaultChannels[0].joinRequestDataRate);
|
||||||
}
|
}
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
state = this->phyLayer->setOutputPower(this->band->powerMax);
|
// set the maximum power supported by both the module and the band
|
||||||
|
int8_t pwr = this->band->powerMax;
|
||||||
|
state = RADIOLIB_ERR_INVALID_OUTPUT_POWER;
|
||||||
|
while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) {
|
||||||
|
// go from the highest power in band and lower it until we hit one supported by the module
|
||||||
|
state = this->phyLayer->setOutputPower(pwr--);
|
||||||
|
}
|
||||||
RADIOLIB_ASSERT(state);
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
uint8_t syncWord[3] = { 0 };
|
uint8_t syncWord[3] = { 0 };
|
||||||
|
@ -536,6 +875,189 @@ int16_t LoRaWANNode::setPhyProperties() {
|
||||||
return(state);
|
return(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen) {
|
||||||
|
// build the command
|
||||||
|
size_t macReqLen = 1 + payloadLen;
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
uint8_t* macReqBuff = new uint8_t[macReqLen];
|
||||||
|
#else
|
||||||
|
uint8_t macReqBuff[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||||
|
#endif
|
||||||
|
macReqBuff[0] = cid;
|
||||||
|
memcpy(&macReqBuff[1], payload, payloadLen);
|
||||||
|
|
||||||
|
// uplink it
|
||||||
|
int16_t state = this->uplink(macReqBuff, macReqLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] macReqBuff;
|
||||||
|
#endif
|
||||||
|
RADIOLIB_ASSERT(state);
|
||||||
|
|
||||||
|
// build the reply buffer
|
||||||
|
size_t macRplLen = 1 + replyLen;
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
uint8_t* macRplBuff = new uint8_t[this->band->payloadLenMax[this->dataRate]];
|
||||||
|
#else
|
||||||
|
uint8_t macRplBuff[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// wait for reply from the server
|
||||||
|
size_t rxRplLen = 0;
|
||||||
|
state = this->downlink(macRplBuff, &rxRplLen);
|
||||||
|
if(state != RADIOLIB_ERR_NONE) {
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] macRplBuff;
|
||||||
|
#endif
|
||||||
|
return(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Module::hexdump(macRplBuff, rxRplLen);
|
||||||
|
|
||||||
|
// check the length - it may be longer than expected
|
||||||
|
// if the server decided to append more MAC commands, but never shorter
|
||||||
|
// TODO how to handle the additional command(s)?
|
||||||
|
if(rxRplLen < macRplLen) {
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] macRplBuff;
|
||||||
|
#endif
|
||||||
|
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the CID
|
||||||
|
if(macRplBuff[0] != cid) {
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] macRplBuff;
|
||||||
|
#endif
|
||||||
|
return(RADIOLIB_ERR_INVALID_CID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the data
|
||||||
|
memcpy(reply, &macRplBuff[1], replyLen);
|
||||||
|
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||||
|
delete[] macRplBuff;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue) {
|
||||||
|
if(queue->numCommands >= RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) {
|
||||||
|
return(RADIOLIB_ERR_COMMAND_QUEUE_FULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&queue->commands[queue->numCommands], cmd, sizeof(LoRaWANMacCommand_t));
|
||||||
|
/*RADIOLIB_DEBUG_PRINTLN("push MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
|
||||||
|
queue->commands[queue->numCommands - 1].cid,
|
||||||
|
queue->commands[queue->numCommands - 1].len,
|
||||||
|
queue->commands[queue->numCommands - 1].payload[0],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[1],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[2],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[3],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[4],
|
||||||
|
queue->commands[queue->numCommands - 1].repeat);*/
|
||||||
|
queue->numCommands++;
|
||||||
|
|
||||||
|
return(RADIOLIB_ERR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force) {
|
||||||
|
if(queue->numCommands == 0) {
|
||||||
|
return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cmd) {
|
||||||
|
/*RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
|
||||||
|
queue->commands[queue->numCommands - 1].cid,
|
||||||
|
queue->commands[queue->numCommands - 1].len,
|
||||||
|
queue->commands[queue->numCommands - 1].payload[0],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[1],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[2],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[3],
|
||||||
|
queue->commands[queue->numCommands - 1].payload[4],
|
||||||
|
queue->commands[queue->numCommands - 1].repeat);*/
|
||||||
|
memcpy(cmd, &queue->commands[queue->numCommands - 1], sizeof(LoRaWANMacCommand_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
if((!force) && (queue->commands[queue->numCommands - 1].repeat > 0)) {
|
||||||
|
queue->commands[queue->numCommands - 1].repeat--;
|
||||||
|
} else {
|
||||||
|
queue->commands[queue->numCommands - 1].repeat = 0;
|
||||||
|
queue->numCommands--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return(RADIOLIB_ERR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
|
||||||
|
//RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len);
|
||||||
|
|
||||||
|
switch(cmd->cid) {
|
||||||
|
case(RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS): {
|
||||||
|
// set the uplink reply
|
||||||
|
cmd->len = 2;
|
||||||
|
cmd->payload[1] = this->battLevel;
|
||||||
|
int8_t snr = this->phyLayer->getSNR();
|
||||||
|
cmd->payload[0] = snr & 0x3F;
|
||||||
|
|
||||||
|
// push it to the uplink queue
|
||||||
|
pushMacCommand(cmd, &this->commandsUp);
|
||||||
|
return(0);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND): {
|
||||||
|
// TODO verify the actual server version here
|
||||||
|
|
||||||
|
// stop sending the ReKey MAC command
|
||||||
|
popMacCommand(NULL, &this->commandsUp, true);
|
||||||
|
return(1);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter) {
|
||||||
|
// figure out how many encryption blocks are there
|
||||||
|
size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE;
|
||||||
|
if(len % RADIOLIB_AES128_BLOCK_SIZE) {
|
||||||
|
numBlocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the encryption blocks
|
||||||
|
uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||||
|
uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||||
|
encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC;
|
||||||
|
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS] = ctrId;
|
||||||
|
encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = dir;
|
||||||
|
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
|
||||||
|
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
|
||||||
|
|
||||||
|
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
||||||
|
|
||||||
|
// now encrypt the input
|
||||||
|
// on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext
|
||||||
|
size_t remLen = len;
|
||||||
|
for(size_t i = 0; i < numBlocks; i++) {
|
||||||
|
if(counter) {
|
||||||
|
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt the buffer
|
||||||
|
RadioLibAES128Instance.init(key);
|
||||||
|
RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer);
|
||||||
|
|
||||||
|
// now xor the buffer with the input
|
||||||
|
size_t xorLen = remLen;
|
||||||
|
if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) {
|
||||||
|
xorLen = RADIOLIB_AES128_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
for(uint8_t j = 0; j < xorLen; j++) {
|
||||||
|
out[i*RADIOLIB_AES128_BLOCK_SIZE + j] = in[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j];
|
||||||
|
}
|
||||||
|
remLen -= xorLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T LoRaWANNode::ntoh(uint8_t* buff, size_t size) {
|
T LoRaWANNode::ntoh(uint8_t* buff, size_t size) {
|
||||||
uint8_t* buffPtr = buff;
|
uint8_t* buffPtr = buff;
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
// recommended default settings
|
// recommended default settings
|
||||||
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000)
|
#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_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_RX1_DR_OFFSET (0)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
|
||||||
|
@ -83,14 +84,20 @@
|
||||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1)
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS (9)
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS (9)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS (17)
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS (17)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE (0xFF)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_0 (0x00)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_1 (0x01)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_2 (0x02)
|
||||||
|
|
||||||
// join accept message layout
|
// join accept message layout
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN (33)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN (33)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS (7)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS (7)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS (4)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS (11)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS (11)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS (12)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS (12)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS (12)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS (13)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS (13)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN (16)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN (16)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS (RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN - 1)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS (RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN - 1)
|
||||||
|
@ -102,17 +109,20 @@
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY (0x02)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY (0x02)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY (0x03)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY (0x03)
|
||||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY (0x04)
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY (0x04)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_ENC_KEY (0x05)
|
||||||
|
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY (0x06)
|
||||||
|
|
||||||
// uplink message layout
|
// frame header layout
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
|
#define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS (16)
|
#define RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 1)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 1)
|
#define RADIOLIB_LORAWAN_FHDR_FCTRL_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 5)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_FCTRL_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 5)
|
#define RADIOLIB_LORAWAN_FHDR_FCNT_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 6)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_FCNT_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 6)
|
#define RADIOLIB_LORAWAN_FHDR_FOPTS_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8)
|
#define RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK (0x0F)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 16)
|
#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 16)
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8 + (FOPTS))
|
#define RADIOLIB_LORAWAN_FHDR_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8 + (FOPTS))
|
||||||
#define RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 9 + (FOPTS))
|
#define RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 9 + (FOPTS))
|
||||||
|
#define RADIOLIB_LORAWAN_FRAME_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
|
||||||
|
|
||||||
// payload encryption/MIC blocks common layout
|
// payload encryption/MIC blocks common layout
|
||||||
#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0)
|
#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0)
|
||||||
|
@ -122,6 +132,7 @@
|
||||||
|
|
||||||
// payload encryption block layout
|
// payload encryption block layout
|
||||||
#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01)
|
#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01)
|
||||||
|
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS (4)
|
||||||
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15)
|
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15)
|
||||||
|
|
||||||
// payload MIC blocks layout
|
// payload MIC blocks layout
|
||||||
|
@ -133,6 +144,25 @@
|
||||||
// magic word saved in persistent memory upon activation
|
// magic word saved in persistent memory upon activation
|
||||||
#define RADIOLIB_LORAWAN_MAGIC (0x12AD101B)
|
#define RADIOLIB_LORAWAN_MAGIC (0x12AD101B)
|
||||||
|
|
||||||
|
// MAC commands
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_RESET_IND (0x01)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_LINK_CHECK_REQ (0x02)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR_ANS (0x03)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_DUTY_CYCLE_ANS (0x04)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP_ANS (0x05)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS (0x06)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_NEW_CHANNEL_ANS (0x07)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP_ANS (0x08)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_TX_PARAM_SETUP_ANS (0x09)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_DI_CHANNEL_ANS (0x0A)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND (0x0B)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP_ANS (0x0C)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME_REQ (0x0D)
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP_ANS (0x0F)
|
||||||
|
|
||||||
|
// the length of internal MAC command queue - hopefully this is enough for most use cases
|
||||||
|
#define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (8)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\struct LoRaWANChannelSpan_t
|
\struct LoRaWANChannelSpan_t
|
||||||
\brief Structure to save information about LoRaWAN channels.
|
\brief Structure to save information about LoRaWAN channels.
|
||||||
|
@ -208,6 +238,29 @@ extern const LoRaWANBand_t AS923;
|
||||||
extern const LoRaWANBand_t KR920;
|
extern const LoRaWANBand_t KR920;
|
||||||
extern const LoRaWANBand_t IN865;
|
extern const LoRaWANBand_t IN865;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\struct LoRaWANMacCommand_t
|
||||||
|
\brief Structure to save information about MAC command
|
||||||
|
*/
|
||||||
|
struct LoRaWANMacCommand_t {
|
||||||
|
/*! \brief The command ID */
|
||||||
|
uint8_t cid;
|
||||||
|
|
||||||
|
/*! \brief Length of the payload */
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
/*! \brief Payload buffer (5 bytes is the longest possible) */
|
||||||
|
uint8_t payload[5];
|
||||||
|
|
||||||
|
/*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */
|
||||||
|
uint8_t repeat;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoRaWANMacCommandQueue_t {
|
||||||
|
LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE];
|
||||||
|
size_t numCommands;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\class LoRaWANNode
|
\class LoRaWANNode
|
||||||
\brief LoRaWAN-compatible node (class A device).
|
\brief LoRaWAN-compatible node (class A device).
|
||||||
|
@ -239,14 +292,14 @@ class LoRaWANNode {
|
||||||
/*!
|
/*!
|
||||||
\brief Join network by performing over-the-air activation. By this procedure,
|
\brief Join network by performing over-the-air activation. By this procedure,
|
||||||
the device will perform an exchange with the network server and set all necessary configuration.
|
the device will perform an exchange with the network server and set all necessary configuration.
|
||||||
\param appEUI 8-byte application identifier.
|
\param joinEUI 8-byte application identifier.
|
||||||
\param devEUI 8-byte device identifier.
|
\param devEUI 8-byte device identifier.
|
||||||
\param nwkKey Pointer to the network AES-128 key.
|
\param nwkKey Pointer to the network AES-128 key.
|
||||||
\param appKey Pointer to the application AES-128 key.
|
\param appKey Pointer to the application AES-128 key.
|
||||||
\param force Set to true to force joining even if previously joined.
|
\param force Set to true to force joining even if previously joined.
|
||||||
\returns \ref status_codes
|
\returns \ref status_codes
|
||||||
*/
|
*/
|
||||||
int16_t beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false);
|
int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Join network by performing activation by personalization.
|
\brief Join network by performing activation by personalization.
|
||||||
|
@ -287,6 +340,71 @@ class LoRaWANNode {
|
||||||
*/
|
*/
|
||||||
int16_t uplink(uint8_t* data, size_t len, uint8_t port);
|
int16_t uplink(uint8_t* data, size_t len, uint8_t port);
|
||||||
|
|
||||||
|
#if defined(RADIOLIB_BUILD_ARDUINO)
|
||||||
|
/*!
|
||||||
|
\brief Wait for downlink from the server in either RX1 or RX2 window.
|
||||||
|
\param str Address of Arduino String to save the received data.
|
||||||
|
\returns \ref status_codes
|
||||||
|
*/
|
||||||
|
int16_t downlink(String& str);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Wait for downlink from the server in either RX1 or RX2 window.
|
||||||
|
\param data Buffer to save received data into.
|
||||||
|
\param len Pointer to variable that will be used to save the number of received bytes.
|
||||||
|
\returns \ref status_codes
|
||||||
|
*/
|
||||||
|
int16_t downlink(uint8_t* data, size_t* len);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Set device status.
|
||||||
|
\param battLevel Battery level to set. 0 for external power source, 1 for lowest battery,
|
||||||
|
254 for highest battery, 255 for unable to measure.
|
||||||
|
*/
|
||||||
|
void setDeviceStatus(uint8_t battLevel);
|
||||||
|
|
||||||
|
#if !defined(RADIOLIB_GODMODE)
|
||||||
|
private:
|
||||||
|
#endif
|
||||||
|
PhysicalLayer* phyLayer = NULL;
|
||||||
|
const LoRaWANBand_t* band = NULL;
|
||||||
|
|
||||||
|
LoRaWANMacCommandQueue_t commandsUp = { .commands = { 0 }, .numCommands = 0 };
|
||||||
|
LoRaWANMacCommandQueue_t commandsDown = { .commands = { 0 }, .numCommands = 0 };
|
||||||
|
|
||||||
|
// the following is either provided by the network server (OTAA)
|
||||||
|
// or directly entered by the user (ABP)
|
||||||
|
uint32_t devAddr = 0;
|
||||||
|
uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||||
|
uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||||
|
uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||||
|
uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||||
|
uint8_t jSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||||
|
float availableChannelsFreq[5] = { 0 };
|
||||||
|
uint16_t availableChannelsMask[6] = { 0 };
|
||||||
|
|
||||||
|
// LoRaWAN revision (1.0 vs 1.1)
|
||||||
|
uint8_t rev = 0;
|
||||||
|
|
||||||
|
// currently configured data rate DR0 - DR15 (band-dependent!)
|
||||||
|
uint8_t dataRate = 0;
|
||||||
|
|
||||||
|
// currently configured channel (band-dependent!)
|
||||||
|
uint8_t chIndex = 0;
|
||||||
|
|
||||||
|
// timestamp to measure the RX1/2 delay (from uplink end)
|
||||||
|
uint32_t rxDelayStart = 0;
|
||||||
|
|
||||||
|
// delays between the uplink and RX1/2 windows
|
||||||
|
uint32_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS };
|
||||||
|
|
||||||
|
// device status - battery level
|
||||||
|
uint8_t battLevel = 0xFF;
|
||||||
|
|
||||||
|
// find the first usable data rate in a given channel span
|
||||||
|
void findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Configure the radio to a given channel frequency and data rate.
|
\brief Configure the radio to a given channel frequency and data rate.
|
||||||
\param chan Channel ID to set.
|
\param chan Channel ID to set.
|
||||||
|
@ -295,32 +413,6 @@ class LoRaWANNode {
|
||||||
*/
|
*/
|
||||||
int16_t configureChannel(uint8_t chan, uint8_t dr);
|
int16_t configureChannel(uint8_t chan, uint8_t dr);
|
||||||
|
|
||||||
#if !defined(RADIOLIB_GODMODE)
|
|
||||||
private:
|
|
||||||
#endif
|
|
||||||
PhysicalLayer* phyLayer;
|
|
||||||
const LoRaWANBand_t* band;
|
|
||||||
|
|
||||||
// the following is either provided by the network server (OTAA)
|
|
||||||
// or directly entered by the user (ABP)
|
|
||||||
uint32_t devAddr;
|
|
||||||
uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE];
|
|
||||||
uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE];
|
|
||||||
uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE];
|
|
||||||
uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE];
|
|
||||||
uint8_t rxDelay;
|
|
||||||
float availableChannelsFreq[5];
|
|
||||||
uint16_t availableChannelsMask[6];
|
|
||||||
|
|
||||||
// LoRaWAN revision (1.0 vs 1.1)
|
|
||||||
uint8_t rev;
|
|
||||||
|
|
||||||
// currently configured data rate DR0 - DR15 (band-dependent!)
|
|
||||||
uint8_t dataRate;
|
|
||||||
|
|
||||||
// currently configured channel (band-dependent!)
|
|
||||||
uint8_t chIndex;
|
|
||||||
|
|
||||||
// method to generate message integrity code
|
// method to generate message integrity code
|
||||||
uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key);
|
uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key);
|
||||||
|
|
||||||
|
@ -328,8 +420,24 @@ class LoRaWANNode {
|
||||||
// it assumes that the MIC is the last 4 bytes of the message
|
// it assumes that the MIC is the last 4 bytes of the message
|
||||||
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
|
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
|
||||||
|
|
||||||
|
// configure the physical layer properties (frequency, sync word etc.)
|
||||||
int16_t setPhyProperties();
|
int16_t setPhyProperties();
|
||||||
|
|
||||||
|
// send a MAC command to the network server
|
||||||
|
int16_t sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen);
|
||||||
|
|
||||||
|
// push MAC command to queue, done by copy
|
||||||
|
int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue);
|
||||||
|
|
||||||
|
// pop MAC command from queue, done by copy unless CMD is NULL
|
||||||
|
int16_t popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force = false);
|
||||||
|
|
||||||
|
// execute mac command, return the number of processed bytes for sequential processing
|
||||||
|
size_t execMacCommand(LoRaWANMacCommand_t* cmd);
|
||||||
|
|
||||||
|
// function to encrypt and decrypt payloads
|
||||||
|
void processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter);
|
||||||
|
|
||||||
// network-to-host conversion method - takes data from network packet and converts it to the host endians
|
// network-to-host conversion method - takes data from network packet and converts it to the host endians
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static T ntoh(uint8_t* buff, size_t size = 0);
|
static T ntoh(uint8_t* buff, size_t size = 0);
|
||||||
|
|
|
@ -289,6 +289,19 @@ float PhysicalLayer::getSNR() {
|
||||||
return(RADIOLIB_ERR_UNSUPPORTED);
|
return(RADIOLIB_ERR_UNSUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t PhysicalLayer::getTimeOnAir(size_t len) {
|
||||||
|
(void)len;
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t PhysicalLayer::startChannelScan() {
|
||||||
|
return(RADIOLIB_ERR_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t PhysicalLayer::getChannelScanResult() {
|
||||||
|
return(RADIOLIB_ERR_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
int16_t PhysicalLayer::scanChannel() {
|
int16_t PhysicalLayer::scanChannel() {
|
||||||
return(RADIOLIB_ERR_UNSUPPORTED);
|
return(RADIOLIB_ERR_UNSUPPORTED);
|
||||||
}
|
}
|
||||||
|
@ -441,6 +454,14 @@ void PhysicalLayer::clearPacketSentAction() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicalLayer::setChannelScanAction(void (*func)(void)) {
|
||||||
|
(void)func;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalLayer::clearChannelScanAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(RADIOLIB_INTERRUPT_TIMING)
|
#if defined(RADIOLIB_INTERRUPT_TIMING)
|
||||||
void PhysicalLayer::setInterruptSetup(void (*func)(uint32_t)) {
|
void PhysicalLayer::setInterruptSetup(void (*func)(uint32_t)) {
|
||||||
Module* mod = getMod();
|
Module* mod = getMod();
|
||||||
|
|
|
@ -302,6 +302,26 @@ class PhysicalLayer {
|
||||||
*/
|
*/
|
||||||
virtual float getSNR();
|
virtual float getSNR();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Get expected time-on-air for a given size of payload
|
||||||
|
\param len Payload length in bytes.
|
||||||
|
\returns Expected time-on-air in microseconds.
|
||||||
|
*/
|
||||||
|
virtual uint32_t getTimeOnAir(size_t len);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Interrupt-driven channel activity detection method. interrupt will be activated
|
||||||
|
when packet is detected. Must be implemented in module class.
|
||||||
|
\returns \ref status_codes
|
||||||
|
*/
|
||||||
|
virtual int16_t startChannelScan();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Read the channel scan result
|
||||||
|
\returns \ref status_codes
|
||||||
|
*/
|
||||||
|
virtual int16_t getChannelScanResult();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Check whether the current communication channel is free or occupied. Performs CAD for LoRa modules,
|
\brief Check whether the current communication channel is free or occupied. Performs CAD for LoRa modules,
|
||||||
or RSSI measurement for FSK modules.
|
or RSSI measurement for FSK modules.
|
||||||
|
@ -409,6 +429,17 @@ class PhysicalLayer {
|
||||||
\brief Clears interrupt service routine to call when a packet is sent.
|
\brief Clears interrupt service routine to call when a packet is sent.
|
||||||
*/
|
*/
|
||||||
virtual void clearPacketSentAction();
|
virtual void clearPacketSentAction();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Sets interrupt service routine to call when a channel scan is finished.
|
||||||
|
\param func ISR to call.
|
||||||
|
*/
|
||||||
|
virtual void setChannelScanAction(void (*func)(void));
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Clears interrupt service routine to call when a channel scan is finished.
|
||||||
|
*/
|
||||||
|
virtual void clearChannelScanAction();
|
||||||
|
|
||||||
#if defined(RADIOLIB_INTERRUPT_TIMING)
|
#if defined(RADIOLIB_INTERRUPT_TIMING)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue