From ebfe7972fc8eea90d4c4d20a0de3e05b07244898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20B=C3=B6rjesson?= Date: Wed, 9 Aug 2023 23:38:39 +0200 Subject: [PATCH 01/25] Remove unnecessary condition This will never be NULL and thus causes an error in ESP-IDF --- src/protocols/APRS/APRS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/APRS/APRS.cpp b/src/protocols/APRS/APRS.cpp index 0c86de42..33559e79 100644 --- a/src/protocols/APRS/APRS.cpp +++ b/src/protocols/APRS/APRS.cpp @@ -24,7 +24,7 @@ int16_t APRSClient::begin(char sym, char* callsign, uint8_t ssid, bool alt) { table = '/'; } - if((!src) && (this->phyLayer != nullptr)) { + if(this->phyLayer != nullptr) { return(RADIOLIB_ERR_INVALID_CALLSIGN); } From 3e5c0d59c77f2c0ae35368a4050b418a676fc3d7 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:35:08 +0200 Subject: [PATCH 02/25] [MOD] Moved CS pin toggling inside SPI transaction block --- src/Module.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Module.cpp b/src/Module.cpp index 2e734034..68fbcf0c 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -166,11 +166,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 +298,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) { From bfe2c0829a21fa1c46cbcffc74b7ed5d531d9bc3 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:35:48 +0200 Subject: [PATCH 03/25] [MOD] Moved debug info to runtime --- src/Module.cpp | 4 ++++ src/RadioLib.h | 12 ------------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Module.cpp b/src/Module.cpp index 68fbcf0c..399dec46 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -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() { diff --git a/src/RadioLib.h b/src/RadioLib.h index e4ea62ad..e3b9db4f 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -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!" From 8c63f93820c23c44b362ebb30f972e9df8e721c7 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:36:12 +0200 Subject: [PATCH 04/25] [MOD] Skip SPI status parsing for single-byte commands --- src/Module.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module.cpp b/src/Module.cpp index 399dec46..80c43037 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -331,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]); } From 2f36d5901e71201196218b40becb2c9e3ddc6736 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:37:46 +0200 Subject: [PATCH 05/25] [LoRaWAN] Added LoRaWAN-specific status codes --- keywords.txt | 8 ++++++++ src/TypeDef.h | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/keywords.txt b/keywords.txt index 70935d10..15958395 100644 --- a/keywords.txt +++ b/keywords.txt @@ -390,3 +390,11 @@ 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 diff --git a/src/TypeDef.h b/src/TypeDef.h index a363f3ab..e24675b2 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -481,6 +481,43 @@ */ #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) + /*! \} */ From 5d80dd46ae746e9f04af84f5c144c03def2741e2 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:38:15 +0200 Subject: [PATCH 06/25] [SX126x] Explicitly set non-inverted IQ on startup --- src/modules/SX126x/SX126x.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index 94195b4f..358cfe79 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -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); } From bb468ad59f37100c083ebc92478519c5bfd1ec09 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:38:46 +0200 Subject: [PATCH 07/25] [SX127x] Explicitly set non-inverted IQ on startup --- src/modules/SX127x/SX127x.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index ca6633bb..3e5cccb4 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -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; From b48567722bd61a3c5bb0f0fccca84fdd4be45e84 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:40:38 +0200 Subject: [PATCH 08/25] [SX127x] Added method to get CAD result --- src/modules/SX127x/SX127x.cpp | 7 +++++++ src/modules/SX127x/SX127x.h | 12 +++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index 3e5cccb4..6009b177 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -701,6 +701,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) { diff --git a/src/modules/SX127x/SX127x.h b/src/modules/SX127x/SX127x.h index b18f6791..6544016d 100644 --- a/src/modules/SX127x/SX127x.h +++ b/src/modules/SX127x/SX127x.h @@ -834,7 +834,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 +1022,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. From 58da2a28ac5cde63cf1da68d774e793672878176 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:42:37 +0200 Subject: [PATCH 09/25] [PHY] Added channel scan methods to common interface --- src/protocols/PhysicalLayer/PhysicalLayer.cpp | 21 +++++++++++++ src/protocols/PhysicalLayer/PhysicalLayer.h | 31 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.cpp b/src/protocols/PhysicalLayer/PhysicalLayer.cpp index 5c9b697c..b244b856 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.cpp +++ b/src/protocols/PhysicalLayer/PhysicalLayer.cpp @@ -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(); diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.h b/src/protocols/PhysicalLayer/PhysicalLayer.h index ef5dd93e..ba7df212 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.h +++ b/src/protocols/PhysicalLayer/PhysicalLayer.h @@ -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. @@ -409,6 +429,17 @@ class PhysicalLayer { \brief Clears interrupt service routine to call when a packet is sent. */ 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) From ea4018d3102db7ed994ce0aa0abd76d284bf45d9 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:43:26 +0200 Subject: [PATCH 10/25] [SX127x] Added common CAD methods --- src/modules/SX127x/SX127x.cpp | 8 ++++++++ src/modules/SX127x/SX127x.h | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index 6009b177..bd0b0d46 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -472,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); diff --git a/src/modules/SX127x/SX127x.h b/src/modules/SX127x/SX127x.h index 6544016d..9ef2890f 100644 --- a/src/modules/SX127x/SX127x.h +++ b/src/modules/SX127x/SX127x.h @@ -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. From db3ac8bf1974dba764f53b4d755c768fc38b6a83 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:45:42 +0200 Subject: [PATCH 11/25] [SX126x] Implemented common CAD methods --- src/modules/SX126x/SX126x.cpp | 15 ++++++++++++++- src/modules/SX126x/SX126x.h | 36 ++++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index 358cfe79..ce34e2f4 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -507,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; @@ -729,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) { @@ -1721,7 +1734,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); diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index 9b691b9c..5b18b969 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -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. @@ -685,22 +696,29 @@ class SX126x: public PhysicalLayer { \returns \ref status_codes */ 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. From ca95135d8a8e035b4f557550a212a9603d9ce5cf Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:49:05 +0200 Subject: [PATCH 12/25] [SX126x] Skip wiping IRQ flags on CAD done --- src/modules/SX126x/SX126x.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index ce34e2f4..69abb173 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -778,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); } From 23fbd87d14a2e90d7aaf9c5d36162d0eac526349 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 18:58:11 +0200 Subject: [PATCH 13/25] [SX126x] Added Rx after CAD example --- ...nel_Activity_Detection_Interrupt_Lolin.ino | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino diff --git a/examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino b/examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino new file mode 100644 index 00000000..20b0ae92 --- /dev/null +++ b/examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino @@ -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 + +// 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); + } + + } + + } + +} From d34902ee46afab71b6ba2c2c64ad103222289d14 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 19:44:13 +0200 Subject: [PATCH 14/25] [SX127x] Added missing parentheses --- src/modules/SX127x/SX127x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index bd0b0d46..1a8f2eaf 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -710,7 +710,7 @@ int16_t SX127x::startChannelScan() { } int16_t SX127x::getChannelScanResult() { - if(this->getIRQFlags() & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED == RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) { + 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); From cd9ac9916cd95c887967bd3a17cd8ed6626dac14 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 19:49:37 +0200 Subject: [PATCH 15/25] [LoRaWAN] Added support for 1.1 and downlink (#58) --- .../LoRaWAN_End_Device/LoRaWAN_End_Device.ino | 53 +- .../LoRaWAN_End_Device_APB.ino | 47 +- keywords.txt | 1 + src/protocols/LoRaWAN/LoRaWAN.cpp | 643 ++++++++++++++---- src/protocols/LoRaWAN/LoRaWAN.h | 144 ++-- 5 files changed, 719 insertions(+), 169 deletions(-) diff --git a/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino b/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino index c1553c7a..a239ee08 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino @@ -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,43 @@ 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 + Serial.print(F("[LoRaWAN] Data:\t\t")); + Serial.println(strDown); + + // 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); } diff --git a/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino b/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino index 8f3a4a0e..a19effdc 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino @@ -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,43 @@ 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 + Serial.print(F("[LoRaWAN] Data:\t\t")); + Serial.println(strDown); + + // 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); } diff --git a/keywords.txt b/keywords.txt index 15958395..617a3983 100644 --- a/keywords.txt +++ b/keywords.txt @@ -292,6 +292,7 @@ wipe KEYWORD2 beginOTAA KEYWORD2 beginAPB KEYWORD2 uplink KEYWORD2 +downlink KEYWORD2 configureChannel KEYWORD2 ####################################### diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index a5bb9465..83fb49e3 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -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(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(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(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(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); mod->hal->setPersistentParameter(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(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], appEUI); + LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI); LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI); LoRaWANNode::hton(&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); + + // 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(&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(&micBuff[1], joinEUI); + LoRaWANNode::hton(&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 uint32_t joinNonce = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); uint32_t homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); this->devAddr = LoRaWANNode::ntoh(&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(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3); - LoRaWANNode::hton(&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(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI); + LoRaWANNode::hton(&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); + + // send the RekeyInd MAC command this->rev = 1; - RADIOLIB_DEBUG_PRINTLN("LoRaWAN 1.1 not supported (yet)"); - (void)appKey; - return(RADIOLIB_ERR_UNSUPPORTED_ENCODING); + uint8_t serverRev = 0xFF; + state = sendMacCommand(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, &this->rev, sizeof(uint8_t), &serverRev, sizeof(uint8_t)); + RADIOLIB_ASSERT(state); + + // check the supported server version + if(serverRev != this->rev) { + return(RADIOLIB_ERR_INVALID_REVISION); + } } else { // 1.0 version, just derive the keys this->rev = 0; + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3); + LoRaWANNode::hton(&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,28 @@ 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); + RADIOLIB_CHECK_RANGE(port, 0x00, 0xDF, RADIOLIB_ERR_INVALID_PORT); - // check maximum payload len as defiend in phy - // TODO implement Fopts + // check if there is a MAC command to piggyback uint8_t foptsLen = 0; + if(this->command) { + foptsLen = 1 + this->command->len; + } + + // 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 < ) { + + }*/ + // 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 +354,41 @@ 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(&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(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); // TODO implement adaptive data rate - uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCTRL_POS] = 0x00; + uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00 | foptsLen; // get frame counter from persistent storage - Module* mod = this->phyLayer->getMod(); - uint32_t fcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID); - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt + 1); - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCNT_POS], (uint16_t)fcnt); + uint32_t fcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1; + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt); + LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt); - // TODO implement FOpts - uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FPORT_POS(foptsLen)] = port; + // check if there is something in FOpts + if(this->command) { + // append MAC command + uint8_t foptsBuff[RADIOLIB_AES128_BLOCK_SIZE]; + foptsBuff[0] = this->command->cid; + for(size_t i = 1; i < this->command->len; i++) { + foptsBuff[i] = this->command->payload[i]; + } + + // encrypt it + processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, false); + } + + // 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(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&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 +414,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(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic); } else { LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF); @@ -371,17 +423,297 @@ 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->command = NULL; + 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(&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; + } + + //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(&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); + } + + // TODO cache the ADR bit? + // TODO cache and check fcnt? + uint16_t fcnt = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); + + // 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 + *len = foptsLen; + + // 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], foptsLen, this->nwkSEncKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true); + + #if !defined(RADIOLIB_STATIC_ONLY) + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_NONE); + } + + // no fopts, just payload + // TODO implement decoding piggybacked Fopts? + *len = downlinkMsgLen; + 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::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 +733,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 +773,6 @@ 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); return(false); } @@ -536,6 +828,113 @@ 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); +} + +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(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&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 T LoRaWANNode::ntoh(uint8_t* buff, size_t size) { uint8_t* buffPtr = buff; diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index d0262df1..098d4a57 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -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,22 @@ // 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) + /*! \struct LoRaWANChannelSpan_t \brief Structure to save information about LoRaWAN channels. @@ -208,6 +235,12 @@ extern const LoRaWANBand_t AS923; extern const LoRaWANBand_t KR920; extern const LoRaWANBand_t IN865; +struct LoRaWANMacCommand_t { + uint8_t cid; + size_t len; + uint8_t* payload; +}; + /*! \class LoRaWANNode \brief LoRaWAN-compatible node (class A device). @@ -239,14 +272,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 +320,60 @@ 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); + +#if !defined(RADIOLIB_GODMODE) + private: +#endif + PhysicalLayer* phyLayer = NULL; + const LoRaWANBand_t* band = NULL; + + LoRaWANMacCommand_t* command = NULL; + + // 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 }; + + // 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 +382,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 +389,15 @@ 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); + + // 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 static T ntoh(uint8_t* buff, size_t size = 0); From cbd15192bc7538e0ecd0505a64866975b9387e16 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 19:52:41 +0200 Subject: [PATCH 16/25] [SX126x] Fixed example path --- ...pt_Lolin.ino => SX126x_Channel_Activity_Detection_Receive.ino} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/SX126x/SX126x_Channel_Activity_Detection_Receive/{SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino => SX126x_Channel_Activity_Detection_Receive.ino} (100%) diff --git a/examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino b/examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Receive.ino similarity index 100% rename from examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Interrupt_Lolin.ino rename to examples/SX126x/SX126x_Channel_Activity_Detection_Receive/SX126x_Channel_Activity_Detection_Receive.ino From 1a31efbf7464b4d2886e0c7d30f907507200be52 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 19:57:22 +0200 Subject: [PATCH 17/25] [LoRaWAN] Drop examples on Uno --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88b3af3f..cdc256b8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 From 45c376bde6370175796c0434dabb6e65091ec2f3 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 19:58:00 +0200 Subject: [PATCH 18/25] [LoRaWAN] Fixed variable range warning --- src/protocols/LoRaWAN/LoRaWAN.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 83fb49e3..b31abbb2 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -325,7 +325,9 @@ 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, 0x00, 0xDF, RADIOLIB_ERR_INVALID_PORT); + if(port > 0xDF) { + return(RADIOLIB_ERR_INVALID_PORT); + } // check if there is a MAC command to piggyback uint8_t foptsLen = 0; From 1ed22717d748db981c3c7d002c6fd9f9226137a2 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 12 Aug 2023 21:48:31 +0200 Subject: [PATCH 19/25] [LoRaWAN] Added check to not uplink in Rx slots --- src/TypeDef.h | 5 +++++ src/protocols/LoRaWAN/LoRaWAN.cpp | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/TypeDef.h b/src/TypeDef.h index e24675b2..96058827 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -518,6 +518,11 @@ */ #define RADIOLIB_ERR_INVALID_CID (-1107) +/*! + \brief User requested to start uplink while still inside RX window. +*/ +#define RADIOLIB_ERR_UPLINK_UNAVAILABLE (-1108) + /*! \} */ diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index b31abbb2..c2cd77d7 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -342,9 +342,10 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { // check if sufficient time has elapsed since the last uplink Module* mod = this->phyLayer->getMod(); - /*if(mod->hal->millis() - this->rxDelayStart < ) { - - }*/ + 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 From 0a72d98750326a66dd509cb2f93bbac4b8ab4219 Mon Sep 17 00:00:00 2001 From: jgromes Date: Mon, 14 Aug 2023 21:38:31 +0200 Subject: [PATCH 20/25] [LoRaWAN] Fixed MIC calculation for downlink packets --- src/protocols/LoRaWAN/LoRaWAN.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index c2cd77d7..0c36377d 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -625,6 +625,12 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { 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(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt); + //Module::hexdump(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); if(state != RADIOLIB_ERR_NONE) { @@ -646,10 +652,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } - // TODO cache the ADR bit? - // TODO cache and check fcnt? - uint16_t fcnt = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); - // check fopts len uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; if(foptsLen > 0) { From 3e8636b1f2002b3d9f129482f0d6ad49220129e0 Mon Sep 17 00:00:00 2001 From: jgromes Date: Fri, 18 Aug 2023 20:21:53 +0200 Subject: [PATCH 21/25] [APRS] Removed redundant condition (#810) --- src/protocols/APRS/APRS.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/protocols/APRS/APRS.cpp b/src/protocols/APRS/APRS.cpp index 33559e79..780dddf4 100644 --- a/src/protocols/APRS/APRS.cpp +++ b/src/protocols/APRS/APRS.cpp @@ -24,13 +24,10 @@ int16_t APRSClient::begin(char sym, char* callsign, uint8_t ssid, bool alt) { table = '/'; } - if(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; From b2b176f1c35c0409333a97202969571ce6bd4ffd Mon Sep 17 00:00:00 2001 From: jgromes Date: Sun, 20 Aug 2023 19:16:07 +0200 Subject: [PATCH 22/25] [LoRaWAN] Added MIC mismatch debug message --- src/protocols/LoRaWAN/LoRaWAN.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 0c36377d..75266256 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -778,6 +778,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 %08x, got %08x", micCalculated, micReceived); return(false); } From 73382c293347480509bce6276f66b6028f74fc42 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sun, 20 Aug 2023 19:16:38 +0200 Subject: [PATCH 23/25] [LoRaWAN] Fixed output power configuration (#814) --- src/protocols/LoRaWAN/LoRaWAN.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 75266256..568abbca 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -806,8 +806,14 @@ int16_t LoRaWANNode::setPhyProperties() { state = this->configureChannel(channelId, this->band->defaultChannels[0].joinRequestDataRate); } 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 }; From 16f0ba7cce84d40ed82dc50c55e14633ee530a60 Mon Sep 17 00:00:00 2001 From: jgromes Date: Wed, 23 Aug 2023 19:22:23 +0200 Subject: [PATCH 24/25] [LoRaWAN] Implemented MAC command support --- .../LoRaWAN_End_Device/LoRaWAN_End_Device.ino | 8 +- .../LoRaWAN_End_Device_APB.ino | 8 +- keywords.txt | 2 + src/TypeDef.h | 10 ++ src/protocols/LoRaWAN/LoRaWAN.cpp | 169 +++++++++++++++--- src/protocols/LoRaWAN/LoRaWAN.h | 44 ++++- 6 files changed, 206 insertions(+), 35 deletions(-) diff --git a/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino b/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino index a239ee08..a28cad77 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device/LoRaWAN_End_Device.ino @@ -133,9 +133,13 @@ void loop() { if(state == RADIOLIB_ERR_NONE) { Serial.println(F("success!")); - // print data of the packet + // print data of the packet (if there are any) Serial.print(F("[LoRaWAN] Data:\t\t")); - Serial.println(strDown); + if(strDown.length() > 0) { + Serial.println(strDown); + } else { + Serial.println(F("")); + } // print RSSI (Received Signal Strength Indicator) Serial.print(F("[LoRaWAN] RSSI:\t\t")); diff --git a/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino b/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino index a19effdc..2ecf6569 100644 --- a/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino +++ b/examples/LoRaWAN/LoRaWAN_End_Device_APB/LoRaWAN_End_Device_APB.ino @@ -128,9 +128,13 @@ void loop() { if(state == RADIOLIB_ERR_NONE) { Serial.println(F("success!")); - // print data of the packet + // print data of the packet (if there are any) Serial.print(F("[LoRaWAN] Data:\t\t")); - Serial.println(strDown); + if(strDown.length() > 0) { + Serial.println(strDown); + } else { + Serial.println(F("")); + } // print RSSI (Received Signal Strength Indicator) Serial.print(F("[LoRaWAN] RSSI:\t\t")); diff --git a/keywords.txt b/keywords.txt index 617a3983..36bfb988 100644 --- a/keywords.txt +++ b/keywords.txt @@ -399,3 +399,5 @@ 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 diff --git a/src/TypeDef.h b/src/TypeDef.h index 96058827..11b3f7f6 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -523,6 +523,16 @@ */ #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) + /*! \} */ diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 568abbca..63d4d154 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -250,16 +250,16 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey); //Module::hexdump(this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); - // send the RekeyInd MAC command + // enqueue the RekeyInd MAC command to be sent in the next uplink this->rev = 1; - uint8_t serverRev = 0xFF; - state = sendMacCommand(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, &this->rev, sizeof(uint8_t), &serverRev, sizeof(uint8_t)); + 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); - - // check the supported server version - if(serverRev != this->rev) { - return(RADIOLIB_ERR_INVALID_REVISION); - } } else { // 1.0 version, just derive the keys @@ -329,10 +329,11 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { return(RADIOLIB_ERR_INVALID_PORT); } - // check if there is a MAC command to piggyback - uint8_t foptsLen = 0; - if(this->command) { - foptsLen = 1 + this->command->len; + // 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 @@ -361,24 +362,30 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); // TODO implement adaptive data rate - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00 | foptsLen; + // foptslen will be added later + uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; // get frame counter from persistent storage uint32_t fcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1; mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt); LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt); - // check if there is something in FOpts - if(this->command) { - // append MAC command + // 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] = this->command->cid; - for(size_t i = 1; i < this->command->len; i++) { - foptsBuff[i] = this->command->payload[i]; + 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_FRAME_PAYLOAD_POS(0)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, false); + processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); } // set the port @@ -435,7 +442,6 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { RADIOLIB_ASSERT(state); // set the timestamp so that we can measure when to start receiving - this->command = NULL; this->rxDelayStart = txStart + timeOnAir; return(RADIOLIB_ERR_NONE); } @@ -656,21 +662,46 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* 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 - *len = foptsLen; + 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], foptsLen, this->nwkSEncKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true); + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true); - #if !defined(RADIOLIB_STATIC_ONLY) - delete[] downlinkMsg; - #endif + //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 = 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); } - // no fopts, just payload - // TODO implement decoding piggybacked Fopts? - *len = downlinkMsgLen; + // 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) @@ -680,6 +711,10 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { 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; @@ -905,6 +940,82 @@ int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloa 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; diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 098d4a57..73ebbf35 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -160,6 +160,9 @@ #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. @@ -235,10 +238,27 @@ 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; - uint8_t* payload; + + /*! \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; }; /*! @@ -337,13 +357,21 @@ class LoRaWANNode { */ 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; - LoRaWANMacCommand_t* command = 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) @@ -371,6 +399,9 @@ class LoRaWANNode { // 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); @@ -395,6 +426,15 @@ class LoRaWANNode { // 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); From 2555857013a676f28798e550921ac6f0305b0e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20B=C3=B6rjesson?= Date: Wed, 23 Aug 2023 20:16:57 +0200 Subject: [PATCH 25/25] Fix "narrowing conversion" error on ESP-IDF The 1 without a cast caused the result to be an integer. --- src/protocols/LoRaWAN/LoRaWAN.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 63d4d154..8445c465 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -676,7 +676,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { while(remLen > 0) { LoRaWANMacCommand_t cmd = { .cid = *foptsPtr, - .len = remLen - 1, + .len = (uint8_t)(remLen - 1), .payload = { 0 }, }; memcpy(cmd.payload, foptsPtr + 1, cmd.len);