From 1b2b8bd67b9c22544be0a6df105c50b01648a7a6 Mon Sep 17 00:00:00 2001 From: StevenCellist <47155822+StevenCellist@users.noreply.github.com> Date: Wed, 1 May 2024 13:35:22 +0200 Subject: [PATCH] [LoRaWAN] Improve PHY behaviour, update beginABP, bugfixes (#1080) * [LoRaWAN] Add getter for ToA, prevent MAC queue overflow * [LoRaWAN] Permute arguments to beginABP * Implement & split off checkOutputPower * [LoRaWAN] Configure physical layer on each up/downlink * [LoRaWAN] Remove unnecessary dynamic array * [LoRaWAN] Improve downlink handling * Resolve return-warnings in checkOutputPower() * [LoRaWAN] Improve buffer definition * [LoRaWAN] Prevent requesting repeated MAC commands * Update keywords.txt * [CC1101] Resolve unused variable warning * [CC1101] Update checkOutputPower * [SX1278] Fix variable assignment * Update keywords.txt * [CC1101] Added checkOutputPower override for PHY compatibility * [LR11x0] Added checkOutputPower override for PHY compatibility * [SX127x] Added checkOutputPower override for PHY compatibility --------- Co-authored-by: jgromes --- .github/workflows/main.yml | 2 +- examples/LoRaWAN/LoRaWAN_ABP/configABP.h | 6 +- keywords.txt | 3 +- src/modules/CC1101/CC1101.cpp | 110 ++++++--- src/modules/CC1101/CC1101.h | 18 ++ src/modules/LR11x0/LR11x0.cpp | 36 ++- src/modules/LR11x0/LR11x0.h | 19 ++ src/modules/SX126x/SX1261.cpp | 14 +- src/modules/SX126x/SX1261.h | 8 + src/modules/SX126x/SX1262.cpp | 14 +- src/modules/SX126x/SX1262.h | 8 + src/modules/SX126x/SX1268.cpp | 14 +- src/modules/SX126x/SX1268.h | 8 + src/modules/SX127x/SX1272.cpp | 31 ++- src/modules/SX127x/SX1272.h | 18 ++ src/modules/SX127x/SX1278.cpp | 43 +++- src/modules/SX127x/SX1278.h | 18 ++ src/modules/SX128x/SX128x.cpp | 13 +- src/modules/SX128x/SX128x.h | 8 + src/protocols/LoRaWAN/LoRaWAN.cpp | 223 +++++++++-------- src/protocols/LoRaWAN/LoRaWAN.h | 224 +++++++++--------- src/protocols/PhysicalLayer/PhysicalLayer.cpp | 6 + src/protocols/PhysicalLayer/PhysicalLayer.h | 8 + 23 files changed, 561 insertions(+), 291 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e7d4cbe3f..48acb19a9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -170,7 +170,7 @@ jobs: else # apply special flags for LoRaWAN if [[ ${example} =~ "LoRaWAN" ]]; then - flags="-DRADIOLIB_LORAWAN_DEV_ADDR=0 -DRADIOLIB_LORAWAN_NWKS_KEY=0 -DRADIOLIB_LORAWAN_SNWKSINT_KEY=0 -DRADIOLIB_LORAWAN_NWKSENC_KEY=0 -DRADIOLIB_LORAWAN_APPS_KEY=0 -DRADIOLIB_LORAWAN_APP_KEY=0 -DRADIOLIB_LORAWAN_NWK_KEY=0 -DRADIOLIB_LORAWAN_DEV_EUI=0 -DARDUINO_TTGO_LORA32_V1" + flags="-DRADIOLIB_LORAWAN_DEV_ADDR=0 -DRADIOLIB_LORAWAN_FNWKSINT_KEY=0 -DRADIOLIB_LORAWAN_SNWKSINT_KEY=0 -DRADIOLIB_LORAWAN_NWKSENC_KEY=0 -DRADIOLIB_LORAWAN_APPS_KEY=0 -DRADIOLIB_LORAWAN_APP_KEY=0 -DRADIOLIB_LORAWAN_NWK_KEY=0 -DRADIOLIB_LORAWAN_DEV_EUI=0 -DARDUINO_TTGO_LORA32_V1" fi # build sketch diff --git a/examples/LoRaWAN/LoRaWAN_ABP/configABP.h b/examples/LoRaWAN/LoRaWAN_ABP/configABP.h index 52e39d8e6..b9de72e7d 100644 --- a/examples/LoRaWAN/LoRaWAN_ABP/configABP.h +++ b/examples/LoRaWAN/LoRaWAN_ABP/configABP.h @@ -12,8 +12,8 @@ const uint32_t uplinkIntervalSeconds = 5UL * 60UL; // minutes x seconds #define RADIOLIB_LORAWAN_DEV_ADDR 0x------ #endif -#ifndef RADIOLIB_LORAWAN_NWKS_KEY // Replace with your NwkS Key -#define RADIOLIB_LORAWAN_NWKS_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- +#ifndef RADIOLIB_LORAWAN_FNWKSINT_KEY // Replace with your FNwkSInt Key +#define RADIOLIB_LORAWAN_FNWKSINT_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- #endif #ifndef RADIOLIB_LORAWAN_SNWKSINT_KEY // Replace with your SNwkSInt Key #define RADIOLIB_LORAWAN_SNWKSINT_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- @@ -118,7 +118,7 @@ const uint8_t subBand = 0; // For US915, change this to 2, otherwise leave on 0 // copy over the keys in to the something that will not compile if incorrectly formatted uint32_t devAddr = RADIOLIB_LORAWAN_DEV_ADDR; -uint8_t NwkSKey[] = { RADIOLIB_LORAWAN_NWKS_KEY }; +uint8_t NwkSKey[] = { RADIOLIB_LORAWAN_FNWKSINT_KEY }; uint8_t SNwkSIntKey[] = { RADIOLIB_LORAWAN_SNWKSINT_KEY }; // Previously sNwkSIntKey uint8_t NwkSEncKey[] = { RADIOLIB_LORAWAN_NWKSENC_KEY }; // Previously fNwkSIntKey uint8_t AppSKey[] = { RADIOLIB_LORAWAN_APPS_KEY }; diff --git a/keywords.txt b/keywords.txt index d160e7820..5fbbcbb37 100644 --- a/keywords.txt +++ b/keywords.txt @@ -127,6 +127,7 @@ setCodingRate KEYWORD2 setFrequency KEYWORD2 setSyncWord KEYWORD2 setOutputPower KEYWORD2 +checkOutputPower KEYWORD2 setCurrentLimit KEYWORD2 setPreambleLength KEYWORD2 setGain KEYWORD2 @@ -328,10 +329,10 @@ timeUntilUplink KEYWORD2 setDwellTime KEYWORD2 maxPayloadDwellTime KEYWORD2 setTxPower KEYWORD2 -setCSMA KEYWORD2 getMacLinkCheckAns KEYWORD2 getMacDeviceTimeAns KEYWORD2 getDevAddr KEYWORD2 +getLastToA KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/src/modules/CC1101/CC1101.cpp b/src/modules/CC1101/CC1101.cpp index 5d8f963b0..1da68a02d 100644 --- a/src/modules/CC1101/CC1101.cpp +++ b/src/modules/CC1101/CC1101.cpp @@ -560,6 +560,62 @@ int16_t CC1101::getFrequencyDeviation(float *freqDev) { } int16_t CC1101::setOutputPower(int8_t pwr) { + // check if power value is configurable + uint8_t powerRaw = 0; + int16_t state = checkOutputPower(pwr, NULL, &powerRaw); + RADIOLIB_ASSERT(state); + + // store the value + this->power = pwr; + + if(this->modulation == RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK){ + // Amplitude modulation: + // PA_TABLE[0] is the power to be used when transmitting a 0 (no power) + // PA_TABLE[1] is the power to be used when transmitting a 1 (full power) + + uint8_t paValues[2] = {0x00, powerRaw}; + SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_PATABLE, paValues, 2); + return(RADIOLIB_ERR_NONE); + + } else { + // Freq modulation: + // PA_TABLE[0] is the power to be used when transmitting. + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PATABLE, powerRaw)); + } +} + +int16_t CC1101::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, NULL)); +} + +int16_t CC1101::checkOutputPower(int8_t power, int8_t* clipped, uint8_t* raw) { + constexpr int8_t allowedPwrs[8] = { -30, -20, -15, -10, 0, 5, 7, 10 }; + + if(clipped) { + if(power <= -30) { + *clipped = -30; + } else if(power >= 10) { + *clipped = 10; + } else { + for(int i = 0; i < 8; i++) { + if(allowedPwrs[i] > power) { + break; + } + *clipped = allowedPwrs[i]; + } + } + } + + // if just a check occurs (and not requesting the raw power value), return now + if(!raw) { + for(int i = 0; i < 8; i++) { + if(allowedPwrs[i] == power) { + return(RADIOLIB_ERR_NONE); + } + } + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + // round to the known frequency settings uint8_t f; if(this->frequency < 374.0) { @@ -586,53 +642,35 @@ int16_t CC1101::setOutputPower(int8_t pwr) { {0xCB, 0xC8, 0xCB, 0xC7}, {0xC2, 0xC0, 0xC2, 0xC0}}; - uint8_t powerRaw; - switch(pwr) { - case -30: - powerRaw = paTable[0][f]; + switch(power) { + case allowedPwrs[0]: // -30 + *raw = paTable[0][f]; break; - case -20: - powerRaw = paTable[1][f]; + case allowedPwrs[1]: // -20 + *raw = paTable[1][f]; break; - case -15: - powerRaw = paTable[2][f]; + case allowedPwrs[2]: // -15 + *raw = paTable[2][f]; break; - case -10: - powerRaw = paTable[3][f]; + case allowedPwrs[3]: // -10 + *raw = paTable[3][f]; break; - case 0: - powerRaw = paTable[4][f]; + case allowedPwrs[4]: // 0 + *raw = paTable[4][f]; break; - case 5: - powerRaw = paTable[5][f]; + case allowedPwrs[5]: // 5 + *raw = paTable[5][f]; break; - case 7: - powerRaw = paTable[6][f]; + case allowedPwrs[6]: // 7 + *raw = paTable[6][f]; break; - case 10: - powerRaw = paTable[7][f]; + case allowedPwrs[7]: // 10 + *raw = paTable[7][f]; break; default: return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); } - - // store the value - this->power = pwr; - - if(this->modulation == RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK){ - // Amplitude modulation: - // PA_TABLE[0] is the power to be used when transmitting a 0 (no power) - // PA_TABLE[1] is the power to be used when transmitting a 1 (full power) - - uint8_t paValues[2] = {0x00, powerRaw}; - SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_PATABLE, paValues, 2); - return(RADIOLIB_ERR_NONE); - - } else { - // Freq modulation: - // PA_TABLE[0] is the power to be used when transmitting. - return(SPIsetRegValue(RADIOLIB_CC1101_REG_PATABLE, powerRaw)); - } + return(RADIOLIB_ERR_NONE); } int16_t CC1101::setSyncWord(uint8_t* syncWord, uint8_t len, uint8_t maxErrBits, bool requireCarrierSense) { diff --git a/src/modules/CC1101/CC1101.h b/src/modules/CC1101/CC1101.h index 3619d2184..237e1dcf2 100644 --- a/src/modules/CC1101/CC1101.h +++ b/src/modules/CC1101/CC1101.h @@ -774,6 +774,24 @@ class CC1101: public PhysicalLayer { */ int16_t setOutputPower(int8_t pwr); + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param raw Raw internal value. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, uint8_t* raw); + /*! \brief Sets 16-bit sync word as a two byte value. \param syncH MSB of the sync word. diff --git a/src/modules/LR11x0/LR11x0.cpp b/src/modules/LR11x0/LR11x0.cpp index b44c1bb07..c9244bb89 100644 --- a/src/modules/LR11x0/LR11x0.cpp +++ b/src/modules/LR11x0/LR11x0.cpp @@ -593,22 +593,17 @@ int16_t LR11x0::setOutputPower(int8_t power) { } int16_t LR11x0::setOutputPower(int8_t power, bool forceHighPower) { + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL, forceHighPower); + RADIOLIB_ASSERT(state); + // determine whether to use HP or LP PA and check range accordingly bool useHp = forceHighPower || (power > 14); - if(useHp) { - RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); - useHp = true; - - } else { - RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); - useHp = false; - } - // TODO how and when to configure OCP? // update PA config - always use VBAT for high-power PA - int16_t state = setPaConfig((uint8_t)useHp, (uint8_t)useHp, 0x04, 0x07); + state = setPaConfig((uint8_t)useHp, (uint8_t)useHp, 0x04, 0x07); RADIOLIB_ASSERT(state); // set output power @@ -616,6 +611,27 @@ int16_t LR11x0::setOutputPower(int8_t power, bool forceHighPower) { return(state); } +int16_t LR11x0::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t LR11x0::checkOutputPower(int8_t power, int8_t* clipped, bool forceHighPower) { + if(forceHighPower || (power > 14)) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } else { + if(clipped) { + *clipped = RADIOLIB_MAX(-17, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } + return(RADIOLIB_ERR_NONE); +} + int16_t LR11x0::setBandwidth(float bw) { // check active modem uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; diff --git a/src/modules/LR11x0/LR11x0.h b/src/modules/LR11x0/LR11x0.h index cdee10e53..3a100e637 100644 --- a/src/modules/LR11x0/LR11x0.h +++ b/src/modules/LR11x0/LR11x0.h @@ -802,6 +802,25 @@ class LR11x0: public PhysicalLayer { */ int16_t setOutputPower(int8_t power, bool forceHighPower); + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, PA will be determined automatically. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param forceHighPower Force using the high-power PA. If set to false, PA will be determined automatically + based on configured output power, preferring the low-power PA. If set to true, only high-power PA will be used. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool forceHighPower); + /*! \brief Sets LoRa bandwidth. Allowed values are 62.5, 125.0, 250.0 and 500.0 kHz. \param bw LoRa bandwidth to be set in kHz. diff --git a/src/modules/SX126x/SX1261.cpp b/src/modules/SX126x/SX1261.cpp index cabe2b579..d6a90c342 100644 --- a/src/modules/SX126x/SX1261.cpp +++ b/src/modules/SX126x/SX1261.cpp @@ -6,11 +6,13 @@ SX1261::SX1261(Module* mod): SX1262(mod) { } int16_t SX1261::setOutputPower(int8_t power) { - RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); // get current OCP configuration uint8_t ocp = 0; - int16_t state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); RADIOLIB_ASSERT(state); // set PA config @@ -25,4 +27,12 @@ int16_t SX1261::setOutputPower(int8_t power) { return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); } +int16_t SX1261::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-17, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + #endif diff --git a/src/modules/SX126x/SX1261.h b/src/modules/SX126x/SX1261.h index 01375d558..6f4011599 100644 --- a/src/modules/SX126x/SX1261.h +++ b/src/modules/SX126x/SX1261.h @@ -34,6 +34,14 @@ class SX1261 : public SX1262 { */ int16_t setOutputPower(int8_t power); + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped); + #if !RADIOLIB_GODMODE private: #endif diff --git a/src/modules/SX126x/SX1262.cpp b/src/modules/SX126x/SX1262.cpp index 036cba0ad..232ad6380 100644 --- a/src/modules/SX126x/SX1262.cpp +++ b/src/modules/SX126x/SX1262.cpp @@ -98,11 +98,13 @@ int16_t SX1262::setFrequency(float freq, bool calibrate) { } int16_t SX1262::setOutputPower(int8_t power) { - RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); // get current OCP configuration uint8_t ocp = 0; - int16_t state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); RADIOLIB_ASSERT(state); // set PA config @@ -117,4 +119,12 @@ int16_t SX1262::setOutputPower(int8_t power) { return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); } +int16_t SX1262::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + #endif diff --git a/src/modules/SX126x/SX1262.h b/src/modules/SX126x/SX1262.h index f47b1a813..ebe8652a5 100644 --- a/src/modules/SX126x/SX1262.h +++ b/src/modules/SX126x/SX1262.h @@ -87,6 +87,14 @@ class SX1262: public SX126x { */ virtual int16_t setOutputPower(int8_t power); + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped); + #if !RADIOLIB_GODMODE private: #endif diff --git a/src/modules/SX126x/SX1268.cpp b/src/modules/SX126x/SX1268.cpp index 1b63c4d06..c50baaf16 100644 --- a/src/modules/SX126x/SX1268.cpp +++ b/src/modules/SX126x/SX1268.cpp @@ -93,11 +93,13 @@ int16_t SX1268::setFrequency(float freq, bool calibrate) { } int16_t SX1268::setOutputPower(int8_t power) { - RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); // get current OCP configuration uint8_t ocp = 0; - int16_t state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); RADIOLIB_ASSERT(state); // set PA config @@ -112,4 +114,12 @@ int16_t SX1268::setOutputPower(int8_t power) { return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); } +int16_t SX1268::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + #endif diff --git a/src/modules/SX126x/SX1268.h b/src/modules/SX126x/SX1268.h index 04edba39f..d6f0415e7 100644 --- a/src/modules/SX126x/SX1268.h +++ b/src/modules/SX126x/SX1268.h @@ -85,6 +85,14 @@ class SX1268: public SX126x { */ int16_t setOutputPower(int8_t power); + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped); + #if !RADIOLIB_GODMODE private: #endif diff --git a/src/modules/SX127x/SX1272.cpp b/src/modules/SX127x/SX1272.cpp index 003a1cc20..6e8b6c0bf 100644 --- a/src/modules/SX127x/SX1272.cpp +++ b/src/modules/SX127x/SX1272.cpp @@ -280,15 +280,12 @@ int16_t SX1272::setOutputPower(int8_t power) { } int16_t SX1272::setOutputPower(int8_t power, bool useRfo) { - // check allowed power range - if(useRfo) { - RADIOLIB_CHECK_RANGE(power, -1, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } else { - RADIOLIB_CHECK_RANGE(power, 2, 20, RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL, useRfo); + RADIOLIB_ASSERT(state); // set mode to standby - int16_t state = SX127x::standby(); + state = SX127x::standby(); Module* mod = this->getMod(); if(useRfo) { @@ -317,6 +314,26 @@ int16_t SX1272::setOutputPower(int8_t power, bool useRfo) { return(state); } +int16_t SX1272::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t SX1272::checkOutputPower(int8_t power, int8_t* clipped, bool useRfo) { + // check allowed power range + if(useRfo) { + if(clipped) { + *clipped = RADIOLIB_MAX(-1, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -1, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } else { + if(clipped) { + *clipped = RADIOLIB_MAX(2, RADIOLIB_MIN(20, power)); + } + RADIOLIB_CHECK_RANGE(power, 2, 20, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + return(RADIOLIB_ERR_NONE); +} + int16_t SX1272::setGain(uint8_t gain) { // check allowed range if(gain > 6) { diff --git a/src/modules/SX127x/SX1272.h b/src/modules/SX127x/SX1272.h index 37e468ee0..0f9a17171 100644 --- a/src/modules/SX127x/SX1272.h +++ b/src/modules/SX127x/SX1272.h @@ -206,6 +206,24 @@ class SX1272: public SX127x { */ int16_t setOutputPower(int8_t power, bool useRfo); + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, assumes PA_BOOST pin. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param useRfo Whether to use the RFO (true) or the PA_BOOST (false) pin for the RF output. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool useRfo); + /*! \brief Sets gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. Set to 0 to enable automatic gain control (recommended). diff --git a/src/modules/SX127x/SX1278.cpp b/src/modules/SX127x/SX1278.cpp index 22c243038..78251d245 100644 --- a/src/modules/SX127x/SX1278.cpp +++ b/src/modules/SX127x/SX1278.cpp @@ -294,19 +294,12 @@ int16_t SX1278::setOutputPower(int8_t power) { } int16_t SX1278::setOutputPower(int8_t power, bool useRfo) { - // check allowed power range - if(useRfo) { - // RFO output - RADIOLIB_CHECK_RANGE(power, -3, 15, RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } else { - // PA_BOOST output, check high-power operation - if(power != 20) { - RADIOLIB_CHECK_RANGE(power, 2, 17, RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } - } + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL, useRfo); + RADIOLIB_ASSERT(state); // set mode to standby - int16_t state = SX127x::standby(); + state = SX127x::standby(); Module* mod = this->getMod(); if(useRfo) { @@ -342,6 +335,34 @@ int16_t SX1278::setOutputPower(int8_t power, bool useRfo) { return(state); } +int16_t SX1278::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t SX1278::checkOutputPower(int8_t power, int8_t* clipped, bool useRfo) { + // check allowed power range + if(useRfo) { + // RFO output + if(clipped) { + *clipped = RADIOLIB_MAX(-3, RADIOLIB_MIN(15, power)); + } + RADIOLIB_CHECK_RANGE(power, -3, 15, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } else { + // PA_BOOST output, check high-power operation + if(clipped) { + if(power != 20) { + *clipped = RADIOLIB_MAX(2, RADIOLIB_MIN(17, power)); + } else { + *clipped = 20; + } + } + if(power != 20) { + RADIOLIB_CHECK_RANGE(power, 2, 17, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + } + return(RADIOLIB_ERR_NONE); +} + int16_t SX1278::setGain(uint8_t gain) { // check allowed range if(gain > 6) { diff --git a/src/modules/SX127x/SX1278.h b/src/modules/SX127x/SX1278.h index 371b16601..a54623a9a 100644 --- a/src/modules/SX127x/SX1278.h +++ b/src/modules/SX127x/SX1278.h @@ -218,6 +218,24 @@ class SX1278: public SX127x { */ int16_t setOutputPower(int8_t power, bool useRfo); + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, assumes PA_BOOST pin. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param useRfo Whether to use the RFO (true) or the PA_BOOST (false) pin for the RF output. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool useRfo); + /*! \brief Sets gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. Set to 0 to enable automatic gain control (recommended). diff --git a/src/modules/SX128x/SX128x.cpp b/src/modules/SX128x/SX128x.cpp index 4d79c4ef3..00f5b5e28 100644 --- a/src/modules/SX128x/SX128x.cpp +++ b/src/modules/SX128x/SX128x.cpp @@ -765,11 +765,22 @@ int16_t SX128x::setCodingRate(uint8_t cr, bool longInterleaving) { } int16_t SX128x::setOutputPower(int8_t pwr) { - RADIOLIB_CHECK_RANGE(pwr, -18, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); + this->power = pwr + 18; return(setTxParams(this->power)); } +int16_t SX128x::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-18, RADIOLIB_MIN(13, power)); + } + RADIOLIB_CHECK_RANGE(power, -18, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + int16_t SX128x::setPreambleLength(uint32_t preambleLength) { uint8_t modem = getPacketType(); if((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING)) { diff --git a/src/modules/SX128x/SX128x.h b/src/modules/SX128x/SX128x.h index 8c8d30cd6..80618d71f 100644 --- a/src/modules/SX128x/SX128x.h +++ b/src/modules/SX128x/SX128x.h @@ -608,6 +608,14 @@ class SX128x: public PhysicalLayer { */ int16_t setOutputPower(int8_t pwr); + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped); + /*! \brief Sets preamble length for currently active modem. Allowed values range from 1 to 65535. \param preambleLength Preamble length to be set in symbols (LoRa) or bits (FSK/BLE/FLRC). diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 3922f1456..03d2a7f07 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -241,9 +241,6 @@ int16_t LoRaWANNode::restore(uint16_t checkSum, uint16_t lwMode, uint8_t lwClass // copy uplink MAC command queue back in place memcpy(&this->commandsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], sizeof(LoRaWANMacCommandQueue_t)); - state = this->setPhyProperties(); - RADIOLIB_ASSERT(state); - // full session is restored, so set joined flag to whichever mode is restored this->activeMode = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE]); @@ -498,16 +495,12 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // setup all MAC properties to default values this->beginCommon(joinDr); - // set the physical layer configuration - state = this->setPhyProperties(); - RADIOLIB_ASSERT(state); - // select a random pair of Tx/Rx channels state = this->selectChannels(); RADIOLIB_ASSERT(state); - // configure for uplink with default configuration - state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); + // set the physical layer configuration for uplink + state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); RADIOLIB_ASSERT(state); // copy devNonce currently in use @@ -732,7 +725,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, bool force, uint8_t initialDr) { +int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey, bool force, uint8_t initialDr) { // if not forced and already joined, don't do anything if(!force && this->isJoined()) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("beginABP(): Did not rejoin: session already active"); @@ -744,7 +737,7 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, // check if we actually need to restart from a clean session uint16_t checkSum = 0; checkSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&addr), 4); - checkSum ^= LoRaWANNode::checkSum16(nwkSKey, 16); + checkSum ^= LoRaWANNode::checkSum16(nwkSEncKey, 16); checkSum ^= LoRaWANNode::checkSum16(appSKey, 16); if(fNwkSIntKey) { checkSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, 16); } if(sNwkSIntKey) { checkSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, 16); } @@ -763,12 +756,12 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, this->devAddr = addr; memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->nwkSEncKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); if(fNwkSIntKey) { this->rev = 1; memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } else { - memcpy(this->fNwkSIntKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); } if(sNwkSIntKey) { memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); @@ -784,10 +777,6 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, // setup all MAC properties to default values this->beginCommon(initialDr); - // set the physical layer configuration - state = this->setPhyProperties(); - RADIOLIB_ASSERT(state); - // reset all frame counters this->fcntUp = 0; this->aFcntDown = 0; @@ -970,9 +959,9 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf } } - // configure for uplink + // set the physical layer configuration for uplink this->selectChannels(); - state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); + state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); RADIOLIB_ASSERT(state); // if dwell time is imposed, calculated expected time on air and cancel if exceeds @@ -1019,12 +1008,8 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf // check if we have some MAC commands to append if(foptsLen > 0) { - #if RADIOLIB_STATIC_ONLY // assume maximum possible buffer size uint8_t foptsBuff[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN]; - #else - uint8_t* foptsBuff = new uint8_t[foptsLen]; - #endif uint8_t* foptsPtr = foptsBuff; // append all MAC replies into fopts buffer @@ -1052,9 +1037,6 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf // encrypt it processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fcntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); - #if !RADIOLIB_STATIC_ONLY - delete[] foptsBuff; - #endif } // set the port @@ -1146,30 +1128,28 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf int16_t LoRaWANNode::downlinkCommon() { Module* mod = this->phyLayer->getMod(); - const RadioLibTime_t scanGuard = 10; + + // according to the spec, the Rx window must be at least enough time to effectively detect a preamble + // but we pad it a bit on both sides (start and end) to make sure it is wide enough + const RadioLibTime_t scanGuard = 10; // Rx window padding in milliseconds // check if there are any upcoming Rx windows // if the Rx1 window has already started, you're too late, because most downlinks happen in Rx1 - if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[0] - scanGuard)) { + RadioLibTime_t now = mod->hal->millis(); // Fix the current timestamp to prevent negative delays + if(now > this->rxDelayStart + this->rxDelays[0] - scanGuard) { // if between start of Rx1 and end of Rx2, wait until Rx2 closes - if(mod->hal->millis() - this->rxDelayStart < this->rxDelays[1]) { - mod->hal->delay(this->rxDelays[1] + this->rxDelayStart - mod->hal->millis()); + if(now < this->rxDelayStart + this->rxDelays[1]) { + mod->hal->delay(this->rxDelays[1] + this->rxDelayStart - now); } // update the end timestamp in case user got stuck between uplink and downlink this->rxDelayEnd = mod->hal->millis(); return(RADIOLIB_ERR_NO_RX_WINDOW); } - // configure for downlink - int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); + // set the physical layer configuration for downlink + int16_t state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); RADIOLIB_ASSERT(state); - // downlink messages are sent with inverted IQ - if(!this->FSK) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } - // create the masks that are required for receiving downlinks uint16_t irqFlags = 0x0000; uint16_t irqMask = 0x0000; @@ -1182,14 +1162,16 @@ int16_t LoRaWANNode::downlinkCommon() { downlinkAction = false; // calculate the Rx timeout - // according to the spec, this must be at least enough time to effectively detect a preamble - // but pad it a bit on both sides (start and end) to make sure it is wide enough RadioLibTime_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000; RadioLibTime_t timeoutMod = this->phyLayer->calculateRxTimeout(timeoutHost); // wait for the start of the Rx window + RadioLibTime_t waitLen = this->rxDelayStart + this->rxDelays[i] - mod->hal->millis(); + // make sure that no underflow occured; if so, clip the delay (although this will likely miss any downlink) + if(waitLen > this->rxDelays[i]) { + waitLen = this->rxDelays[i]; + } // the waiting duration is shortened a bit to cover any possible timing errors - RadioLibTime_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart); if(waitLen > scanGuard) { waitLen -= scanGuard; } @@ -1215,7 +1197,8 @@ int16_t LoRaWANNode::downlinkCommon() { RADIOLIB_ASSERT(state); DataRate_t dataRate; - findDataRate(this->rx2.drMax, &dataRate); + state = findDataRate(this->rx2.drMax, &dataRate); + RADIOLIB_ASSERT(state); state = this->phyLayer->setDataRate(dataRate); RADIOLIB_ASSERT(state); } @@ -1687,16 +1670,51 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { return(true); } -int16_t LoRaWANNode::setPhyProperties() { +int16_t LoRaWANNode::setPhyProperties(uint8_t dir) { // set the physical layer configuration - int8_t pwr = this->txPowerMax - this->txPowerCur * 2; - int16_t state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; - while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { - // go from the highest power and lower it until we hit one supported by the module - state = this->phyLayer->setOutputPower(pwr--); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN(""); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %6.3f MHz", dir ? 'D' : 'U', this->currentChannels[dir].freq); + int16_t state = this->phyLayer->setFrequency(this->currentChannels[dir].freq); + RADIOLIB_ASSERT(state); + + // if this channel is an FSK channel, toggle the FSK switch + if(this->band->dataRates[this->dataRates[dir]] == RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { + this->FSK = true; + } else { + this->FSK = false; } + + int8_t pwr = this->txPowerMax - this->txPowerCur * 2; + + // at this point, assume that Tx power value is already checked, so ignore the return value + (void)this->phyLayer->checkOutputPower(pwr, &pwr); + state = this->phyLayer->setOutputPower(pwr); RADIOLIB_ASSERT(state); + DataRate_t dr; + state = findDataRate(this->dataRates[dir], &dr); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setDataRate(dr); + RADIOLIB_ASSERT(state); + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: SF = %d, TX = %d dBm, BW = %6.3f kHz, CR = 4/%d", + dr.lora.spreadingFactor, pwr, dr.lora.bandwidth, dr.lora.codingRate); + + if(this->FSK) { + state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); + } + + // downlink messages are sent with inverted IQ + if(dir == RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK) { + if(!this->FSK) { + state = this->phyLayer->invertIQ(true); + RADIOLIB_ASSERT(state); + } + } + + // this only needs to be done once-ish uint8_t syncWord[3] = { 0 }; uint8_t syncWordLen = 0; size_t preLen = 0; @@ -1987,7 +2005,8 @@ void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) { uint8_t LoRaWANNode::maxPayloadDwellTime() { // configure current datarate DataRate_t dr; - findDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], &dr); + // TODO this may fail horribly? + (void)findDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], &dr); (void)this->phyLayer->setDataRate(dr); uint8_t minPayLen = 0; uint8_t maxPayLen = 255; @@ -2035,6 +2054,8 @@ int16_t LoRaWANNode::setTxPower(int8_t txPower) { } int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + uint8_t dataRateBand = this->band->dataRates[dr]; if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { @@ -2059,50 +2080,37 @@ int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { dataRate->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6; dataRate->lora.codingRate = (dataRateBand & 0x03) + 5; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: SF = %d, BW = %6.3f kHz, CR = 4/%d", - dataRate->lora.spreadingFactor, dataRate->lora.bandwidth, dataRate->lora.codingRate); } - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::configureChannel(uint8_t dir) { - // set the frequency - RADIOLIB_DEBUG_PROTOCOL_PRINTLN(""); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %6.3f MHz", dir ? 'D' : 'U', this->currentChannels[dir].freq); - int state = this->phyLayer->setFrequency(this->currentChannels[dir].freq); - RADIOLIB_ASSERT(state); - - // if this channel is an FSK channel, toggle the FSK switch - if(this->band->dataRates[this->dataRates[dir]] == RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { - this->FSK = true; - } else { - this->FSK = false; - } - - DataRate_t dr; - findDataRate(this->dataRates[dir], &dr); - state = this->phyLayer->setDataRate(dr); - RADIOLIB_ASSERT(state); - - if(this->FSK) { - state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); - } + state = this->phyLayer->checkDataRate(*dataRate); return(state); } -bool LoRaWANNode::sendMacCommandReq(uint8_t cid) { +int16_t LoRaWANNode::sendMacCommandReq(uint8_t cid) { bool valid = false; for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_MAC_COMMANDS; i++) { if(MacTable[i].cid == cid) { valid = MacTable[i].user; } } - if(!valid) - return(false); + if(!valid) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("You are not allowed to request this MAC command"); + return(RADIOLIB_ERR_INVALID_CID); + } + + // if there are already 15 MAC bytes in the uplink queue, we can't add a new one + if(this->commandsUp.len + 1 > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The maximum number of FOpts payload was reached"); + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + if(this->commandsUp.numCommands > RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The RadioLib internal MAC command queue was full"); + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + // delete any prior requests for this MAC command, in case this is requested more than once + (void)deleteMacCommand(cid, &this->commandsUp); LoRaWANMacCommand_t cmd = { .cid = cid, @@ -2182,8 +2190,6 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { case(RADIOLIB_LORAWAN_MAC_LINK_ADR): { int16_t state = RADIOLIB_ERR_UNKNOWN; // get the ADR configuration - // per spec, all these configuration should only be set if all ACKs are set, otherwise retain previous state - // but we don't bother and try to set each individual command uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4; uint8_t txSteps = cmd->payload[0] & 0x0F; bool isInternalTxDr = cmd->payload[3] >> 7; @@ -2193,19 +2199,15 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { uint8_t nbTrans = cmd->payload[3] & 0x0F; RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRReq: dataRate = %d, txSteps = %d, chMask = 0x%04x, chMaskCntl = %d, nbTrans = %d", drUp, txSteps, chMask, chMaskCntl, nbTrans); - // apply the configuration + // try to apply the datarate configuration uint8_t drAck = 0; if(drUp == 0x0F) { // keep the same drAck = 1; - // replace the 'placeholder' with the current actual value for saving - cmd->payload[0] = (cmd->payload[0] & 0x0F) | (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4); - } else if (this->band->dataRates[drUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { // check if the module supports this data rate DataRate_t dr; - findDataRate(drUp, &dr); - state = this->phyLayer->checkDataRate(dr); + state = findDataRate(drUp, &dr); if(state == RADIOLIB_ERR_NONE) { uint8_t drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin, @@ -2215,6 +2217,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { drAck = 1; } else { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure dataRate %d, code %d!", drUp, state); + drUp = 0x0F; // set value to 'keep the same' } } @@ -2224,25 +2227,20 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { if(txSteps == 0x0F) { pwrAck = 1; - // replace the 'placeholder' with the current actual value for saving - cmd->payload[0] = (cmd->payload[0] & 0xF0) | this->txPowerCur; - } else { - int8_t pwr = this->txPowerMax - 2*txSteps; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: TX = %d dBm", pwr); - state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; - while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { - // go from the highest power and lower it until we hit one supported by the module - state = this->phyLayer->setOutputPower(pwr--); - } - // only acknowledge if the requested datarate was succesfully configured - if(state == RADIOLIB_ERR_NONE) { + int8_t power = this->txPowerMax - 2*txSteps; + int8_t powerActual = 0; + state = this->phyLayer->checkOutputPower(power, &powerActual); + // only acknowledge if the radio is able to operate at or below the requested power level + if(state == RADIOLIB_ERR_NONE || (state == RADIOLIB_ERR_INVALID_OUTPUT_POWER && powerActual < power)) { pwrAck = 1; this->txPowerCur = txSteps; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure Tx power %d, code %d!", power, state); + txSteps = 0x0F; // set value to 'keep the same' } } - uint8_t chMaskAck = 1; // only apply channel mask when the RFU bit is not set // (which is only set in internal MAC commands for changing Tx/Dr) @@ -2269,12 +2267,23 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { } } - if(nbTrans == 0) { // keep the same - cmd->payload[3] = (cmd->payload[3] & 0xF0) | this->nbTrans; // set current number of retransmissions for saving - } else { + if(nbTrans) { // if there is a value for NbTrans, set this value this->nbTrans = nbTrans; } + // replace 'placeholder' or failed values with the current values for saving + // per spec, all these configuration should only be set if all ACKs are set, otherwise retain previous state + // but we don't bother and try to set each individual command + if(drUp == 0x0F || !drAck) { + cmd->payload[0] = (cmd->payload[0] & 0x0F) | (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4); + } + if(txSteps == 0x0F || !pwrAck) { + cmd->payload[0] = (cmd->payload[0] & 0xF0) | this->txPowerCur; + } + if(nbTrans == 0) { + cmd->payload[3] = (cmd->payload[3] & 0xF0) | this->nbTrans; + } + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { // if RFU bit is set, this is just a change in Datarate or TxPower, so read ADR command and overwrite first byte if(isInternalTxDr) { @@ -2801,6 +2810,10 @@ uint64_t LoRaWANNode::getDevAddr() { return(this->devAddr); } +RadioLibTime_t LoRaWANNode::getLastToA() { + return(this->lastToA); +} + // The following function enables LMAC, a CSMA scheme for LoRa as specified // in the LoRa Alliance Technical Recommendation #13. // A user may enable CSMA to provide frames an additional layer of protection from interference. diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 061a20b2d..9359c2ae2 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -159,6 +159,12 @@ #define RADIOLIB_LORAWAN_MIC_DATA_RATE_POS (3) #define RADIOLIB_LORAWAN_MIC_CH_INDEX_POS (4) +// maximum allowed dwell time on bands that implement dwell time limitations +#define RADIOLIB_LORAWAN_DWELL_TIME (400) + +// unused frame counter value +#define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) + // MAC commands #define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (16) @@ -179,15 +185,6 @@ #define RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP (0x0F) #define RADIOLIB_LORAWAN_MAC_PROPRIETARY (0x80) -// maximum allowed dwell time on bands that implement dwell time limitations -#define RADIOLIB_LORAWAN_DWELL_TIME (400) - -// unused LoRaWAN version -#define RADIOLIB_LORAWAN_VERSION_NONE (0xFF) - -// unused frame counter value -#define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) - // the length of internal MAC command queue - hopefully this is enough for most use cases #define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (9) @@ -217,74 +214,109 @@ struct LoRaWANMacSpec_t { const bool user; }; -const LoRaWANMacSpec_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS + 1] = { +constexpr LoRaWANMacSpec_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS + 1] = { { 0x00, 0, 0, false }, // not an actual MAC command, exists for index offsetting - { RADIOLIB_LORAWAN_MAC_RESET, 1, 1, false }, - { RADIOLIB_LORAWAN_MAC_LINK_CHECK, 2, 0, true }, - { RADIOLIB_LORAWAN_MAC_LINK_ADR, 4, 1, false }, - { RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, 4, 1, false }, - { RADIOLIB_LORAWAN_MAC_DEV_STATUS, 0, 2, false }, - { RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, 5, 1, false }, - { RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_DL_CHANNEL, 4, 1, false }, - { RADIOLIB_LORAWAN_MAC_REKEY, 1, 1, false }, - { RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, 1, 0, false }, - { RADIOLIB_LORAWAN_MAC_DEVICE_TIME, 5, 0, true }, - { RADIOLIB_LORAWAN_MAC_FORCE_REJOIN, 2, 0, false }, - { RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP, 1, 1, false }, - { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, true } + { RADIOLIB_LORAWAN_MAC_RESET, 1, 1, false }, + { RADIOLIB_LORAWAN_MAC_LINK_CHECK, 2, 0, true }, + { RADIOLIB_LORAWAN_MAC_LINK_ADR, 4, 1, false }, + { RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, 1, 0, false }, + { RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, 4, 1, false }, + { RADIOLIB_LORAWAN_MAC_DEV_STATUS, 0, 2, false }, + { RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, 5, 1, false }, + { RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, 1, 0, false }, + { RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, 1, 0, false }, + { RADIOLIB_LORAWAN_MAC_DL_CHANNEL, 4, 1, false }, + { RADIOLIB_LORAWAN_MAC_REKEY, 1, 1, false }, + { RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, 1, 0, false }, + { RADIOLIB_LORAWAN_MAC_DEVICE_TIME, 5, 0, true }, + { RADIOLIB_LORAWAN_MAC_FORCE_REJOIN, 2, 0, false }, + { RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP, 1, 1, false }, + { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, true } +}; + +/*! + \struct LoRaWANMacCommand_t + \brief Structure to save information about MAC command +*/ +struct LoRaWANMacCommand_t { + /*! \brief The command ID */ + uint8_t cid; + + /*! \brief Payload buffer (5 bytes is the longest possible) */ + uint8_t payload[5]; + + /*! \brief Length of the payload */ + uint8_t len; + + /*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */ + uint8_t repeat; +}; + +/*! + \struct LoRaWANMacCommandQueue_t + \brief Structure to hold information about a queue of MAC commands +*/ +struct LoRaWANMacCommandQueue_t { + /*! \brief Number of commands in the queue */ + uint8_t numCommands; + + /*! \brief Total length of the queue */ + uint8_t len; + + /*! \brief MAC command buffer */ + LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE]; }; #define RADIOLIB_LORAWAN_NONCES_VERSION_VAL (0x0001) enum LoRaWANSchemeBase_t { - RADIOLIB_LORAWAN_NONCES_VERSION = 0x00, // 2 bytes - RADIOLIB_LORAWAN_NONCES_MODE = 0x02, // 2 bytes - RADIOLIB_LORAWAN_NONCES_CLASS = 0x04, // 1 byte - RADIOLIB_LORAWAN_NONCES_PLAN = 0x05, // 1 byte - RADIOLIB_LORAWAN_NONCES_CHECKSUM = 0x06, // 2 bytes - RADIOLIB_LORAWAN_NONCES_DEV_NONCE = 0x08, // 2 bytes - RADIOLIB_LORAWAN_NONCES_JOIN_NONCE = 0x0A, // 3 bytes - RADIOLIB_LORAWAN_NONCES_ACTIVE = 0x0D, // 1 byte - RADIOLIB_LORAWAN_NONCES_SIGNATURE = 0x0E, // 2 bytes - RADIOLIB_LORAWAN_NONCES_BUF_SIZE = 0x10 // = 16 bytes + RADIOLIB_LORAWAN_NONCES_START = 0x00, + RADIOLIB_LORAWAN_NONCES_VERSION = RADIOLIB_LORAWAN_NONCES_START, // 2 bytes + RADIOLIB_LORAWAN_NONCES_MODE = RADIOLIB_LORAWAN_NONCES_VERSION + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_CLASS = RADIOLIB_LORAWAN_NONCES_MODE + sizeof(uint16_t), // 1 byte + RADIOLIB_LORAWAN_NONCES_PLAN = RADIOLIB_LORAWAN_NONCES_CLASS + sizeof(uint8_t), // 1 byte + RADIOLIB_LORAWAN_NONCES_CHECKSUM = RADIOLIB_LORAWAN_NONCES_PLAN + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_DEV_NONCE = RADIOLIB_LORAWAN_NONCES_CHECKSUM + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_JOIN_NONCE = RADIOLIB_LORAWAN_NONCES_DEV_NONCE + sizeof(uint16_t), // 3 bytes + RADIOLIB_LORAWAN_NONCES_ACTIVE = RADIOLIB_LORAWAN_NONCES_JOIN_NONCE + 3, // 1 byte + RADIOLIB_LORAWAN_NONCES_SIGNATURE = RADIOLIB_LORAWAN_NONCES_ACTIVE + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_BUF_SIZE = RADIOLIB_LORAWAN_NONCES_SIGNATURE + sizeof(uint16_t) // Nonces buffer size }; enum LoRaWANSchemeSession_t { - RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY = 0x00, // 16 bytes - RADIOLIB_LORAWAN_SESSION_APP_SKEY = 0x10, // 16 bytes - RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = 0x20, // 16 bytes - RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = 0x30, // 16 bytes - RADIOLIB_LORAWAN_SESSION_DEV_ADDR = 0x40, // 4 bytes - RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE = 0x44, // 2 bytes - RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN = 0x46, // 4 bytes - RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP = 0x4A, // 4 bytes - RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN = 0x4E, // 4 bytes - RADIOLIB_LORAWAN_SESSION_RJ_COUNT0 = 0x52, // 2 bytes - RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 = 0x54, // 2 bytes - RADIOLIB_LORAWAN_SESSION_HOMENET_ID = 0x56, // 4 bytes - RADIOLIB_LORAWAN_SESSION_VERSION = 0x5A, // 1 byte - RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE = 0x5B, // 1 byte - RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP = 0x5C, // 4 bytes - RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = 0x60, // 1 byte - RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = 0x61, // 1 byte - RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = 0x62, // 1 byte - RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = 0x63, // 1 byte - RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = 0x64, // 3 bytes - RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL = 0x67, // 4 bytes - RADIOLIB_LORAWAN_SESSION_PERIODICITY = 0x6B, // 1 byte - RADIOLIB_LORAWAN_SESSION_LAST_TIME = 0x6C, // 4 bytes - RADIOLIB_LORAWAN_SESSION_UL_CHANNELS = 0x70, // 16*8 bytes - RADIOLIB_LORAWAN_SESSION_DL_CHANNELS = 0xF0, // 16*4 bytes - RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL = 0x0130, // 9*8+2 bytes - RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = 0x017A, // 4 bytes - RADIOLIB_LORAWAN_SESSION_ADR_FCNT = 0x017E, // 4 bytes - RADIOLIB_LORAWAN_SESSION_LINK_ADR = 0x0182, // 4 bytes - RADIOLIB_LORAWAN_SESSION_FCNT_UP = 0x0186, // 4 bytes - RADIOLIB_LORAWAN_SESSION_SIGNATURE = 0x018A, // 2 bytes - RADIOLIB_LORAWAN_SESSION_BUF_SIZE = 0x018C // 396 bytes + RADIOLIB_LORAWAN_SESSION_START = 0x00, + RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY = RADIOLIB_LORAWAN_SESSION_START, // 16 bytes + RADIOLIB_LORAWAN_SESSION_APP_SKEY = RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY + RADIOLIB_AES128_BLOCK_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_APP_SKEY + RADIOLIB_AES128_BLOCK_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY + RADIOLIB_AES128_BLOCK_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_DEV_ADDR = RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY + RADIOLIB_AES128_BLOCK_SIZE, // 4 bytes + RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE = RADIOLIB_LORAWAN_SESSION_DEV_ADDR + sizeof(uint32_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE + sizeof(uint16_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP = RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_RJ_COUNT0 = RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN + sizeof(uint32_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 = RADIOLIB_LORAWAN_SESSION_RJ_COUNT0 + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_HOMENET_ID = RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 + sizeof(uint16_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_VERSION = RADIOLIB_LORAWAN_SESSION_HOMENET_ID + sizeof(uint32_t), // 1 byte + RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE = RADIOLIB_LORAWAN_SESSION_VERSION + sizeof(uint8_t), // 1 byte + RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE + MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn, // 4 bytes + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn, // 1 byte + RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn, // 1 byte + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn, // 1 byte + RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP].lenDn, // 1 byte + RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + MacTable[RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP].lenDn, // 3 bytes + RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL = RADIOLIB_LORAWAN_SESSION_BEACON_FREQ + 3, // 4 bytes + RADIOLIB_LORAWAN_SESSION_PERIODICITY = RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL + 4, // 1 byte + RADIOLIB_LORAWAN_SESSION_LAST_TIME = RADIOLIB_LORAWAN_SESSION_PERIODICITY + 1, // 4 bytes + RADIOLIB_LORAWAN_SESSION_UL_CHANNELS = RADIOLIB_LORAWAN_SESSION_LAST_TIME + 4, // 16*5 bytes + RADIOLIB_LORAWAN_SESSION_DL_CHANNELS = RADIOLIB_LORAWAN_SESSION_UL_CHANNELS + 16*MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn, // 16*4 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL = RADIOLIB_LORAWAN_SESSION_DL_CHANNELS + 16*MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn, // 9*8+2 bytes + RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL + sizeof(LoRaWANMacCommandQueue_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_ADR_FCNT = RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_LINK_ADR = RADIOLIB_LORAWAN_SESSION_ADR_FCNT + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_FCNT_UP = RADIOLIB_LORAWAN_SESSION_LINK_ADR + MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, // 4 bytes + RADIOLIB_LORAWAN_SESSION_SIGNATURE = RADIOLIB_LORAWAN_SESSION_FCNT_UP + sizeof(uint32_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_BUF_SIZE = RADIOLIB_LORAWAN_SESSION_SIGNATURE + sizeof(uint16_t) // Session buffer size }; /*! @@ -428,38 +460,6 @@ enum LoRaWANBandNum_t { // array of currently supported bands extern const LoRaWANBand_t* LoRaWANBands[]; -/*! - \struct LoRaWANMacCommand_t - \brief Structure to save information about MAC command -*/ -struct LoRaWANMacCommand_t { - /*! \brief The command ID */ - uint8_t cid; - - /*! \brief Payload buffer (5 bytes is the longest possible) */ - uint8_t payload[5]; - - /*! \brief Length of the payload */ - uint8_t len; - - /*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */ - uint8_t repeat; -}; -/*! - \struct LoRaWANMacCommandQueue_t - \brief Structure to hold information about a queue of MAC commands -*/ -struct LoRaWANMacCommandQueue_t { - /*! \brief Number of commands in the queue */ - uint8_t numCommands; - - /*! \brief Total length of the queue */ - uint8_t len; - - /*! \brief MAC command buffer */ - LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE]; -}; - /*! \struct LoRaWANEvent_t \brief Structure to save extra information about uplink/downlink event. @@ -567,15 +567,16 @@ class LoRaWANNode { \brief Join network by performing activation by personalization. In this procedure, all necessary configuration must be provided by the user. \param addr Device address. - \param nwkSKey Pointer to the network session AES-128 key (LoRaWAN 1.0) or MAC command network session key (LoRaWAN 1.1). + \param fNwkSIntKey Pointer to the Forwarding network session (LoRaWAN 1.1), NULL for LoRaWAN 1.0. + \param sNwkSIntKey Pointer to the Serving network session (LoRaWAN 1.1), NULL for LoRaWAN 1.0. + \param nwkSEncKey Pointer to the MAC command network session key [NwkSEncKey] (LoRaWAN 1.1) + or network session AES-128 key [NwkSKey] (LoRaWAN 1.0). \param appSKey Pointer to the application session AES-128 key. - \param fNwkSIntKey Pointer to the Forwarding network session (LoRaWAN 1.1), unused for LoRaWAN 1.0. - \param sNwkSIntKey Pointer to the Serving network session (LoRaWAN 1.1), unused for LoRaWAN 1.0. \param force Set to true to force a new session, even if one exists. \param initialDr The datarate at which to send the first uplink and any subsequent uplinks (unless ADR is enabled) \returns \ref status_codes */ - int16_t beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL, bool force = false, uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED); + int16_t beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey, bool force = false, uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED); /*! \brief Whether there is an ongoing session active */ bool isJoined(); @@ -591,9 +592,9 @@ class LoRaWANNode { Only LinkCheck and DeviceTime are available to the user. Other commands are ignored; duplicate MAC commands are discarded. \param cid ID of the MAC command - \returns Whether or not the MAC command was added to the queue. + \returns \ref status_codes */ - bool sendMacCommandReq(uint8_t cid); + int16_t sendMacCommandReq(uint8_t cid); #if defined(RADIOLIB_BUILD_ARDUINO) /*! @@ -835,6 +836,12 @@ class LoRaWANNode { */ uint64_t getDevAddr(); + /*! + \brief Get the Time-on-air of the last uplink message + \returns (RadioLibTime_t) time-on-air (ToA) of last uplink message + */ + RadioLibTime_t getLastToA(); + #if !RADIOLIB_GODMODE private: #endif @@ -964,7 +971,7 @@ class LoRaWANNode { // configure the common physical layer properties (preamble, sync word etc.) // channels must be configured separately by setupChannelsDyn()! - int16_t setPhyProperties(); + int16_t setPhyProperties(uint8_t dir); // setup uplink/downlink channel data rates and frequencies // for dynamic channels, there is a small set of predefined channels @@ -984,9 +991,6 @@ class LoRaWANNode { // find the first usable data rate for the given band int16_t findDataRate(uint8_t dr, DataRate_t* dataRate); - // configure channel based on cached data rate ID and frequency - int16_t configureChannel(uint8_t dir); - // restore all available channels from persistent storage int16_t restoreChannels(); diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.cpp b/src/protocols/PhysicalLayer/PhysicalLayer.cpp index 822cc18a6..55e69329d 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.cpp +++ b/src/protocols/PhysicalLayer/PhysicalLayer.cpp @@ -256,6 +256,12 @@ int16_t PhysicalLayer::setOutputPower(int8_t power) { return(RADIOLIB_ERR_UNSUPPORTED); } +int16_t PhysicalLayer::checkOutputPower(int8_t power, int8_t* clipped) { + (void)power; + (void)clipped; + return(RADIOLIB_ERR_UNSUPPORTED); +} + int16_t PhysicalLayer::setSyncWord(uint8_t* sync, size_t len) { (void)sync; (void)len; diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.h b/src/protocols/PhysicalLayer/PhysicalLayer.h index f27e79ea1..3d48eda2b 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.h +++ b/src/protocols/PhysicalLayer/PhysicalLayer.h @@ -276,6 +276,14 @@ class PhysicalLayer { */ virtual int16_t setOutputPower(int8_t power); + /*! + \brief Check if output power is configurable. Must be implemented in module class if the module supports it. + \param power Output power in dBm. The allowed range depends on the module used. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + virtual int16_t checkOutputPower(int8_t power, int8_t* clipped); + /*! \brief Set sync word. Must be implemented in module class if the module supports it. \param sync Pointer to the sync word.