Merge pull request #1 from nicklasb/nicklasb-patch-narrowing

Fix narrowing conversion error on ESP-IDF
This commit is contained in:
Nicklas Börjesson 2023-08-23 23:48:29 +02:00 committed by GitHub
commit f8ae458185
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1286 additions and 209 deletions

View file

@ -47,7 +47,7 @@ jobs:
# platform-dependent settings - extra board options, board index URLs, skip patterns etc.
include:
- 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
run: echo "options=':cpu=atmega2560'" >> $GITHUB_OUTPUT
- id: arduino:avr:leonardo

View file

@ -60,10 +60,10 @@ void setup() {
// the end device in TTN and perform the join procedure again!
//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
// 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
// when adding new end device in TTN, you can generate this number,
@ -76,10 +76,14 @@ void setup() {
const char nwkKey[] = "topSecretKey1234";
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
// this can take up to 20 seconds, and requires a LoRaWAN gateway in range
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) {
Serial.println(F("success!"));
} else {
@ -110,8 +114,8 @@ int count = 0;
void loop() {
// send uplink to port 10
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
String str = "Hello World! #" + String(count++);
int state = node.uplink(str, 10);
String strUp = "Hello World! #" + String(count++);
int state = node.uplink(strUp, 10);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
@ -119,6 +123,47 @@ void loop() {
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);
}

View file

@ -72,6 +72,10 @@ void setup() {
const char nwkSKey[] = "topSecretKey1234";
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
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey);
@ -105,8 +109,8 @@ int count = 0;
void loop() {
// send uplink to port 10
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
String str = "Hello World! #" + String(count++);
int state = node.uplink(str, 10);
String strUp = "Hello World! #" + String(count++);
int state = node.uplink(strUp, 10);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
@ -114,6 +118,47 @@ void loop() {
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);
}

View file

@ -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);
}
}
}
}

View file

@ -292,6 +292,7 @@ wipe KEYWORD2
beginOTAA KEYWORD2
beginAPB KEYWORD2
uplink KEYWORD2
downlink KEYWORD2
configureChannel KEYWORD2
#######################################
@ -390,3 +391,13 @@ RADIOLIB_ERR_RANGING_TIMEOUT LITERAL1
RADIOLIB_ERR_INVALID_PAYLOAD LITERAL1
RADIOLIB_ERR_ADDRESS_NOT_FOUND 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

View file

@ -44,6 +44,10 @@ void Module::init() {
this->hal->init();
this->hal->pinMode(csPin, this->hal->GpioModeOutput);
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() {
@ -166,11 +170,11 @@ void Module::SPItransfer(uint8_t cmd, uint16_t reg, uint8_t* dataOut, uint8_t* d
}
// do the transfer
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
this->hal->spiBeginTransaction();
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
this->hal->spiTransfer(buffOut, buffLen, buffIn);
this->hal->spiEndTransaction();
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh);
this->hal->spiEndTransaction();
// copy the data
if(cmd == SPIreadCommand) {
@ -298,11 +302,11 @@ int16_t Module::SPItransferStream(uint8_t* cmd, uint8_t cmdLen, bool write, uint
}
// do the transfer
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
this->hal->spiBeginTransaction();
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow);
this->hal->spiTransfer(buffOut, buffLen, buffIn);
this->hal->spiEndTransaction();
this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh);
this->hal->spiEndTransaction();
// wait for GPIO to go high and then low
if(waitForGpio) {
@ -327,7 +331,7 @@ int16_t Module::SPItransferStream(uint8_t* cmd, uint8_t cmdLen, bool write, uint
// parse status
int16_t state = RADIOLIB_ERR_NONE;
if(this->SPIparseStatusCb != nullptr) {
if((this->SPIparseStatusCb != nullptr) && (numBytes > 0)) {
state = this->SPIparseStatusCb(buffIn[cmdLen]);
}

View file

@ -51,18 +51,6 @@
#warning "God mode active, I hope it was intentional. Buckle up, lads."
#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
#if defined(RADIOLIB_UNKNOWN_PLATFORM)
#warning "RadioLib might not be compatible with this Arduino board - check supported platforms at https://github.com/jgromes/RadioLib!"

View file

@ -481,6 +481,58 @@
*/
#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)
/*!
\}
*/

View file

@ -91,6 +91,9 @@ int16_t SX126x::begin(uint8_t cr, uint8_t syncWord, uint16_t preambleLength, flo
state = setCRC(2);
RADIOLIB_ASSERT(state);
state = invertIQ(false);
RADIOLIB_ASSERT(state);
return(state);
}
@ -504,6 +507,15 @@ void SX126x::clearPacketSentAction() {
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) {
// suppress unused variable warning
(void)addr;
@ -726,6 +738,10 @@ int16_t SX126x::readData(uint8_t* data, size_t len) {
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) {
// check active modem
if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
@ -762,11 +778,9 @@ int16_t SX126x::getChannelScanResult() {
uint16_t cadResult = getIrqStatus();
if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DETECTED) {
// detected some LoRa activity
clearIrqStatus();
return(RADIOLIB_LORA_DETECTED);
} else if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DONE) {
// channel is free
clearIrqStatus();
return(RADIOLIB_CHANNEL_FREE);
}
@ -1718,7 +1732,7 @@ int16_t SX126x::setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) {
data[2] = detMin;
}
// configure paramaters
// configure parameters
int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data, 7);
RADIOLIB_ASSERT(state);

View file

@ -601,6 +601,17 @@ class SX126x: public PhysicalLayer {
*/
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.
Overloads for string-based transmissions are implemented in PhysicalLayer.
@ -687,20 +698,27 @@ class SX126x: public PhysicalLayer {
int16_t readData(uint8_t* data, size_t len) override;
/*!
\brief Interrupt-driven channel activity detection method. DIO0 will be activated
when LoRa preamble is detected, or upon timeout.
\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.
\brief Interrupt-driven channel activity detection method. DIO1 will be activated
when LoRa preamble is detected, or upon timeout. Defaults to CAD parameter values recommended by AN1200.48.
\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
\returns \ref status_codes
*/
int16_t getChannelScanResult();
int16_t getChannelScanResult() override;
// configuration methods
@ -929,7 +947,7 @@ class SX126x: public PhysicalLayer {
\param len Payload length in bytes.
\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.

View file

@ -51,6 +51,10 @@ int16_t SX127x::begin(uint8_t chipVersion, uint8_t syncWord, uint16_t preambleLe
state = SX127x::setPreambleLength(preambleLength);
RADIOLIB_ASSERT(state);
// disable IQ inversion
state = SX127x::invertIQ(false);
RADIOLIB_ASSERT(state);
// initialize internal variables
this->dataRate = 0.0;
@ -468,6 +472,14 @@ void SX127x::clearPacketSentAction() {
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)) {
// set DIO1 to the FIFO empty event (the register setting is done in startTransmit)
setDio1Action(func, this->mod->hal->GpioInterruptRising);
@ -697,6 +709,13 @@ int16_t SX127x::startChannelScan() {
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) {
// check active modem
if(getActiveModem() != RADIOLIB_SX127X_LORA) {

View file

@ -738,6 +738,17 @@ class SX127x: public PhysicalLayer {
*/
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.
\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.
\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
@ -1016,11 +1033,11 @@ class SX127x: public PhysicalLayer {
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.
\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.

View file

@ -24,13 +24,10 @@ int16_t APRSClient::begin(char sym, char* callsign, uint8_t ssid, bool alt) {
table = '/';
}
if((!src) && (this->phyLayer != nullptr)) {
return(RADIOLIB_ERR_INVALID_CALLSIGN);
}
if(strlen(callsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) {
return(RADIOLIB_ERR_INVALID_CALLSIGN);
}
memcpy(this->src, callsign, strlen(callsign));
this->id = ssid;

View file

@ -15,6 +15,17 @@ static void LoRaWANNodeOnDownlink(void) {
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) {
this->phyLayer = phy;
this->band = band;
@ -34,12 +45,11 @@ int16_t LoRaWANNode::begin() {
Module* mod = this->phyLayer->getMod();
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
return(RADIOLIB_ERR_CHIP_NOT_FOUND);
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
}
// pull all needed information from persistent storage
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_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);
@ -47,7 +57,7 @@ int16_t LoRaWANNode::begin() {
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
Module* mod = this->phyLayer->getMod();
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
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);
RADIOLIB_DEBUG_PRINTLN("devNonce = %d", devNonce);
// build the join-request message
uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
// set the packet fields
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<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);
// downlink messages are sent with inverted IQ
// TODO use downlink() for this
if(!this->FSK) {
state = this->phyLayer->invertIQ(true);
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);
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);
return(RADIOLIB_ERR_RX_TIMEOUT);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// read the packet
@ -136,30 +146,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
// check reply message type
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]);
return(RADIOLIB_ERR_RX_TIMEOUT);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// decrypt the join accept message
// 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];
joinAcceptMsg[0] = joinAcceptMsgEnc[0];
RadioLibAES128Instance.init(nwkKey);
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);
// verify MIC
// 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);
}
}
// parse the contents
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);
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->rxDelay = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS];
this->rxDelays[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]*1000;
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
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 {
// TODO list of masks
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
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_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)
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;
RADIOLIB_DEBUG_PRINTLN("LoRaWAN 1.1 not supported (yet)");
(void)appKey;
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
LoRaWANMacCommand_t cmd = {
.cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND,
.len = sizeof(uint8_t),
.payload = { this->rev },
.repeat = RADIOLIB_LORAWAN_ADR_ACK_LIMIT,
};
state = pushMacCommand(&cmd, &this->commandsUp);
RADIOLIB_ASSERT(state);
} else {
// 1.0 version, just derive the keys
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;
RadioLibAES128Instance.init(nwkKey);
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) {
// 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
// TODO implement Fopts
uint8_t foptsLen = 0;
// check if there are some MAC commands to piggyback
size_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]) {
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
// 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)
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#else
@ -281,62 +358,47 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
#endif
// set the packet fields
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_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);
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_FHDR_DEV_ADDR_POS], this->devAddr);
// 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
Module* mod = this->phyLayer->getMod();
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 + 1);
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCNT_POS], (uint16_t)fcnt);
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1;
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt);
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt);
// TODO implement FOpts
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FPORT_POS(foptsLen)] = port;
// check if we have some MAC command to append
// 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
uint8_t* encKey = this->appSKey;
if(port == 0) {
if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
encKey = this->nwkSEncKey;
}
// 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_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;
}
// encrypt the frame payload
processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true);
// create blocks for MIC calculation
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
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);
} else {
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);
// 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)
delete[] uplinkMsg;
#endif
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);
}
#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) {
// find the span based on the channel ID
uint8_t span = 0;
@ -401,58 +773,19 @@ int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
}
if(!found) {
return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS);
return(RADIOLIB_ERR_INVALID_CHANNEL);
}
this->chIndex = chan;
RADIOLIB_DEBUG_PRINTLN("Channel span %d, channel %d", span, spanChannelId);
// set the frequency
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);
RADIOLIB_ASSERT(state);
// 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;
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:
return(RADIOLIB_ERR_INVALID_BANDWIDTH);
}
datr.lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
}
findDataRate(dr, &datr, &this->band->defaultChannels[span]);
state = this->phyLayer->setDataRate(datr);
return(state);
@ -480,7 +813,7 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) {
// calculate the expected value and compare
uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key);
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);
}
@ -509,7 +842,13 @@ int16_t LoRaWANNode::setPhyProperties() {
}
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);
uint8_t syncWord[3] = { 0 };
@ -536,6 +875,189 @@ int16_t LoRaWANNode::setPhyProperties() {
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>
T LoRaWANNode::ntoh(uint8_t* buff, size_t size) {
uint8_t* buffPtr = buff;

View file

@ -68,6 +68,7 @@
// recommended default settings
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000)
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS ((RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS) + 1000)
#define RADIOLIB_LORAWAN_RX_WINDOW_LEN_MS (500)
#define RADIOLIB_LORAWAN_RX1_DR_OFFSET (0)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
@ -83,14 +84,20 @@
#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS (9)
#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
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN (33)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4)
#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_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_LEN (16)
#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_S_NWK_S_INT_KEY (0x03)
#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
#define RADIOLIB_LORAWAN_UPLINK_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
#define RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS (16)
#define RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 1)
#define RADIOLIB_LORAWAN_UPLINK_FCTRL_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 5)
#define RADIOLIB_LORAWAN_UPLINK_FCNT_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 6)
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8)
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 16)
#define RADIOLIB_LORAWAN_UPLINK_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8 + (FOPTS))
#define RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 9 + (FOPTS))
// frame header layout
#define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16)
#define RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 1)
#define RADIOLIB_LORAWAN_FHDR_FCTRL_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 5)
#define RADIOLIB_LORAWAN_FHDR_FCNT_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 6)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK (0x0F)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 16)
#define RADIOLIB_LORAWAN_FHDR_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8 + (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
#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0)
@ -122,6 +132,7 @@
// payload encryption block layout
#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01)
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS (4)
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15)
// payload MIC blocks layout
@ -133,6 +144,25 @@
// magic word saved in persistent memory upon activation
#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
\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 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
\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,
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 nwkKey Pointer to the network AES-128 key.
\param appKey Pointer to the application AES-128 key.
\param force Set to true to force joining even if previously joined.
\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.
@ -287,6 +340,71 @@ class LoRaWANNode {
*/
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.
\param chan Channel ID to set.
@ -295,32 +413,6 @@ class LoRaWANNode {
*/
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
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
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
// configure the physical layer properties (frequency, sync word etc.)
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
template<typename T>
static T ntoh(uint8_t* buff, size_t size = 0);

View file

@ -289,6 +289,19 @@ float PhysicalLayer::getSNR() {
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() {
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)
void PhysicalLayer::setInterruptSetup(void (*func)(uint32_t)) {
Module* mod = getMod();

View file

@ -302,6 +302,26 @@ class PhysicalLayer {
*/
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,
or RSSI measurement for FSK modules.
@ -410,6 +430,17 @@ class PhysicalLayer {
*/
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)
/*!