From a9699d42f10f2bfe5a0035ea1ff9f96285b8760b Mon Sep 17 00:00:00 2001 From: StevenCellist Date: Mon, 2 Sep 2024 16:16:08 +0200 Subject: [PATCH] [LoRaWAN] Major rework --- src/protocols/LoRaWAN/LoRaWAN.cpp | 4495 +++++++++++++----------- src/protocols/LoRaWAN/LoRaWAN.h | 766 ++-- src/protocols/LoRaWAN/LoRaWANBands.cpp | 556 ++- 3 files changed, 3122 insertions(+), 2695 deletions(-) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index a1f63a7d..bf5c056c 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -6,54 +6,189 @@ #if !RADIOLIB_EXCLUDE_LORAWAN -// flag to indicate whether there was some action during Rx mode (timeout or downlink) -static volatile bool downlinkAction = false; - -// interrupt service routine to handle downlinks automatically -#if defined(ESP8266) || defined(ESP32) - IRAM_ATTR -#endif -static void LoRaWANNodeOnDownlinkAction(void) { - downlinkAction = true; -} - -uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_t min, uint8_t max) { - int8_t dr = uplink - offset + base; - if(dr < min) { - dr = min; - } else if (dr > max) { - dr = max; - } - return(dr); -} - LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand) { this->phyLayer = phy; this->band = band; - this->rx2 = this->band->rx2; + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; this->txPowerMax = this->band->powerMax; this->subBand = subBand; - this->difsSlots = 2; - this->backoffMax = 6; - this->enableCSMA = false; this->dwellTimeEnabledUp = this->dwellTimeUp != 0; this->dwellTimeEnabledDn = this->dwellTimeDn != 0; - memset(this->availableChannels, 0, sizeof(this->availableChannels)); + memset(this->channelPlan, 0, sizeof(this->channelPlan)); } -void LoRaWANNode::setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA) { - this->backoffMax = backoffMax; - this->difsSlots = difsSlots; - this->enableCSMA = enableCSMA; +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + const char* dataUp = strUp.c_str(); + + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + state = this->sendReceive((uint8_t*)dataUp, strlen(dataUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown); + + if(state == RADIOLIB_ERR_NONE) { + // add null terminator + dataDown[lenDown] = '\0'; + + // initialize Arduino String class + strDown = String((char*)dataDown); + } + + return(state); +} +#endif + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + state = this->sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown); + + return(state); } -void LoRaWANNode::createNonces() { - // set the device credentials - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], this->lwMode); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], this->lwClass); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], this->keyCheckSum); +int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + state = this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, lenDown, isConfirmed, eventUp, eventDown); + + return(state); +} + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // if after (at) ADR_ACK_LIMIT frames no RekeyConf was received, revert to Join state + if(this->fCntUp == (1 << this->adrLimitExp)) { + state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_REKEY, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK); + if(state == RADIOLIB_ERR_NONE) { + this->clearSession(); + } + } + + // if not joined, don't do anything + if(!this->isActivated()) { + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + if(this->adrEnabled) { + this->adrBackoff(); + } + + // check if the requested payload + fPort are allowed, also given dutycycle + uint8_t totalLen = lenUp + this->fOptsUpLen; + state = this->isValidUplink(&totalLen, fPort); + RADIOLIB_ASSERT(state); + + // in case of TS009, a payload that is too long may have gotten clipped, + // so recalculate the actual payload length + // (outside of TS009, a payload that is too long throws an error) + lenUp = totalLen - this->fOptsUpLen; + + // the first 16 bytes are reserved for MIC calculation blocks + size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(lenUp, this->fOptsUpLen); + #if RADIOLIB_STATIC_ONLY + uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen]; + #endif + + // build the encrypted uplink message + this->composeUplink(dataUp, lenUp, uplinkMsg, uplinkMsgLen, fPort, isConfirmed); + + // reset Time-on-Air as we are starting new uplink sequence + this->lastToA = 0; + + // repeat uplink+downlink up to 'nbTrans' times (ADR) + uint8_t trans = 0; + for(; trans < this->nbTrans; trans++) { + + // keep track of number of hopped channels + uint8_t numHops = this->maxChanges; + + // number of additional CAD tries + uint8_t numBackoff = 0; + if(this->backoffMax) { + numBackoff = this->phyLayer->random(1, this->backoffMax + 1); + } + + do { + // select a pair of Tx/Rx channels for uplink+downlink + this->selectChannels(); + + // generate and set uplink MIC (depends on selected channel) + this->micUplink(uplinkMsg, uplinkMsgLen); + + // if CSMA is enabled, repeat channel selection & encryption up to numHops times + } while(this->csmaEnabled && numHops-- > 0 && !this->csmaChannelClear(this->difsSlots, numBackoff)); + + // send it (without the MIC calculation blocks) + state = this->transmitUplink(&this->channels[RADIOLIB_LORAWAN_UPLINK], + &uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], + (uint8_t)(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS)); + if(state != RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] uplinkMsg; + #endif + RADIOLIB_ASSERT(state); + } + + // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received + state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart); + + // if a downlink was received, stop retransmission + if(state == RADIOLIB_ERR_NONE) { + break; + } + // if a hardware error occurred, return + if(state != RADIOLIB_LORAWAN_NO_DOWNLINK) { + #if !RADIOLIB_STATIC_ONLY + delete[] uplinkMsg; + #endif + RADIOLIB_ASSERT(state); + } + } // end of transmission + + // pass the uplink info if requested + if(eventUp) { + eventUp->dir = RADIOLIB_LORAWAN_UPLINK; + eventUp->confirmed = isConfirmed; + eventUp->confirming = (this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE); + eventUp->datarate = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + eventUp->freq = this->channels[RADIOLIB_LORAWAN_UPLINK].freq; + eventUp->power = this->txPowerMax - this->txPowerSteps * 2; + eventUp->fCnt = this->fCntUp; + eventUp->fPort = fPort; + eventUp->nbTrans = trans; + } + + // the downlink confirmation was acknowledged, so clear the counter value + this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + + // increase frame counter by one for the next uplink + this->fCntUp += 1; + + // if no downlink was received, remove only non-persistent MAC commands + // the other commands should be re-sent until downlink is received + if(state == RADIOLIB_LORAWAN_NO_DOWNLINK) { + LoRaWANNode::clearMacCommands(this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + return(state); + } + + // a downlink was received, so we can clear the whole MAC uplink buffer + memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + this->fOptsUpLen = 0; + + state = this->parseDownlink(dataDown, lenDown, eventDown); + return(state); } void LoRaWANNode::clearNonces() { @@ -66,15 +201,14 @@ void LoRaWANNode::clearNonces() { this->rev = 0; } -void LoRaWANNode::clearSession() { - memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); - memset(&(this->commandsUp), 0, sizeof(LoRaWANMacCommandQueue_t)); - memset(&(this->commandsDown), 0, sizeof(LoRaWANMacCommandQueue_t)); - this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; - this->isActive = false; -} - uint8_t* LoRaWANNode::getBufferNonces() { + // set the device credentials + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], this->lwMode); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], this->lwClass); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], this->keyCheckSum); + // generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2); LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature); @@ -118,6 +252,166 @@ int16_t LoRaWANNode::setBufferNonces(uint8_t* persistentBuffer) { return(state); } +void LoRaWANNode::clearSession() { + memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; + this->isActive = false; + + // reset all frame counters + this->fCntUp = 0; + this->aFCntDown = 0; + this->nFCntDown = 0; + this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; + this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + this->adrFCnt = 0; + + // reset number of retransmissions from ADR + this->nbTrans = 1; + + // clear CSMA settings + this->csmaEnabled = false; + this->maxChanges = 0; + this->difsSlots = 0; + this->backoffMax = 0; +} + +void LoRaWANNode::createSession(uint16_t lwMode, uint8_t initialDr) { + this->clearSession(); + + // setup JoinRequest uplink/downlink frequencies and datarates + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(true); + } else { + this->selectChannelPlanFix(); + } + + uint8_t drUp = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + + // on fixed bands, the first OTAA uplink (JoinRequest) is sent on fixed datarate + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && lwMode == RADIOLIB_LORAWAN_MODE_OTAA) { + // randomly select one of 8 or 9 channels and find corresponding datarate + uint8_t numChannels = this->band->numTxSpans == 1 ? 8 : 9; + uint8_t rand = this->phyLayer->random(numChannels) + 1; // range 1-8 or 1-9 + if(rand <= 8) { + drUp = this->band->txSpans[0].drJoinRequest; // if one of the first 8 channels, select datarate of span 0 + } else { + drUp = this->band->txSpans[1].drJoinRequest; // if ninth channel, select datarate of span 1 + } + } + + // on dynamic bands, the first OTAA uplink (JoinRequest) can be any available datarate + // this is also true for ABP where there is no JoinRequest + if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + uint8_t i = 0; + for(; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + if(initialDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin + && initialDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) { + break; + } + } + } + // if there is no channel that allowed the user-specified datarate, revert to default datarate + if(i == RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); + initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + } + } + + // if there is no (channel that allowed the) user-specified datarate, use a default datarate + // we use the floor of the average datarate of the first enabled channel + if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + uint8_t drMin = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin; + uint8_t drMax = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax; + drUp = (drMin + drMax) / 2; + } + } + } + + uint8_t cOcts[5]; // 5 = maximum downlink payload length + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = (drUp << 4); // set uplink datarate + cOcts[0] |= 0; // default to max Tx Power + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; + LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + uint8_t maxDCyclePower; + switch(this->band->dutyCycle) { + case(0): + maxDCyclePower = 0; + break; + case(3600): + maxDCyclePower = 10; + break; + case(36000): + maxDCyclePower = 7; + break; + default: + maxDCyclePower = 0; + break; + } + cOcts[0] = maxDCyclePower; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET << 4); + cOcts[0] |= this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr; // may be set by user, otherwise band's default upon initialization + LoRaWANNode::hton(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (this->band->dwellTimeDn > 0 ? 1 : 0) << 5; + cOcts[0] |= (this->band->dwellTimeUp > 0 ? 1 : 0) << 4; + uint8_t maxEIRPRaw; + switch(this->band->powerMax) { + case(12): + maxEIRPRaw = 2; + break; + case(14): + maxEIRPRaw = 4; + break; + case(16): + maxEIRPRaw = 5; + break; + case(19): // this option does not exist for the TxParamSetupReq but will be caught during execution + maxEIRPRaw = 7; + break; + case(30): + maxEIRPRaw = 13; + break; + default: + maxEIRPRaw = 2; + break; + } + cOcts[0] |= maxEIRPRaw; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4); + cOcts[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N << 4); + cOcts[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N; + (void)execMacCommand(cid, cOcts, cLen); +} + uint8_t* LoRaWANNode::getBufferSession() { // store all frame counters LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN], this->aFCntDown); @@ -127,8 +421,12 @@ uint8_t* LoRaWANNode::getBufferSession() { LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT], this->adrFCnt); LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP], this->fCntUp); + uint16_t chMask = 0x0000; + (void)this->getAvailableChannels(&chMask); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS], chMask); + // save the current uplink MAC command queue - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t)); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], this->fOptsUp, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); // generate the signature of the Session buffer, and store it in the last two bytes of the Session buffer uint16_t signature = LoRaWANNode::checkSum16(this->bufferSession, RADIOLIB_LORAWAN_SESSION_BUF_SIZE - 2); @@ -186,106 +484,84 @@ int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { // all-zero buffer used for checking if MAC commands are set uint8_t bufferZeroes[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - LoRaWANMacCommand_t cmd = { - .cid = 0, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; + uint8_t cOcts[14]; // TODO explain + uint8_t cid; + uint8_t cLen; - // for dynamic bands, first restore the defined channels before restoring ADR - // this is because the ADR command acts as a mask for the enabled channels + // setup the default channels if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // setup the default channels - state = this->setupChannelsDyn(); - RADIOLIB_ASSERT(state); + this->selectChannelPlanDyn(); + } else { // type == RADIOLIB_LORAWAN_BAND_FIXED) + this->selectChannelPlanFix(); + } + // for dynamic bands, the additional channels must be restored per-channel + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { // restore the session channels uint8_t *startChannelsUp = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; - cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - memcpy(cmd.payload, startChannelsUp + (i * cmd.len), cmd.len); - if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) { // only execute if it is not all zeroes - cmd.repeat = 1; - (void)execMacCommand(&cmd); + memcpy(cOcts, startChannelsUp + (i * cLen), cLen); + if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes + (void)execMacCommand(cid, cOcts, cLen); } } uint8_t *startChannelsDown = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS]; - cmd.cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; + cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DL_CHANNEL].lenDn; - memcpy(cmd.payload, startChannelsDown + (i * cmd.len), cmd.len); - if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) { // only execute if it is not all zeroes - (void)execMacCommand(&cmd); + memcpy(cOcts, startChannelsDown + (i * cLen), cLen); + if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes + (void)execMacCommand(cid, cOcts, cLen); } } } - cmd.cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn, - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cmd.len); - (void)execMacCommand(&cmd); + cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cLen = 14; // special internal ADR command + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], cLen); + (void)execMacCommand(cid, cOcts, cLen); - cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], cmd.len); - (void)execMacCommand(&cmd); + cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], cLen); + (void)execMacCommand(cid, cOcts, cLen); - // for fixed bands, first restore ADR, then the defined channels - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { - // setup the default channels - state = this->setupChannelsFix(this->subBand); - RADIOLIB_ASSERT(state); + cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], cLen); + (void)execMacCommand(cid, cOcts, cLen); - // restore the session channels - uint8_t *startMACpayload = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; - - // there are at most 8 channel masks present - cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; - for(int i = 0; i < 8; i++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - memcpy(cmd.payload, startMACpayload + (i * cmd.len), cmd.len); - // there COULD, according to spec, be an all zeroes ADR command - meh - if(memcmp(cmd.payload, bufferZeroes, cmd.len) == 0) { - break; - } - cmd.repeat = (i+1); - (void)execMacCommand(&cmd); - } + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], cLen); + (void)execMacCommand(cid, cOcts, cLen); - } + cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cLen); + (void)execMacCommand(cid, cOcts, cLen); - cmd.cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], cmd.len); - (void)execMacCommand(&cmd); + cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], cLen); + (void)execMacCommand(cid, cOcts, cLen); - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], cmd.len); - (void)execMacCommand(&cmd); + cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], cLen); + (void)execMacCommand(cid, cOcts, cLen); - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], cmd.len); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP].lenDn; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], cmd.len); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP].lenDn; - memcpy(cmd.payload, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], cmd.len); - (void)execMacCommand(&cmd); + // set the available channels + uint16_t chMask = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS]); + this->setAvailableChannels(chMask); // copy uplink MAC command queue back in place - memcpy(&this->commandsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], sizeof(LoRaWANMacCommandQueue_t)); + memcpy(this->fOptsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL], RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); // as both the Nonces and session are restored, revert to active session this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; @@ -293,167 +569,6 @@ int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { return(state); } -int16_t LoRaWANNode::checkBufferCommon(uint8_t *buffer, uint16_t size) { - // check if there are actually values in the buffer - size_t i = 0; - for(; i < size; i++) { - if(buffer[i]) { - break; - } - } - if(i == size) { - return(RADIOLIB_ERR_NETWORK_NOT_JOINED); - } - - // check integrity of the whole buffer (compare checksum to included checksum) - uint16_t checkSum = LoRaWANNode::checkSum16(buffer, size - 2); - uint16_t signature = LoRaWANNode::ntoh(&buffer[size - 2]); - if(signature != checkSum) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Calculated checksum: %04x, expected: %04x", checkSum, signature); - return(RADIOLIB_ERR_CHECKSUM_MISMATCH); - } - return(RADIOLIB_ERR_NONE); -} - -void LoRaWANNode::activateCommon(uint8_t initialDr) { - uint8_t drUp = 0; - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // if join datarate is user-specified and valid, select that value - if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - if(initialDr >= this->band->txFreqs[0].drMin && initialDr <= this->band->txFreqs[0].drMax) { - drUp = initialDr; - } else { - // if there is no channel that allowed the user-specified datarate, revert to default datarate - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); - initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - } - - // if there is no (channel that allowed the) user-specified datarate, use a default datarate - // we use the floor of the average datarate of the first default channel - if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - drUp = (this->band->txFreqs[0].drMin + this->band->txFreqs[0].drMax) / 2; - } - - } else { - // if the user specified a certain datarate, check if any of the configured channels allows it - if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - uint8_t i = 0; - for(; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - if(initialDr >= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin - && initialDr <= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax) { - break; - } - } - } - // if there is no channel that allowed the user-specified datarate, revert to default datarate - if(i == RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); - initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - } - - // if there is no (channel that allowed the) user-specified datarate, use a default datarate - // we use the join-request datarate for one of the available channels - if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - // randomly select one of 8 or 9 channels and find corresponding datarate - uint8_t numChannels = this->band->numTxSpans == 1 ? 8 : 9; - uint8_t rand = this->phyLayer->random(numChannels) + 1; // range 1-8 or 1-9 - if(rand <= 8) { - drUp = this->band->txSpans[0].joinRequestDataRate; // if one of the first 8 channels, select datarate of span 0 - } else { - drUp = this->band->txSpans[1].joinRequestDataRate; // if ninth channel, select datarate of span 1 - } - } - - } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; - cmd.payload[0] = (drUp << 4); // set uplink datarate - cmd.payload[0] |= 0; // default to max Tx Power - cmd.payload[3] = (1 << 7); // set the RFU bit, which means that the channel mask gets ignored - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_DUTY_CYCLE].lenDn; - uint8_t maxDCyclePower; - switch(this->band->dutyCycle) { - case(0): - maxDCyclePower = 0; - break; - case(3600): - maxDCyclePower = 10; - break; - case(36000): - maxDCyclePower = 7; - break; - default: - maxDCyclePower = 0; - break; - } - cmd.payload[0] = maxDCyclePower; - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET << 4); - cmd.payload[0] |= this->rx2.drMax; // may be set by user, otherwise band's default upon initialization - uint32_t rx2Freq = uint32_t(this->rx2.freq * 10000); - LoRaWANNode::hton(&cmd.payload[1], rx2Freq, 3); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP].lenDn; - cmd.payload[0] = (this->band->dwellTimeDn > 0 ? 1 : 0) << 5; - cmd.payload[0] |= (this->band->dwellTimeUp > 0 ? 1 : 0) << 4; - uint8_t maxEIRPRaw; - switch(this->band->powerMax) { - case(12): - maxEIRPRaw = 2; - break; - case(14): - maxEIRPRaw = 4; - break; - case(16): - maxEIRPRaw = 5; - break; - case(19): // this option does not exist for the TxParamSetupReq but will be caught during execution - maxEIRPRaw = 7; - break; - case(30): - maxEIRPRaw = 13; - break; - default: - maxEIRPRaw = 2; - break; - } - cmd.payload[0] |= maxEIRPRaw; - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4); - cmd.payload[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP].lenDn; - cmd.payload[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N << 4); - cmd.payload[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N; - (void)execMacCommand(&cmd); -} - void LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey) { // clear all the device credentials in case there were any this->clearNonces(); @@ -467,118 +582,66 @@ void LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, } // generate activation key checksum - this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&joinEUI), 8); - this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&devEUI), 8); - this->keyCheckSum ^= LoRaWANNode::checkSum16(appKey, 16); + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&joinEUI), sizeof(uint64_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&devEUI), sizeof(uint64_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(appKey, RADIOLIB_AES128_KEY_SIZE); if(nwkKey) { - this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkKey, 16); + this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkKey, RADIOLIB_AES128_KEY_SIZE); } this->lwMode = RADIOLIB_LORAWAN_MODE_OTAA; this->lwClass = RADIOLIB_LORAWAN_CLASS_A; - - // set the device credentials - this->createNonces(); } -int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) { - // check if there is an active session - if(this->isActivated()) { - // already activated, don't do anything - return(RADIOLIB_ERR_NONE); - } - if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) { - // session restored but not yet activated - do so now - this->isActive = true; - return(RADIOLIB_LORAWAN_SESSION_RESTORED); - } +void LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey) { + // clear all the device credentials in case there were any + this->clearNonces(); - int16_t state = RADIOLIB_ERR_UNKNOWN; - - // either no valid session was found or user forced a new session, so clear all activity - this->clearSession(); - - // starting a new session, so make sure to update event fields already - if(joinEvent) { - joinEvent->newSession = true; - joinEvent->devNonce = this->devNonce; - joinEvent->joinNonce = this->joinNonce; - } - - // setup join-request uplink/downlink frequencies and datarates - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - state = this->setupChannelsDyn(true); + this->devAddr = addr; + memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + if(fNwkSIntKey && sNwkSIntKey) { + this->rev = 1; + memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } else { - state = this->setupChannelsFix(this->subBand); - } - RADIOLIB_ASSERT(state); - - // on fixed bands, the join-datarate is specified per specification - // therefore, we ignore the value that was specified by the user - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { - joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - // setup all MAC properties to default values - this->activateCommon(joinDr); - - // select a random pair of Tx/Rx channels - state = this->selectChannels(); - RADIOLIB_ASSERT(state); - - // set the physical layer configuration for uplink - state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); - RADIOLIB_ASSERT(state); - - if(this->dwellTimeEnabledUp) { - // calculate JoinRequest time-on-air in milliseconds - RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000; - if(toa > this->dwellTimeUp) { - return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); - } + memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->sNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); } + // generate activation key checksum + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&addr), sizeof(uint32_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + this->keyCheckSum ^= LoRaWANNode::checkSum16(appSKey, RADIOLIB_AES128_KEY_SIZE); + if(fNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } + if(sNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } + + this->lwMode = RADIOLIB_LORAWAN_MODE_ABP; + this->lwClass = RADIOLIB_LORAWAN_CLASS_A; +} + +void LoRaWANNode::composeJoinRequest(uint8_t* out) { // copy devNonce currently in use uint16_t devNonceUsed = this->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], this->joinEUI); - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], this->devEUI); - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonceUsed); + out[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], this->joinEUI); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], this->devEUI); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonceUsed); // add the authentication code uint32_t mic = 0; if(this->rev == 1) { - mic =this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->nwkKey); + mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->nwkKey); } else { - mic =this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->appKey); + mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->appKey); } - LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic); +} - // send it - Module* mod = this->phyLayer->getMod(); - state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); - this->rxDelayStart = mod->hal->millis(); - RADIOLIB_ASSERT(state); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce); - - // join-request successfully sent, so increase & save devNonce - this->devNonce += 1; - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce); - - // configure Rx delay for join-accept message - these are re-configured once a valid join-request is received - this->rxDelays[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS; - this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS; - - // make sure the Rx2 settings are back to this band's default - this->rx2 = this->band->rx2; - - // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received - state = downlinkCommon(); - RADIOLIB_ASSERT(state); +int16_t LoRaWANNode::processJoinAccept(LoRaWANJoinEvent_t *joinEvent) { + int16_t state = RADIOLIB_ERR_UNKNOWN; // build the buffer for the reply data uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; @@ -656,7 +719,7 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE; LoRaWANNode::hton(&micBuff[1], this->joinEUI); - LoRaWANNode::hton(&micBuff[9], devNonceUsed); + LoRaWANNode::hton(&micBuff[9], this->devNonce - 1); memcpy(&micBuff[11], joinAcceptMsg, lenRx); if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) { @@ -670,46 +733,39 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) } } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP].lenDn, - .repeat = 0, - }; - cmd.payload[0] = dlSettings & 0x7F; - uint32_t rx2Freq = uint32_t(this->rx2.freq * 10000); // default Rx2 frequency - LoRaWANNode::hton(&cmd.payload[1], rx2Freq, 3); - (void)execMacCommand(&cmd); - - cmd.cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; - memset(cmd.payload, 0, RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN); - cmd.payload[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP].lenDn; - (void)execMacCommand(&cmd); - // in case of dynamic band, setup the default channels first + // in case of dynamic band, reset the channels to clear JoinRequest-specific channels if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannelsDyn(false); + this->selectChannelPlanDyn(false); } + uint8_t cOcts[5]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + uint8_t cLen = 0; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = dlSettings & 0x7F; + LoRaWANNode::hton(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]; + (void)execMacCommand(cid, cOcts, cLen); + // process CFlist if present (and if CFListType matches used band type) if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN && joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS] == this->band->bandType) { - uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 }; - memcpy(&cfList[0], &joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS], RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN); - this->processCFList(cfList); + this->processCFList(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS]); } // if no (valid) CFList was received, default or subband are already setup so don't need to do anything else - // 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_AES_JOIN_NONCE_POS], this->joinNonce, 3); // check protocol version (1.0 vs 1.1) if(this->rev == 1) { // 1.1 version, derive the keys - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], this->joinEUI); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonceUsed); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS], this->joinEUI); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS], this->devNonce - 1); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(this->appKey); @@ -727,19 +783,10 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) RadioLibAES128Instance.init(this->nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey); - // enqueue the RekeyInd MAC command to be sent in the next uplink - cmd.cid = RADIOLIB_LORAWAN_MAC_REKEY; - memset(cmd.payload, 0, RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN); - cmd.payload[0] = this->rev; - cmd.len = sizeof(uint8_t); - cmd.repeat = 0x01 << RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP; - state = pushMacCommand(&cmd, &this->commandsUp); - RADIOLIB_ASSERT(state); - } else { // 1.0 version, just derive the keys LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], this->homeNetId, 3); - LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonceUsed); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], this->devNonce - 1); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(this->appKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); @@ -753,15 +800,16 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) } - // reset all frame counters - this->fCntUp = 0; - this->aFCntDown = 0; - this->nFCntDown = 0; - this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; - this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; - this->adrFCnt = 0; + // for LW v1.1, send the RekeyInd MAC command + if(this->rev == 1) { + // enqueue the RekeyInd MAC command to be sent in the next uplink + cid = RADIOLIB_LORAWAN_MAC_REKEY; + LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_UPLINK); + cOcts[0] = this->rev; + state = LoRaWANNode::pushMacCommand(cid, cOcts, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + RADIOLIB_ASSERT(state); + } - LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], this->joinNonce, 3); this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; @@ -772,10 +820,10 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) // store DevAddr and all keys LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR], this->devAddr); - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); // set the signature of the Nonces buffer in the Session buffer LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], signature); @@ -786,42 +834,93 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) this->isActive = true; - // received join-accept, so update JoinNonce value in event + // received JoinAccept, so update JoinNonce value in event if(joinEvent) { joinEvent->joinNonce = this->joinNonce; } - return(RADIOLIB_LORAWAN_NEW_SESSION); + return(state); } -void LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey) { - // clear all the device credentials in case there were any - this->clearNonces(); - - this->devAddr = addr; - memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); - if(fNwkSIntKey && sNwkSIntKey) { - this->rev = 1; - memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); - } else { - memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); - memcpy(this->sNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); +int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) { + // check if there is an active session + if(this->isActivated()) { + // already activated, don't do anything + return(RADIOLIB_ERR_NONE); + } + if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) { + // session restored but not yet activated - do so now + this->isActive = true; + return(RADIOLIB_LORAWAN_SESSION_RESTORED); } - // generate activation key checksum - this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&addr), 4); - this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkSEncKey, 16); - this->keyCheckSum ^= LoRaWANNode::checkSum16(appSKey, 16); - if(fNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, 16); } - if(sNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, 16); } + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); - this->lwMode = RADIOLIB_LORAWAN_MODE_ABP; - this->lwClass = RADIOLIB_LORAWAN_CLASS_A; + // starting a new session, so make sure to update event fields already + if(joinEvent) { + joinEvent->newSession = true; + joinEvent->devNonce = this->devNonce; + joinEvent->joinNonce = this->joinNonce; + } - // set the device credentials - this->createNonces(); + // setup all MAC properties to default values + this->createSession(RADIOLIB_LORAWAN_MODE_OTAA, joinDr); + + // build the JoinRequest message + uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; + this->composeJoinRequest(joinRequestMsg); + + // select a random pair of Tx/Rx channels + state = this->selectChannels(); + RADIOLIB_ASSERT(state); + + // set the physical layer configuration for uplink + state = this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK], + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + + // calculate JoinRequest time-on-air in milliseconds + if(this->dwellTimeEnabledUp) { + RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000; + if(toa > this->dwellTimeUp) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Dwell time exceeded: ToA = %d, max = %d", toa, this->dwellTimeUp); + return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); + } + } + + // if requested, delay until transmitting JoinRequest + RadioLibTime_t tNow = mod->hal->millis(); + if(this->tUplink > tNow) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %d ms", this->tUplink - tNow); + mod->hal->delay(this->tUplink - mod->hal->millis()); + } + + // send it + state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + + // JoinRequest successfully sent, so increase & save devNonce + this->devNonce += 1; + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce); + + // configure Rx1 and Rx2 delay for JoinAccept message - these are re-configured once a valid JoinAccept is received + this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS; + this->rxDelays[2] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS; + + // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received + state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart); + RADIOLIB_ASSERT(state); + + // process JoinAccept message + state = this->processJoinAccept(joinEvent); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_LORAWAN_NEW_SESSION); } int16_t LoRaWANNode::activateABP(uint8_t initialDr) { @@ -836,26 +935,8 @@ int16_t LoRaWANNode::activateABP(uint8_t initialDr) { return(RADIOLIB_LORAWAN_SESSION_RESTORED); } - // either no valid session was found or user forced a new session, so clear all activity - this->clearSession(); - - // setup the uplink/downlink channels and initial datarate - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannelsDyn(); - } else { - this->setupChannelsFix(this->subBand); - } - // setup all MAC properties to default values - this->activateCommon(initialDr); - - // reset all frame counters - this->fCntUp = 0; - this->aFCntDown = 0; - this->nFCntDown = 0; - this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; - this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; - this->adrFCnt = 0; + this->createSession(RADIOLIB_LORAWAN_MODE_ABP, initialDr); // new session all good, so set active-bit to true this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; @@ -883,28 +964,279 @@ int16_t LoRaWANNode::activateABP(uint8_t initialDr) { return(RADIOLIB_LORAWAN_NEW_SESSION); } +void LoRaWANNode::processCFList(uint8_t* cfList) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processing CFList"); + + uint8_t cOcts[14]; // TODO explain + uint8_t cid; + uint8_t cLen; + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // retrieve number of existing (default) channels + size_t num = 0; + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(!this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + break; + } + num++; + } + + uint8_t freqZero[3] = { 0 }; + + // datarate range for all new channels is equal to the default channels + cOcts[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin; + for(uint8_t i = 0; i < 5; i++, num++) { + // if the frequency fields are all zero, there are no more channels in the CFList + if(memcmp(&cfList[i*3], freqZero, 3) == 0) { + break; + } + cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + (void)LoRaWANNode::getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = num; + memcpy(&cOcts[1], &cfList[i*3], 3); + (void)execMacCommand(cid, cOcts, cLen); + } + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // complete channel mask received, so clear all existing channels + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + + // copy channel mask straight over to LinkAdr MAC command + cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cLen = 14; // special internal ADR length + cOcts[0] = 0xFF; // same datarate and cOcts + memcpy(&cOcts[1], cfList, 12); // copy mask + cOcts[13] = 0; // set NbTrans = 0 -> keep the same + (void)execMacCommand(cid, cOcts, cLen); + } + +} + bool LoRaWANNode::isActivated() { return(this->isActive); } -#if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::uplink(String& str, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* event) { - return(this->uplink(str.c_str(), fPort, isConfirmed, event)); -} -#endif +int16_t LoRaWANNode::isValidUplink(uint8_t* len, uint8_t fPort) { + // check destination fPort + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: { + // MAC FPort only good if internally overruled + if (!this->isMACPayload) { + return(RADIOLIB_ERR_INVALID_PORT); + } + // if this is MAC only payload, continue and reset for next uplink + this->isMACPayload = false; + } break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: { + // all good + } break; + case RADIOLIB_LORAWAN_FPORT_TS009: { + // TS009 FPort only good if overruled during verification testing + if(!this->TS009) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort); + return(RADIOLIB_ERR_INVALID_PORT); + } + } break; + case RADIOLIB_LORAWAN_FPORT_TS011: { + // TS011 FPort only good if overruled during relay exchange + if(!this->TS011) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort); + return(RADIOLIB_ERR_INVALID_PORT); + } + } break; + default: { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort); + } break; + } -int16_t LoRaWANNode::uplink(const char* str, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* event) { - return(this->uplink((uint8_t*)str, strlen(str), fPort, isConfirmed, event)); + // check maximum payload len as defined in band + if(*len > this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]) { + // normally, throw an error if the packet is too long + if(this->TS009 == false) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + // if testing with TS009 Specification Verification Protocol, don't throw error but clip the message + *len = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + } + + return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* event) { - // if not joined, don't do anything - if(!this->isActivated()) { - return(RADIOLIB_ERR_NETWORK_NOT_JOINED); +void LoRaWANNode::adrBackoff() { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // check if we need to do ADR stuff + uint32_t adrLimit = 0x01 << this->adrLimitExp; + uint32_t adrDelay = 0x01 << this->adrDelayExp; + if((this->fCntUp - this->adrFCnt) >= adrLimit) { + this->adrAckReq = true; + } else { + this->adrAckReq = false; + } + // if we hit the Limit + Delay, try one of three, in order: + // set TxPower to max, set DR to min, enable all default channels + if ((this->fCntUp - this->adrFCnt) == (adrLimit + adrDelay)) { + uint8_t adrStage = 1; + while(adrStage != 0) { + switch(adrStage) { + case(1): { + // if the TxPower field has some offset, remove it and switch to maximum power + if(this->txPowerSteps > 0) { + // set the maximum power supported by both the module and the band + state = this->setTxPower(this->txPowerMax); + if(state == RADIOLIB_ERR_NONE) { + this->txPowerSteps = 0; + adrStage = 0; // successfully did some ADR stuff + } + } + if(adrStage == 1) { // if nothing succeeded, proceed to stage 2 + adrStage = 2; + } + } break; + case(2): { + // try to decrease the datarate + if(this->channels[RADIOLIB_LORAWAN_UPLINK].dr > 0) { + if(this->setDatarate(this->channels[RADIOLIB_LORAWAN_UPLINK].dr - 1) == RADIOLIB_ERR_NONE) { + adrStage = 0; // successfully did some ADR stuff + } + } + if(adrStage == 2) { // if nothing succeeded, proceed to stage 3 + adrStage = 3; + } + } break; + case(3): { + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(false); // revert to default frequencies + } else { + // go back to default selected subband + // hopefully it'll help something, but probably not; at least we tried.. + this->selectChannelPlanFix(); + } + this->nbTrans = 1; + adrStage = 0; // nothing else to do, so end the cycle + } break; + } + } + + // we tried something to improve the range, so increase the ADR frame counter by 'ADR delay' + this->adrFCnt += adrDelay; + } +} + +void LoRaWANNode::composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t lenOut, uint8_t fPort, bool isConfirmed) { + // set the packet fields + if(isConfirmed) { + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; + this->confFCntUp = this->fCntUp; + } else { + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; + } + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); + + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; + if(this->adrEnabled) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; + if(adrAckReq) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; + } } - Module* mod = this->phyLayer->getMod(); + // check if we have some MAC commands to append + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= this->fOptsUpLen; + + // if the saved confirm-fCnt is set, set the ACK bit + bool isConfirmingDown = false; + if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + isConfirmingDown = true; + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; + } + + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fCntUp); + + if(this->fOptsUpLen > 0) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload:"); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(this->fOptsUp, this->fOptsUpLen); + + lenOut = RADIOLIB_LORAWAN_FRAME_LEN(lenIn, this->fOptsUpLen); + + if(this->rev == 1) { + // in LoRaWAN v1.1, the FOpts are encrypted using the NwkSEncKey + processAES(this->fOptsUp, this->fOptsUpLen, this->nwkSEncKey, &out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x01, true); + } else { + // in LoRaWAN v1.0.x, the FOpts are unencrypted + memcpy(&out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fOptsUp, this->fOptsUpLen); + } + + } + + // set the fPort + out[RADIOLIB_LORAWAN_FHDR_FPORT_POS(this->fOptsUpLen)] = fPort; + + // select encryption key based on the target fPort + uint8_t* encKey; + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: + encKey = this->nwkSEncKey; + break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: + encKey = this->appSKey; + break; + case RADIOLIB_LORAWAN_FPORT_TS009: + encKey = this->appSKey; + break; + case RADIOLIB_LORAWAN_FPORT_TS011: + encKey = this->nwkSEncKey; + break; + default: + encKey = this->appSKey; + break; + } + + // encrypt the frame payload + processAES(in, lenIn, encKey, &out[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(this->fOptsUpLen)], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x00, true); +} + +void LoRaWANNode::micUplink(uint8_t* inOut, uint8_t lenInOut) { + // create blocks for MIC calculation + uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_UPLINK; + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fCntUp); + block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = lenInOut - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); + + uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); + if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + LoRaWANNode::hton(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntDown); + } + block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].idx; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink (FCntUp = %lu) decoded:", (unsigned long)this->fCntUp); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(inOut, lenInOut); + + // calculate authentication codes + memcpy(inOut, block1, RADIOLIB_AES128_BLOCK_SIZE); + uint32_t micS = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->sNwkSIntKey); + memcpy(inOut, block0, RADIOLIB_AES128_BLOCK_SIZE); + uint32_t micF = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->fNwkSIntKey); + + // check LoRaWAN revision + if(this->rev == 1) { + 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(&inOut[lenInOut - sizeof(uint32_t)], mic); + } else { + LoRaWANNode::hton(&inOut[lenInOut - sizeof(uint32_t)], micF); + } +} + +int16_t LoRaWANNode::transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); + // check if the Rx windows were closed after sending the previous uplink // this FORCES a user to call downlink() after an uplink() if(this->rxDelayEnd < this->rxDelayStart) { @@ -912,304 +1244,101 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t fPort, bool isCon return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); } + RadioLibTime_t tNow = mod->hal->millis(); + // if scheduled uplink time is in the past, reschedule to now + if(this->tUplink < tNow) { + this->tUplink = tNow; + } + // if adhering to dutyCycle and the time since last uplink + interval has not elapsed, return an error - if(this->dutyCycleEnabled && this->rxDelayStart + (RadioLibTime_t)dutyCycleInterval(this->dutyCycle, this->lastToA) > mod->hal->millis()) { - return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); - } - - // check destination fPort - if(fPort > RADIOLIB_LORAWAN_FPORT_RESERVED) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is RFU.", fPort); - return(RADIOLIB_ERR_INVALID_PORT); - } - if(fPort == RADIOLIB_LORAWAN_FPORT_TS009 && this->TS009 == false) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! TS009 was not enabled.", fPort); - } - // fPort 0 is only allowed for MAC-only payloads - if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - if (!this->isMACPayload) { - return(RADIOLIB_ERR_INVALID_PORT); - } - // if this is MAC only payload, continue and reset for next uplink - this->isMACPayload = false; - } - - int16_t state = RADIOLIB_ERR_UNKNOWN; - - // check if there are some MAC commands to piggyback (only when piggybacking onto a application-frame) - uint8_t fOptsLen = 0; - if(this->commandsUp.numCommands > 0 && fPort != RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - // there are, assume the maximum possible FOpts len for buffer allocation - fOptsLen = this->commandsUp.len; - } - - // check maximum payload len as defined in phy - if(len > this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]) { - // normally, throw an error if the packet is too long - if(this->TS009 == false) { - return(RADIOLIB_ERR_PACKET_TOO_LONG); - } - // if testing with TS009 Specification Verification Protocol, don't throw error but clip the message - len = this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]; - } - - bool adrAckReq = false; - if(this->adrEnabled) { - // check if we need to do ADR stuff - uint32_t adrLimit = 0x01 << this->adrLimitExp; - uint32_t adrDelay = 0x01 << this->adrDelayExp; - if((this->fCntUp - this->adrFCnt) >= adrLimit) { - adrAckReq = true; - } - // if we hit the Limit + Delay, try one of three, in order: - // set TxPower to max, set DR to min, enable all default channels - if ((this->fCntUp - this->adrFCnt) == (adrLimit + adrDelay)) { - uint8_t adrStage = 1; - while(adrStage != 0) { - switch(adrStage) { - case(1): { - // if the TxPower field has some offset, remove it and switch to maximum power - if(this->txPowerSteps > 0) { - // set the maximum power supported by both the module and the band - state = this->setTxPower(this->txPowerMax); - if(state == RADIOLIB_ERR_NONE) { - this->txPowerSteps = 0; - adrStage = 0; // successfully did some ADR stuff - } - } - if(adrStage == 1) { // if nothing succeeded, proceed to stage 2 - adrStage = 2; - } - } - break; - case(2): { - // try to decrease the datarate - if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] > 0) { - if(this->setDatarate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] - 1) == RADIOLIB_ERR_NONE) { - adrStage = 0; // successfully did some ADR stuff - } - } - if(adrStage == 2) { // if nothing succeeded, proceed to stage 3 - adrStage = 3; - } - } - break; - case(3): { - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannelsDyn(false); // revert to default frequencies - } else { - // go back to default selected subband - // hopefully it'll help something, but probably not; at least we tried.. - this->setupChannelsFix(this->subBand); - } - adrStage = 0; // nothing else to do, so end the cycle - } - break; - } - } - - // we tried something to improve the range, so increase the ADR frame counter by 'ADR delay' - this->adrFCnt += adrDelay; + if(this->dutyCycleEnabled) { + if(this->rxDelayStart + (RadioLibTime_t)dutyCycleInterval(this->dutyCycle, this->lastToA) > this->tUplink) { + return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); } } // set the physical layer configuration for uplink - this->selectChannels(); - state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); + state = this->setPhyProperties(chnl, + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); RADIOLIB_ASSERT(state); - - // if dwell time is imposed, calculated expected time on air and cancel if exceeds - if(this->dwellTimeEnabledUp && this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_FRAME_LEN(len, fOptsLen) - 16)/1000 > this->dwellTimeUp) { - return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); - } - - // build the uplink message - // the first 16 bytes are reserved for MIC calculation blocks - size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, fOptsLen); - #if RADIOLIB_STATIC_ONLY - uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen]; - #endif - // set the packet fields - if(isConfirmed) { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; - this->confFCntUp = this->fCntUp; - } else { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; - } - uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); - - // length of fOpts will be added later - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; - if(this->adrEnabled) { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; - if(adrAckReq) { - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; - } + // if requested, wait until transmitting uplink + if(this->tUplink > tNow) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %d ms", this->tUplink - tNow); + tNow = mod->hal->millis(); // recalculate for maximum precision + mod->hal->delay(this->tUplink - tNow); // wait } - // if the saved confirm-fCnt is set, set the ACK bit - bool isConfirmingDown = false; - if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { - isConfirmingDown = true; - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; - } - - LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fCntUp); - - // check if we have some MAC commands to append - if(fOptsLen > 0) { - // assume maximum possible buffer size - uint8_t fOptsBuff[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN]; - - this->macQueueToBuff(&(this->commandsUp), fOptsBuff); - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsBuff, fOptsLen); - - uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, fOptsLen); - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= fOptsLen; - - if(this->rev == 1) { - // in LoRaWAN v1.1, the FOpts are encrypted using the NwkSEncKey - processAES(fOptsBuff, fOptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fCntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); - } else { - // in LoRaWAN v1.0.x, the FOpts are unencrypted - memcpy(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fOptsBuff, fOptsLen); - } - - } - - // set the fPort - uplinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsLen)] = fPort; - - // select encryption key based on the target fPort - uint8_t* encKey = this->appSKey; - if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - encKey = this->nwkSEncKey; - } - - // encrypt the frame payload - processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsLen)], this->fCntUp, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); - - // perform CSMA if enabled (do it now already, because MIC calculation depends on this) - if (enableCSMA) { - performCSMA(); - } - - // create blocks for MIC calculation - uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; - block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fCntUp); - block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = uplinkMsgLen - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); - - uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; - memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); - if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { - LoRaWANNode::hton(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntDown); - } - block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; - block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx; - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink (FCntUp = %lu) decoded:", (unsigned long)this->fCntUp); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(uplinkMsg, uplinkMsgLen); - - // calculate authentication codes - memcpy(uplinkMsg, block1, RADIOLIB_AES128_BLOCK_SIZE); - uint32_t micS = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->sNwkSIntKey); - memcpy(uplinkMsg, block0, RADIOLIB_AES128_BLOCK_SIZE); - uint32_t micF = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->fNwkSIntKey); - - // check LoRaWAN revision - if(this->rev == 1) { - 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); - } - - // send it (without the MIC calculation blocks) - state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS); + state = this->phyLayer->transmit(in, len); // set the timestamp so that we can measure when to start receiving this->rxDelayStart = mod->hal->millis(); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink sent <-- Rx Delay start"); - // calculate Time on Air of this uplink in milliseconds - this->lastToA = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000; + // increase Time on Air of the uplink sequence + this->lastToA += this->phyLayer->getTimeOnAir(len - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000; - #if !RADIOLIB_STATIC_ONLY - delete[] uplinkMsg; - #endif - RADIOLIB_ASSERT(state); - - // the downlink confirmation was acknowledged, so clear the counter value - this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; - - // pass the extra info if requested - if(event) { - event->dir = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; - event->confirmed = isConfirmed; - event->confirming = isConfirmingDown; - event->datarate = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; - event->freq = currentChannels[event->dir].freq; - event->power = this->txPowerMax - this->txPowerSteps * 2; - event->fCnt = this->fCntUp; - event->fPort = fPort; - } - - // increase frame counter by one for the next uplink - this->fCntUp += 1; - - return(RADIOLIB_ERR_NONE); + return(state); } -int16_t LoRaWANNode::downlinkCommon() { +// flag to indicate whether there was some action during Rx mode (timeout or downlink) +static volatile bool downlinkAction = false; + +// interrupt service routine to handle downlinks automatically +#if defined(ESP8266) || defined(ESP32) + IRAM_ATTR +#endif +static void LoRaWANNodeOnDownlinkAction(void) { + downlinkAction = true; +} + +int16_t LoRaWANNode::receiveCommon(uint8_t dir, LoRaWANChannel_t* dlChannels, RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference) { Module* mod = this->phyLayer->getMod(); + int16_t state = RADIOLIB_ERR_UNKNOWN; + // 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 RadioLibTime_t now = mod->hal->millis(); // fix the current timestamp to prevent negative delays - if(now > this->rxDelayStart + this->rxDelays[0] - this->scanGuard) { - // if between start of Rx1 and end of Rx2, wait until Rx2 closes - if(now < this->rxDelayStart + this->rxDelays[1]) { - mod->hal->delay(this->rxDelays[1] + this->rxDelayStart - now); + if(now > tReference + dlDelays[1] - this->scanGuard) { + // if function was called while Rx windows are in progress, + // wait until last window closes to prevent very bad stuff + if(now < tReference + dlDelays[numWindows]) { + mod->hal->delay(dlDelays[numWindows] + tReference - 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); } - // set the physical layer configuration for downlink - int16_t state = this->setPhyProperties(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); - RADIOLIB_ASSERT(state); - // create the masks that are required for receiving downlinks RadioLibIrqFlags_t irqFlags = (1UL << RADIOLIB_IRQ_RX_DONE) | (1UL << RADIOLIB_IRQ_TIMEOUT); RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK; this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction); + RadioLibTime_t tOpen = 0; int16_t timedOut = 0; - // perform listening in the two Rx windows - for(uint8_t i = 0; i < 2; i++) { + // listen during the specified windows + for(uint8_t i = 1; i <= numWindows; i++) { downlinkAction = false; + // set the physical layer configuration for downlink + this->phyLayer->standby(); + state = this->setPhyProperties(&dlChannels[i], dir, this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + // calculate the Rx timeout RadioLibTime_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*this->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(); + RadioLibTime_t waitLen = tReference + dlDelays[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]; + if(waitLen > dlDelays[i]) { + waitLen = dlDelays[i]; } // the waiting duration is shortened a bit to cover any possible timing errors if(waitLen > this->scanGuard) { @@ -1219,71 +1348,57 @@ int16_t LoRaWANNode::downlinkCommon() { // open Rx window by starting receive with specified timeout state = this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0); + tOpen = mod->hal->millis(); RADIOLIB_ASSERT(state); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", i+1, (int)(timeoutHost / 1000 + scanGuard / 2)); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", i, (int)(timeoutHost / 1000 + scanGuard / 2)); // wait for the timeout to complete (and a small additional delay) mod->hal->delay(timeoutHost / 1000 + this->scanGuard / 2); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", i+1); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", i); - // check if the IRQ bit for Rx Timeout is set + // if the IRQ bit for Rx Timeout is not set, something is received, so stop the windows timedOut = this->phyLayer->checkIrq(RADIOLIB_IRQ_TIMEOUT); if(timedOut == RADIOLIB_ERR_UNSUPPORTED) { return(timedOut); } if(!timedOut) { break; - - } else if(i == 0) { - // nothing in the first window, configure for the second - this->phyLayer->standby(); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %6.3f MHz", 'D', this->rx2.freq); - state = this->phyLayer->setFrequency(this->rx2.freq); - RADIOLIB_ASSERT(state); - - DataRate_t dataRate; - state = findDataRate(this->rx2.drMax, &dataRate); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setDataRate(dataRate); - RADIOLIB_ASSERT(state); } - } // Rx windows are now closed this->rxDelayEnd = mod->hal->millis(); // if we got here due to a timeout, stop ongoing activities if(timedOut) { - this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { - this->phyLayer->invertIQ(false); - } - + this->phyLayer->standby(); return(RADIOLIB_LORAWAN_NO_DOWNLINK); } + // get the maximum allowed Time-on-Air of a packet given the current datarate + RadioLibTime_t tMax = this->phyLayer->getTimeOnAir(this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]); + bool downlinkComplete = true; + // wait for the DIO to fire indicating a downlink is received now = mod->hal->millis(); - bool downlinkComplete = true; while(!downlinkAction) { mod->hal->yield(); - // this should never happen, but if it does this would be an infinite loop - // TODO scale this value using expected ToA of maximum allowed payload length - if(mod->hal->millis() - now > 3000UL) { + // stay in Rx mode for the maximum allowed Time-on-Air plus small grace period + if(mod->hal->millis() - tOpen > tMax + scanGuard) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink missing!"); downlinkComplete = false; break; } } - // we have a message, clear actions, go to standby and reset the IQ inversion - this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? - this->phyLayer->clearPacketReceivedAction(); - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { - state = this->phyLayer->invertIQ(false); - RADIOLIB_ASSERT(state); + // update time of downlink reception + if(downlinkComplete) { + this->tDownlink = mod->hal->millis(); } + // we have a message, clear actions, go to standby + this->phyLayer->clearPacketReceivedAction(); + this->phyLayer->standby(); + if(!downlinkComplete) { state = RADIOLIB_LORAWAN_NO_DOWNLINK; } @@ -1291,47 +1406,12 @@ int16_t LoRaWANNode::downlinkCommon() { return(state); } -#if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::downlink(String& str, LoRaWANEvent_t* event) { +int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) { int16_t state = RADIOLIB_ERR_UNKNOWN; - - // 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, event); - 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(LoRaWANEvent_t* event) { - int16_t state = RADIOLIB_ERR_UNKNOWN; - - // 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, event); - - return(state); -} - -int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) { - // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received - int16_t state = downlinkCommon(); - RADIOLIB_ASSERT(state); + + // set user-data length to 0 to prevent undefined behaviour in case of bad use + // if there is user-data, this will be handled at the appropriate place + *len = 0; // get the packet length size_t downlinkMsgLen = this->phyLayer->getPacketLength(); @@ -1376,44 +1456,15 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } - // calculate length of FOpts and payload - uint8_t fOptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; + // calculate length of piggy-backed FOpts + uint8_t fOptsPbLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; - // check if the ACK bit is set, indicating this frame acknowledges the previous uplink - bool isConfirmingUp = false; - if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK)) { - isConfirmingUp = true; - } - - // total - MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOpts - MIC(4) - // potentially also an FPort, but we'll find out soon enough - uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsLen - 4; - - // get the frame counter - uint16_t fCnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); - - // set the MIC calculation blocks - memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); - downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; - // if this downlink is confirming an uplink, the MIC was generated with the least-significant 16 bits of that fCntUp - if(isConfirmingUp && (this->rev == 1)) { - LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntUp); - } - downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK; - LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); - LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt16); - downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t); - - // check the MIC - if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - return(RADIOLIB_ERR_CRC_MISMATCH); - } + // MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOptsPb - Payload - MIC(4) + // potentially also an FPort, will find out next + uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsPbLen - 4; // in LoRaWAN v1.1, a frame is a Network frame if there is no Application payload - // i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload (FPort = 0) + // i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload uint8_t fPort = RADIOLIB_LORAWAN_FPORT_MAC_COMMAND; bool isAppDownlink = false; if(this->rev == 0) { @@ -1421,32 +1472,52 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) } if(payLen > 0) { payLen -= 1; // subtract one as fPort is set - fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsLen)]; + fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsPbLen)]; + // check if fPort value is actually allowed - if(fPort > RADIOLIB_LORAWAN_FPORT_RESERVED) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Received downlink at FPort %d - rejected! This FPort is RFU!", fPort); - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - return(RADIOLIB_ERR_INVALID_PORT); - } - if(fPort == RADIOLIB_LORAWAN_FPORT_TS009 && this->TS009 == false) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Received downlink at FPort %d - rejected! TS009 was not enabled.", fPort); - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - return(RADIOLIB_ERR_INVALID_PORT); + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: { + // payload consists of all MAC commands (or is empty) + } break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: { + // payload is user-defined (or empty) - may carry piggybacked MAC commands + isAppDownlink = true; + } break; + case RADIOLIB_LORAWAN_FPORT_TS009: { + // TS009 FPort only good if overruled during verification testing + if(!this->TS009) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + isAppDownlink = true; + } break; + case RADIOLIB_LORAWAN_FPORT_TS011: { + // TS011 FPort only good if overruled during relay exchange + if(!this->TS011) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + isAppDownlink = true; + } break; + default: { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is reserved.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } break; } - if(fPort > RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - isAppDownlink = true; - } else { - fOptsLen = payLen; - } } - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %d) encoded:", isAppDownlink ? "A" : "N", fCnt16); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); + + // get the frame counter + uint16_t fCnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); // check the fCntDown value (Network or Application) uint32_t fCntDownPrev = 0; @@ -1475,6 +1546,33 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) fCnt32 |= ((uint32_t)msb << 16); // add back the MSB part } } + + // check if the ACK bit is set, indicating this frame acknowledges the previous uplink + bool isConfirmingUp = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK)) { + isConfirmingUp = true; + } + + // set the MIC calculation blocks + memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); + downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + // if this downlink is confirming an uplink, the MIC was generated with the least-significant 16 bits of that fCntUp + // (LoRaWAN v1.1 only) + if(isConfirmingUp && (this->rev == 1)) { + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntUp); + } + downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_DOWNLINK; + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt32); + downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t); + + // check the MIC + if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_CRC_MISMATCH); + } // save current fCnt to respective frame counter if (isAppDownlink) { @@ -1483,196 +1581,1069 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) this->nFCntDown = fCnt32; } + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %d) encoded:", + isAppDownlink ? "A" : "N", isAppDownlink ? this->aFCntDown : this->nFCntDown); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); + // if this is a confirmed frame, save the downlink number (only app frames can be confirmed) bool isConfirmedDown = false; if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { this->confFCntDown = this->aFCntDown; isConfirmedDown = true; } + + // a downlink was received, so reset the ADR counter to the last uplink's fCnt + this->adrFCnt = this->getFCntUp(); + // if this downlink is on FPort 0, the FOptsLen is the length of the payload + // in any other case, the payload (length) is user accessible + uint8_t fOptsLen = fOptsPbLen; + if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && payLen > 0) { + fOptsLen = payLen; + } else { + *len = payLen; + } + + #if !RADIOLIB_STATIC_ONLY + uint8_t* fOpts = new uint8_t[fOptsLen]; + #else + uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // figure out if the payload should end up in user data or internal FOpts buffer + uint8_t* dest; + if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { + dest = fOpts; + } else { + dest = data; + } + + // figure out which key to use to decrypt the payload + uint8_t* encKey; + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: + encKey = this->nwkSEncKey; + break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: + encKey = this->appSKey; + break; + case RADIOLIB_LORAWAN_FPORT_TS009: + encKey = this->appSKey; + break; + case RADIOLIB_LORAWAN_FPORT_TS011: + encKey = this->nwkSEncKey; + break; + default: + encKey = this->appSKey; + break; + } + + // decrypt the frame payload + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsPbLen)], payLen, encKey, dest, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true); + + // decrypt any piggy-backed FOpts + if(fOptsPbLen > 0) { + // the decryption depends on the LoRaWAN version + if(this->rev == 1) { + // in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey + uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, ctrId, true); + } else { + // in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted + memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen); + } + } + + // clear the previous MAC commands, if any + memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + + // process FOpts (if there are any) + uint8_t cid; + uint8_t fLen; + uint8_t* mPtr = fOpts; + uint8_t procLen = 0; + + #if !RADIOLIB_STATIC_ONLY + uint8_t* fOptsRe = new uint8_t[250]; + #else + uint8_t fOptsRe[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + uint8_t fOptsReLen = 0; + + // indication whether LinkAdr MAC command has been processed + bool mAdr = false; + + while(procLen < fOptsLen) { + cid = *mPtr; // MAC id is the first byte + state = LoRaWANNode::getMacLen(cid, &fLen, RADIOLIB_LORAWAN_DOWNLINK, true); + RADIOLIB_ASSERT(state); + uint8_t fLenRe = 0; + state = LoRaWANNode::getMacLen(cid, &fLenRe, RADIOLIB_LORAWAN_UPLINK, true); + RADIOLIB_ASSERT(state); + + if(procLen + fLen > fOptsLen) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Incomplete MAC command %02x (%d bytes, expected %d)", cid, fOptsLen, fLen); + return(RADIOLIB_ERR_INVALID_CID); + } + + bool reply = false; + + // if this is a LinkAdr MAC command, pre-process contiguous commands into one atomic block + if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) { + // if there was any LinkAdr command before, set NACK and continue without processing + if(mAdr) { + reply = true; + fOptsRe[fOptsReLen + 1] = 0x00; + + // if this is the first LinkAdr command, do some special treatment: + } else { + mAdr = true; + uint8_t fAdrLen = 5; + uint8_t mAdrOpt[14] = { 0 }; + + // retrieve all contiguous LinkAdr commands + while(procLen + fLen + fAdrLen < fOptsLen + 1 && *(mPtr + fLen) == RADIOLIB_LORAWAN_MAC_LINK_ADR) { + fLen += 5; // ADR command is 5 bytes + fLenRe += 2; // ADR response is 2 bytes + } + + // pre-process them into a single complete channel mask (stored in mAdrOpt) + LoRaWANNode::preprocessMacLinkAdr(mPtr, fLen, mAdrOpt); + + // execute like a normal MAC command (but pointing to mAdrOpt instead) + reply = this->execMacCommand(cid, mAdrOpt, 14, &fOptsRe[fOptsReLen + 1]); + + // in LoRaWAN v1.0.x, all ACK bytes should have equal status - fix in post-processing + if(this->rev == 0) { + LoRaWANNode::postprocessMacLinkAdr(&fOptsRe[fOptsReLen], fLen); + + // in LoRaWAN v1.1, just provide one ACK, so no post-processing but cut off reply length + } else { + fLenRe = 2; + } + } + + // MAC command other than LinkAdr, just process the payload + } else { + reply = this->execMacCommand(cid, mPtr + 1, fLen - 1, &fOptsRe[fOptsReLen + 1]); + } + + if(reply) { + fOptsRe[fOptsReLen] = cid; + fOptsReLen += fLenRe; + } + + procLen += fLen; + mPtr += fLen; + } + + // remove all MAC commands except those whose payload can be requested by the user + // (which are LinkCheck and DeviceTime) + if(fOptsLen > 0) { + LoRaWANNode::clearMacCommands(fOpts, &fOptsLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(this->fOptsDown, fOpts, fOptsLen); + } + this->fOptsDownLen = fOptsLen; + + // if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink + if(fOptsReLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC-only payload (%d bytes):", fOptsReLen); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsRe, fOptsReLen); + + this->isMACPayload = true; + // temporarily lift dutyCycle restrictions to allow immediate MAC response + bool prevDC = this->dutyCycleEnabled; + this->dutyCycleEnabled = false; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. "); + + this->sendReceive(fOptsRe, fOptsReLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); + + this->dutyCycleEnabled = prevDC; + + } else { // fOptsReLen <= 15 + memcpy(this->fOptsUp, fOptsRe, fOptsReLen); + this->fOptsUpLen = fOptsReLen; + } + // pass the extra info if requested if(event) { - event->dir = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK; + event->dir = RADIOLIB_LORAWAN_DOWNLINK; event->confirmed = isConfirmedDown; event->confirming = isConfirmingUp; - event->datarate = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]; - event->freq = currentChannels[event->dir].freq; + event->datarate = this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr; + event->freq = channels[event->dir].freq; event->power = this->txPowerMax - this->txPowerSteps * 2; event->fCnt = isAppDownlink ? this->aFCntDown : this->nFCntDown; event->fPort = fPort; } - // clear the previous MAC commands, if any - memset(&(this->commandsDown), 0, sizeof(LoRaWANMacCommandQueue_t)); - - // process FOpts (if there are any) - if(fOptsLen > 0) { - // there are some Fopts, decrypt them - #if !RADIOLIB_STATIC_ONLY - uint8_t* fOpts = new uint8_t[RADIOLIB_MAX(RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK, (int)fOptsLen)]; - #else - uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE]; - #endif - - // it COULD be the case that the assumed FCnt rollover is incorrect, in which case the following MAC decryption fails... - - if(payLen > 0 && fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - // if the MAC payload is in the payload, process AES is if it were a normal payload but using the NwkSEncKey instead - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], (size_t)fOptsLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); - } else { - // if the MAC payload is piggybacked in the FHDR fields, the decryption depends on the LoRaWAN version. - if(this->rev == 1) { - // in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey - uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, ctrId, true); - } else { - // in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted - memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsLen); - } - } - - // parse the fOpts into the MAC queue - this->macBufftoQueue(&(this->commandsDown), fOpts, fOptsLen); - - // process the MAC queue commands (any response is modified in-place) - int16_t i = 0; - for(; i < this->commandsDown.numCommands; i++) { - bool sendUp = execMacCommand(&(this->commandsDown.commands[i])); - if(sendUp) { - pushMacCommand(&(this->commandsDown.commands[i]), &(this->commandsUp)); - } - } - - // pop the commands from back to front - for (; i >= 0; i--) { - if(this->commandsDown.commands[i].repeat > 0) { - this->commandsDown.commands[i].repeat--; - } else { - deleteMacCommand(this->commandsDown.commands[i].cid, &(this->commandsDown)); - } - } - - #if !RADIOLIB_STATIC_ONLY - delete[] fOpts; - #endif - - // if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink - if(this->commandsUp.len > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { - size_t fOptsBufSize = this->commandsUp.len; - #if RADIOLIB_STATIC_ONLY - uint8_t fOptsBuff[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* fOptsBuff = new uint8_t[fOptsBufSize]; - #endif - - this->macQueueToBuff(&(this->commandsUp), fOptsBuff); - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload (%d commands):", this->commandsUp.numCommands); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsBuff, fOptsBufSize); - - this->isMACPayload = true; - // temporarily lift dutyCycle restrictions to allow immediate MAC response - bool prevDC = this->dutyCycleEnabled; - this->dutyCycleEnabled = false; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. "); - state = this->uplink(fOptsBuff, fOptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN(" .. state: %d", state); - this->dutyCycleEnabled = prevDC; - #if !RADIOLIB_STATIC_ONLY - delete[] fOptsBuff; - #endif - RADIOLIB_ASSERT(state); - - #if RADIOLIB_STATIC_ONLY - uint8_t strDown[RADIOLIB_STATIC_ARRAY_SIZE]; - #else - uint8_t* strDown = new uint8_t[this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]]; - #endif - size_t lenDown = 0; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Receiving after MAC-only uplink .. "); - state = this->downlink(strDown, &lenDown); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN(" .. state: %d", state); - #if !RADIOLIB_STATIC_ONLY - delete[] strDown; - #endif - RADIOLIB_ASSERT(state); - } - - } - - // a downlink was received, so reset the ADR counter to the last uplink's fCnt - this->adrFCnt = this->getFCntUp(); - - // if MAC-only payload, return now - if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - // no payload - *len = 0; - #if !RADIOLIB_STATIC_ONLY - delete[] downlinkMsg; - #endif - - return(RADIOLIB_ERR_NONE); - } - - // process Application payload - *len = payLen; - - // TODO it COULD be the case that the assumed rollover is incorrect, then figure out a way to catch this and retry with just fCnt16 - processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsLen)], payLen, this->appSKey, data, fCnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); - #if !RADIOLIB_STATIC_ONLY + delete[] fOpts; + delete[] fOptsRe; delete[] downlinkMsg; #endif return(RADIOLIB_ERR_NONE); } -#if defined(RADIOLIB_BUILD_ARDUINO) -int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(strUp, fPort, isConfirmed, eventUp); - RADIOLIB_ASSERT(state); - - // wait for the downlink - state = this->downlink(strDown, eventDown); - return(state); +bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn) { + uint8_t buff[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN]; + return(this->execMacCommand(cid, optIn, lenIn, buff)); } -#endif -int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(dataUp, lenUp, fPort, isConfirmed, eventUp); - RADIOLIB_ASSERT(state); +bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[MAC] 0x%02x", cid); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(optIn, lenIn); - // wait for the downlink - state = this->downlink(eventDown); + if(cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) { + // TODO call user-provided callback for proprietary MAC commands? + return(false); + } + + switch(cid) { + case(RADIOLIB_LORAWAN_MAC_RESET): { + // get the server version + uint8_t srvVersion = optIn[0]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ResetConf: server version 1.%d", srvVersion); + if(srvVersion == this->rev) { + // valid server version, stop sending the ResetInd MAC command + LoRaWANNode::deleteMacCommand(RADIOLIB_LORAWAN_MAC_RESET, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + } + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]"); + + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_LINK_ADR): { + // get the ADR configuration + uint8_t macDrUp = (optIn[0] & 0xF0) >> 4; + uint8_t macTxSteps = optIn[0] & 0x0F; + uint64_t macChMaskGrp0123 = 0; + uint32_t macChMaskGrp45 = 0; + // uint8_t macChMaskCntl = 0; + uint8_t macNbTrans = 0; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrReq: dataRate = %d, txSteps = %d", macDrUp, macTxSteps); + + uint8_t chMaskAck = 0; + uint8_t drAck = 0; + uint8_t pwrAck = 0; + + // first, get current configuration + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + uint16_t chMaskActive = 0; + uint8_t numCh = this->getAvailableChannels(&chMaskActive); + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + + // only apply channel mask if present (internal Dr/Tx commands do not set channel mask) + if(lenIn > 1) { + macChMaskGrp0123 = LoRaWANNode::ntoh(&optIn[1]); + macChMaskGrp45 = LoRaWANNode::ntoh(&optIn[9]); + // apply requested channel mask and enable all of them for testing datarate + chMaskAck = this->applyChannelMask(macChMaskGrp0123, macChMaskGrp45); + } else { + chMaskAck = true; + } + + this->setAvailableChannels(0xFFFF); + + int16_t state; + + // try to apply the datarate configuration + // if value is set to 'keep current values', retrieve current value + if(macDrUp == 0x0F) { + macDrUp = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + } + + if (this->band->dataRates[macDrUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + // check if the module supports this data rate + DataRate_t dr; + state = this->findDataRate(macDrUp, &dr); + + // if datarate in hardware all good, set datarate for now + // and check if there are any available Tx channels for this datarate + if(state == RADIOLIB_ERR_NONE) { + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = macDrUp; + + // only if we have available Tx channels, we set an Ack + if(this->getAvailableChannels(NULL) > 0) { + drAck = 1; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: no channels available for datarate %d", macDrUp); + } + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: hardware failure configurating datarate %d, code %d", macDrUp, state); + } + + } + + // try to apply the power configuration + // if value is set to 'keep current values', retrieve current value + if(macTxSteps == 0x0F) { + macTxSteps = this->txPowerSteps; + } + + int8_t power = this->txPowerMax - 2*macTxSteps; + 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->txPowerSteps = macTxSteps; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure Tx power %d, code %d!", power, state); + } + + // set ACK bits + optOut[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0); + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrAns: %02x", optOut[0]); + + // if ACK not completely successful, revert and stop + if(optOut[0] != 0x07) { + this->applyChannelMask(chMaskGrp0123, chMaskGrp45); + this->setAvailableChannels(chMaskActive); + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr; + // Tx power was not modified + return(true); + } + + // ACK successful, so apply and save + this->txPowerSteps = macTxSteps; + if(lenIn > 1) { + macNbTrans = optIn[13] & 0x0F; + if(macNbTrans) { // if there is a value for NbTrans, set this value + this->nbTrans = macNbTrans; + } + } + + // restore original active channels + this->setAvailableChannels(chMaskActive); + + // save to the single ADR MAC location + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DUTY_CYCLE): { + uint8_t maxDutyCycle = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle); + if(maxDutyCycle == 0) { + this->dutyCycle = this->band->dutyCycle; + } else { + this->dutyCycle = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000 / (RadioLibTime_t)(1UL << maxDutyCycle); + } + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP): { + // get the configuration + uint8_t macRx1DrOffset = (optIn[0] & 0x70) >> 4; + uint8_t macRx2Dr = optIn[0] & 0x0F; + uint32_t macRx2Freq = LoRaWANNode::ntoh(&optIn[1], 3); + + uint8_t rx1DrOsAck = 0; + uint8_t rx2DrAck = 0; + uint8_t rx2FreqAck = 0; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupReq: Rx1DrOffset = %d, rx2DataRate = %d, freq = %7.3f", + macRx1DrOffset, macRx2Dr, macRx2Freq / 10000.0); + + // check the requested configuration + uint8_t uplinkDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + if(this->band->rx1DrTable[uplinkDr][macRx1DrOffset] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + rx1DrOsAck = 1; + } + if(this->band->dataRates[macRx2Dr] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + rx2DrAck = 1; + } + if(this->phyLayer->setFrequency(macRx2Freq / 10000.0) == RADIOLIB_ERR_NONE) { + rx2FreqAck = 1; + } + + optOut[0] = (rx1DrOsAck << 2) | (rx2DrAck << 1) | (rx2FreqAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x07) { + return(true); + } + + // passed ACK, so apply configuration + this->rx1DrOffset = macRx1DrOffset; + this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = macRx2Dr; + this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq = macRx2Freq; + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DEV_STATUS): { + // set the uplink reply + optOut[0] = this->battLevel; + int8_t snr = this->phyLayer->getSNR(); + optOut[1] = snr & 0x3F; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", optOut[0], optOut[1]); + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_NEW_CHANNEL): { + // only implemented on dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(false); + } + + // get the configuration + uint8_t macChIndex = optIn[0]; + uint32_t macFreq = LoRaWANNode::ntoh(&optIn[1], 3); + uint8_t macDrMax = (optIn[4] & 0xF0) >> 4; + uint8_t macDrMin = optIn[4] & 0x0F; + + uint8_t newChAck = 0; + uint8_t freqAck = 0; + + // on LoRaWAN v1.1, the default channels may be modified - not on v1.0.x. + // in that case, only allow non-default channels to be modified + // there are at most three default channels, so either check for >2 or else if index is used + if(this->rev == 1 || macChIndex > 2 || this->band->txFreqs[macChIndex].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + newChAck = 1; + } + + // check if the frequency is possible + if(this->phyLayer->setFrequency((float)macFreq / 10000.0) == RADIOLIB_ERR_NONE) { + freqAck = 1; + + // otherwise, if frequency is 0, disable the channel which is also a valid option + } else if(macFreq == 0) { + freqAck = 1; + } + + // set ACK bits + optOut[0] = (newChAck << 1) | (freqAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x03) { + return(true); + } + + // ACK successful, so apply and save + if(macFreq > 0) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled = true; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx = macChIndex; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq = macFreq; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin = macDrMin; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax = macDrMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].available = true; + // downlink channel is identical to uplink channel + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex]; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE; + + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax, + + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].idx, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].enabled, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMin, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMax + ); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + macChIndex * lenIn, optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DL_CHANNEL): { + // only implemented on dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(false); + } + + // get the configuration + uint8_t macChIndex = optIn[0]; + uint32_t macFreq = LoRaWANNode::ntoh(&optIn[1], 3); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelReq: index = %d, freq = %7.3f MHz", macChIndex, macFreq / 10000.0); + uint8_t freqDlAck = 0; + uint8_t freqUlAck = 0; + + // check if the frequency is possible + if(this->phyLayer->setFrequency(macFreq / 10000.0) == RADIOLIB_ERR_NONE) { + freqDlAck = 1; + } + + // check if the corresponding uplink frequency is actually set + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq > 0) { + freqUlAck = 1; + } + + // set ACK bits + optOut[0] = (freqUlAck << 1) | (freqDlAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x03) { + return(true); + } + + // ACK successful, so apply and save + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq = macFreq; + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS] + macChIndex * lenIn, optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): { + // get the configuration + uint8_t delay = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay); + + // apply the configuration + if(delay == 0) { + delay = 1; + } + this->rxDelays[1] = delay * 1000; // Rx1 delay + this->rxDelays[2] = this->rxDelays[1] + 1000; // Rx2 delay + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP): { + // TxParamSetupReq is only supported on a subset of bands + // in other bands, silently ignore without response + if(!this->band->txParamSupported) { + return(false); + } + uint8_t dlDwell = (optIn[0] & 0x20) >> 5; + uint8_t ulDwell = (optIn[0] & 0x10) >> 4; + uint8_t maxEirpRaw = optIn[0] & 0x0F; + + // who the f came up with this ... + const uint8_t eirpEncoding[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 }; + this->txPowerMax = eirpEncoding[maxEirpRaw]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("TxParamSetupReq: dlDwell = %d, ulDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, eirpEncoding[maxEirpRaw]); + + this->dwellTimeEnabledUp = ulDwell ? true : false; + this->dwellTimeUp = ulDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; + + this->dwellTimeEnabledDn = dlDwell ? true : false; + this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_REKEY): { + // get the server version + uint8_t srvVersion = optIn[0]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RekeyConf: server version = 1.%d", srvVersion); + if((srvVersion > 0) && (srvVersion <= this->rev)) { + // valid server version, stop sending the ReKey MAC command + LoRaWANNode::deleteMacCommand(RADIOLIB_LORAWAN_MAC_REKEY, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + } + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): { + this->adrLimitExp = (optIn[0] & 0xF0) >> 4; + this->adrDelayExp = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DEVICE_TIME): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DeviceTimeAns: [user]"); + + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): { + // TODO implement this + uint16_t rejoinReq = LoRaWANNode::ntoh(optIn); + uint8_t period = (rejoinReq & 0x3800) >> 11; + uint8_t maxRetries = (rejoinReq & 0x0700) >> 8; + uint8_t rejoinType = (rejoinReq & 0x0070) >> 4; + uint8_t dr = rejoinReq & 0x000F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); + (void)period; + (void)maxRetries; + (void)rejoinType; + (void)dr; + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP): { + // TODO implement this + uint8_t maxTime = (optIn[0] & 0xF0) >> 4; + uint8_t maxCount = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], optIn, lenIn); + + lenIn = 0; + optIn[0] = (1 << 1) | 1; + + (void)maxTime; + (void)maxCount; + return(true); + } break; + + default: { + // derived classes may implement additional MAC commands + return(derivedMacHandler(cid, optIn, lenIn, optOut)); + } + } + + return(false); +} + +bool LoRaWANNode::derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) { + return(false); +} + +void LoRaWANNode::preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt) { + uint8_t fLen = 5; // single ADR command is 5 bytes + uint8_t numOpts = cLen / fLen; + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + + // set Dr/Tx field from last MAC command + mAdrOpt[0] = mPtr[cLen - fLen + 1]; + + // set NbTrans partial field from last MAC command + mAdrOpt[13] = mPtr[cLen - fLen + 4] & 0x0F; + + uint8_t opt = 0; + while(opt < numOpts) { + uint8_t chMaskCntl = (mPtr[opt * fLen + 4] & 0x70) >> 4; + uint16_t chMask = LoRaWANNode::ntoh(&mPtr[opt * fLen + 2]); + switch(chMaskCntl) { + case 0 ... 3: + chMaskGrp0123 |= (uint64_t)chMask << (16 * chMaskCntl); + break; + case 4: + chMaskGrp45 |= (uint32_t)chMask; + break; + case 5: + // for CN500, this is just a normal channel mask + // for all other bands, the first 10 bits enable banks of 8 125kHz channels + if(this->band->bandNum == BandCN500) { + chMaskGrp45 |= (uint32_t)chMask << 16; + } else { + int bank = 0; + for(; bank < 8; bank++) { + if(chMask & ((uint16_t)1 << bank)) { + chMaskGrp0123 |= (0xFF << (8 * bank)); + } + } + for(; bank < 10; bank++) { + if(chMask & ((uint16_t)1 << bank)) { + chMaskGrp45 |= (0xFF << (8 * (bank - 8))); + } + } + } + break; + case 6: + // for dynamic bands: all channels ON (currently defined) + // for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4 + // except for CN500: all 125kHz channels ON + + // for dynamic bands: retrieve all defined channels + // for fixed bands: cannot store all defined channels, so select a random one from each bank + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) { + chMaskGrp45 |= (uint32_t)chMask; + } + break; + case 7: + // for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4 + // except for CN500: RFU + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) { + chMaskGrp0123 = 0; + chMaskGrp45 |= (uint32_t)chMask; + } + break; + } + opt++; + } + LoRaWANNode::hton(&mAdrOpt[1], chMaskGrp0123); + LoRaWANNode::hton(&mAdrOpt[9], chMaskGrp45); +} + +void LoRaWANNode::postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen) { + uint8_t fLen = 5; // single ADR command is 5 bytes + uint8_t numOpts = cLen / fLen; + + // duplicate the ACK bits of the atomic block response 'numOpts' times + // skip one, as the first response is already there + for(int opt = 1; opt < numOpts; opt++) { + ack[opt*2 + 0] = RADIOLIB_LORAWAN_MAC_LINK_ADR; + ack[opt*2 + 1] = ack[1]; + } +} + +int16_t LoRaWANNode::getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd) { + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_MAC_COMMANDS; i++) { + if(MacTable[i].cid == cid) { + memcpy(cmd, &MacTable[i], sizeof(LoRaWANMacCommand_t)); + return(RADIOLIB_ERR_NONE); + } + } + // didn't find this CID, check if derived class can help (if any) + int16_t state = LoRaWANNode::derivedMacFinder(cid, cmd); return(state); } -int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(strUp, fPort, isConfirmed, eventUp); - RADIOLIB_ASSERT(state); +int16_t LoRaWANNode::derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd) { + return(RADIOLIB_ERR_INVALID_CID); +} - // wait for the downlink - state = this->downlink(dataDown, lenDown, eventDown); +int16_t LoRaWANNode::sendMacCommandReq(uint8_t cid) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = LoRaWANNode::getMacCommand(cid, &cmd); + RADIOLIB_ASSERT(state); + if(!cmd.user) { + 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(fOptsUpLen >= RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The maximum size of FOpts payload was reached"); + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + // if this MAC command is already in the queue, silently stop + if(LoRaWANNode::getMacPayload(cid, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK) == RADIOLIB_ERR_NONE) { + return(RADIOLIB_ERR_NONE); + } + + state = LoRaWANNode::pushMacCommand(cid, NULL, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); return(state); } -int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { - // send the uplink - int16_t state = this->uplink(dataUp, lenUp, fPort, isConfirmed, eventUp); +int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) { + uint8_t len = 0; + int16_t state = LoRaWANNode::getMacLen(RADIOLIB_LORAWAN_MAC_LINK_CHECK, &len, RADIOLIB_LORAWAN_DOWNLINK); + uint8_t payload[len] = { 0 }; + state = LoRaWANNode::getMacPayload(RADIOLIB_LORAWAN_MAC_LINK_CHECK, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK); RADIOLIB_ASSERT(state); - // wait for the downlink - state = this->downlink(dataDown, lenDown, eventDown); + if(margin) { *margin = payload[0]; } + if(gwCnt) { *gwCnt = payload[1]; } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) { + uint8_t len = 0; + int16_t state = LoRaWANNode::getMacLen(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, &len, RADIOLIB_LORAWAN_DOWNLINK); + uint8_t payload[len] = { 0 }; + state = LoRaWANNode::getMacPayload(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK); + RADIOLIB_ASSERT(state); + + if(gpsEpoch) { + *gpsEpoch = LoRaWANNode::ntoh(&payload[0]); + if(returnUnix) { + uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980) + *gpsEpoch += unixOffset; + } + } + if(fraction) { *fraction = payload[4]; } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = LoRaWANNode::getMacCommand(cid, &cmd); + RADIOLIB_ASSERT(state); + if(dir == RADIOLIB_LORAWAN_UPLINK) { + *len = cmd.lenUp; + } else { + *len = cmd.lenDn; + } + if(inclusive) { + *len += 1; // add one byte for CID + } + return(RADIOLIB_ERR_NONE); +} + +bool LoRaWANNode::isPersistentMacCommand(uint8_t cid, uint8_t dir) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = LoRaWANNode::getMacCommand(cid, &cmd); + // in the uplink direction, MAC payload should persist per spec + if(dir == RADIOLIB_LORAWAN_UPLINK) { + return(cmd.persist); + + // in the downlink direction, MAC payload should persist if it is user-accessible + // which is the case for LinkCheck and DeviceTime + } else { + return(cmd.user); + } + return(false); +} + +int16_t LoRaWANNode::pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir) { + uint8_t fLen = 0; + int16_t state = LoRaWANNode::getMacLen(cid, &fLen, dir, true); + RADIOLIB_ASSERT(state); + + // check if we can even append the MAC command into the buffer + if(*lenOut + fLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + out[*lenOut] = cid; // add MAC id + memcpy(&out[*lenOut + 1], cOcts, fLen - 1); // copy payload into buffer + *lenOut += fLen; // payload + command ID + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir) { + size_t i = 0; + + while(i < lenIn) { + uint8_t id = in[i]; + uint8_t fLen = 0; + int16_t state = LoRaWANNode::getMacLen(id, &fLen, dir, true); + RADIOLIB_ASSERT(state); + if(lenIn < i + fLen) { + return(RADIOLIB_ERR_INVALID_CID); + } + + // if this is the requested MAC id, copy the payload over + if(id == cid) { + // only copy payload if destination is supplied + if(out) { + memcpy(out, &in[i + 1], fLen - 1); + } + return(RADIOLIB_ERR_NONE); + } + + // move on to next MAC command + i += fLen; + } + + return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); +} + +int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) { + size_t i = 0; + while(i < *lenInOut) { + uint8_t id = inOut[i]; + uint8_t fLen = 0; + int16_t state = LoRaWANNode::getMacLen(id, &fLen, dir); + RADIOLIB_ASSERT(state); + if(*lenInOut < i + fLen) { + return(RADIOLIB_ERR_INVALID_CID); + } + + // if this is the requested MAC id, + if(id == cid) { + // remove it by moving the rest of the payload forward + memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen); + + // set the remainder of the queue to 0 + memset(&inOut[i + fLen], 0, *lenInOut - i - fLen); + + *lenInOut -= fLen; + return(RADIOLIB_ERR_NONE); + } + + // move on to next MAC command + i += fLen; + } + + return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); +} + +void LoRaWANNode::clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) { + size_t i = 0; + uint8_t numDeleted = 0; + while(i < *lenInOut) { + uint8_t id = inOut[i]; + uint8_t fLen = 1; // if there is an incorrect MAC command, we should at least move forward by one byte + (void)LoRaWANNode::getMacLen(id, &fLen, dir, true); + + // only clear MAC command if it should not persist until a downlink is received + if(!LoRaWANNode::isPersistentMacCommand(id, dir)) { + // remove it by moving the rest of the payload forward + memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen); + + // set the remainder of the queue to 0 + memset(&inOut[i + fLen], 0, *lenInOut - i - fLen); + + numDeleted += fLen; + } + + // move on to next MAC command + i += fLen; + } + *lenInOut -= numDeleted; +} + +int16_t LoRaWANNode::setDatarate(uint8_t drUp) { + // scan through all enabled channels and check if the requested datarate is available + bool isValidDR = false; + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + LoRaWANChannel_t *chnl = &(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i]); + if(chnl->enabled) { + if(drUp >= chnl->drMin && drUp <= chnl->drMax) { + isValidDR = true; + break; + } + } + } + if(!isValidDR) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No defined channel allows datarate %d", drUp); + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + uint8_t cOcts[5]; + uint8_t cAck[1]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = (drUp << 4); // set requested datarate + cOcts[0] |= 0x0F; // keep Tx Power the same + (void)execMacCommand(cid, cOcts, cLen, cAck); + + // check if ACK is set for Datarate + if((cAck[0] >> 1) != 1) { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::setTxPower(int8_t txPower) { + // only allow values within the band's (or MAC state) maximum + if(txPower > this->txPowerMax) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + // Tx Power is set in steps of two + // the selected value is rounded down to nearest multiple of two away from txPowerMax + // e.g. on EU868, max is 16; if 13 is selected then we set to 12 + uint8_t numSteps = (this->txPowerMax - txPower + 1) / (-RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM); + + uint8_t cOcts[5]; + uint8_t cAck[1]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = 0xF0; // keep datarate the same + cOcts[0] |= numSteps; // set requested Tx Power + (void)execMacCommand(cid, cOcts, cLen, cAck); + + // check if ACK is set for Tx Power + if((cAck[0] >> 2) != 1) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::setRx2Dr(uint8_t dr) { + // this can only be configured in ABP mode + if(this->lwMode != RADIOLIB_LORAWAN_MODE_ABP) { + return(RADIOLIB_LORAWAN_INVALID_MODE); + } + + // can only configure different datarate for dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); + } + + // check if datarate is available in the selected band + if(this->band->dataRates[dr] == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + // find and check if the datarate is available for this radio module + DataRate_t dataRate; + int16_t state = findDataRate(dr, &dataRate); + RADIOLIB_ASSERT(state); + + // passed all checks, so configure the datarate + this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = dr; + return(state); } +void LoRaWANNode::setADR(bool enable) { + this->adrEnabled = enable; +} + +void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) { + this->dutyCycleEnabled = enable; + if(!enable) { + this->dutyCycle = 0; + } + if(msPerHour == 0) { + this->dutyCycle = this->band->dutyCycle; + } else { + this->dutyCycle = msPerHour; + } +} + +void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) { + this->dwellTimeEnabledUp = enable; + if(msPerUplink == 0) { + this->dwellTimeUp = this->band->dwellTimeUp; + } else { + this->dwellTimeUp = msPerUplink; + } +} + +// A user may enable CSMA to provide frames an additional layer of protection from interference. +// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma +void LoRaWANNode::setCSMA(bool csmaEnabled, uint8_t maxChanges, uint8_t backoffMax, uint8_t difsSlots) { + this->csmaEnabled = csmaEnabled; + if(csmaEnabled) { + this->maxChanges = maxChanges; + this->difsSlots = difsSlots; + this->backoffMax = backoffMax; + } else { + // disable all values + this->maxChanges = 0; + this->difsSlots = 0; + this->backoffMax = 0; + } +} + void LoRaWANNode::setDeviceStatus(uint8_t battLevel) { this->battLevel = battLevel; } +void LoRaWANNode::scheduleTransmission(RadioLibTime_t tUplink) { + this->tUplink = tUplink; +} + // return fCnt of last uplink; also return 0 if no uplink occured yet uint32_t LoRaWANNode::getFCntUp() { if(this->fCntUp == 0) { @@ -1694,76 +2665,73 @@ void LoRaWANNode::resetFCntDown() { this->aFCntDown = 0; } -uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { - if((msg == NULL) || (len == 0)) { - return(0); - } - - RadioLibAES128Instance.init(key); - uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE]; - RadioLibAES128Instance.generateCMAC(msg, len, cmac); - return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24); +uint32_t LoRaWANNode::getDevAddr() { + return(this->devAddr); } -bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { - if((msg == NULL) || (len < sizeof(uint32_t))) { - return(0); - } - - // extract MIC from the message - uint32_t micReceived = LoRaWANNode::ntoh(&msg[len - sizeof(uint32_t)]); - - // calculate the expected value and compare - uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key); - if(micCalculated != micReceived) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MIC mismatch, expected %08x, got %08x", micCalculated, micReceived); - return(false); - } - - return(true); +RadioLibTime_t LoRaWANNode::getLastToA() { + return(this->lastToA); } -int16_t LoRaWANNode::setPhyProperties(uint8_t dir) { +int16_t LoRaWANNode::setPhyProperties(LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre) { // set the physical layer configuration - 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->modulation = RADIOLIB_LORAWAN_MODULATION_GFSK; + int16_t state = this->phyLayer->standby(); + if(state != RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?"); + return(state); } - int8_t pwr = this->txPowerMax - this->txPowerSteps * 2; + // TODO implement PhysicalLayer::setModem() + // set modem-dependent functions + switch(this->band->dataRates[chnl->dr] & RADIOLIB_LORAWAN_DATA_RATE_MODEM) { + case(RADIOLIB_LORAWAN_DATA_RATE_LORA): + this->modulation = RADIOLIB_LORAWAN_MODULATION_LORA; + // downlink messages are sent with inverted IQ + if(dir == RADIOLIB_LORAWAN_DOWNLINK) { + state = this->phyLayer->invertIQ(true); + } else { + state = this->phyLayer->invertIQ(false); + } + RADIOLIB_ASSERT(state); + break; + case(RADIOLIB_LORAWAN_DATA_RATE_FSK): + this->modulation = RADIOLIB_LORAWAN_MODULATION_GFSK; + state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); + RADIOLIB_ASSERT(state); + break; + case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS): + this->modulation = RADIOLIB_LORAWAN_MODULATION_LR_FHSS; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN(""); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency %cL = %7.3f MHz", dir ? 'D' : 'U', chnl->freq / 10000.0); + state = this->phyLayer->setFrequency(chnl->freq / 10000.0); + RADIOLIB_ASSERT(state); // at this point, assume that Tx power value is already checked, so ignore the return value + // this call is only used to clip a value that is higher than the module supports (void)this->phyLayer->checkOutputPower(pwr, &pwr); state = this->phyLayer->setOutputPower(pwr); RADIOLIB_ASSERT(state); DataRate_t dr; - state = findDataRate(this->dataRates[dir], &dr); + state = findDataRate(chnl->dr, &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->modulation == RADIOLIB_LORAWAN_MODULATION_GFSK) { - state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); - RADIOLIB_ASSERT(state); - state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); - RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FSK: BR = %4.1f, TX = %d dBm, FD = %4.1f kHz", + dr.fsk.bitRate, pwr, dr.fsk.freqDev); } - - // downlink messages are sent with inverted IQ - if(dir == RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK) { - if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } + if(this->modulation == RADIOLIB_LORAWAN_MODULATION_LORA) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRa: SF = %d, TX = %d dBm, BW = %5.1f kHz, CR = 4/%d", + dr.lora.spreadingFactor, pwr, dr.lora.bandwidth, dr.lora.codingRate); } // this only needs to be done once-ish @@ -1800,238 +2768,358 @@ int16_t LoRaWANNode::setPhyProperties(uint8_t dir) { state = this->phyLayer->setSyncWord(syncWord, syncWordLen); RADIOLIB_ASSERT(state); + // if a preamble length is supplied, overrule the 'calculated' preamble length + if(pre) { + preLen = pre; + } if(this->modulation != RADIOLIB_LORAWAN_MODULATION_LR_FHSS) { state = this->phyLayer->setPreambleLength(preLen); } return(state); } -int16_t LoRaWANNode::setupChannelsDyn(bool joinRequest) { +// The following function implements LMAC, a CSMA scheme for LoRa as specified +// in the LoRa Alliance Technical Recommendation #13. +bool LoRaWANNode::csmaChannelClear(uint8_t difs, uint8_t numBackoff) { + // DIFS phase: perform #DIFS CAD operations + uint8_t numCads = 0; + for (; numCads < difs; numCads++) { + if (!this->cadChannelClear()) { + return(false); + } + } + + // BO phase: perform #numBackoff additional CAD operations + for (; numCads < difs + numBackoff; numCads++) { + if (!this->cadChannelClear()) { + return(false); + } + } + + // none of the CADs showed activity, so all clear + return(true); +} + +bool LoRaWANNode::cadChannelClear() { + int16_t state = this->phyLayer->scanChannel(); + // if activity was detected, channel is not clear + if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { + return(false); + } + return(true); +} + +void LoRaWANNode::getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45) { + // clear masks in case anything was set + *chMaskGrp0123 = 0; + *chMaskGrp45 = 0; + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx; + if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + if(idx < 64) { + *chMaskGrp0123 |= ((uint64_t)1 << idx); + } else { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + } + } else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED + // if a subband is set, we can set the channel indices straight from subband + if(this->subBand > 0 && this->subBand <= 8) { + // for sub band 1-8, set bank of 8 125kHz + single 500kHz channel + *chMaskGrp0123 |= 0xFF << ((this->subBand - 1) * 8); + *chMaskGrp45 |= 0x01 << ((this->subBand - 1) * 8); + } else if(this->subBand > 8 && this->subBand <= 12) { + // CN500 only: for sub band 9-12, set bank of 8 125kHz channels + *chMaskGrp45 |= 0xFF << ((this->subBand - 9) * 8); + } else { + // if subband is set to 0, all 125kHz channels are enabled + // however, we can 'only' store 16 channels, so we do not actually store these + // therefore, we select a random channel from each bank of 8 channels + uint8_t num125kHz = this->band->txSpans[0].numChannels; + uint8_t numBanks = num125kHz / 8; + for(uint8_t bank = 0; bank < numBanks; bank++) { + uint8_t bankIdx = this->phyLayer->random(8); + uint8_t idx = bank * 8 + bankIdx; + if(idx < 64) { + *chMaskGrp0123 |= ((uint64_t)1 << idx); + } else { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + // the 500 kHz channels are in the usual channel plan however + // these are the channel indices 64-71 for bands other than CN500 + if(this->band->bandNum != BandCN500) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx; + if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE && idx >= 64) { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + } + } + } +} + +void LoRaWANNode::selectChannelPlanDyn(bool joinRequest) { RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up dynamic channels"); size_t num = 0; // copy the default defined channels into the first slots (where Tx = Rx) for(; num < 3 && this->band->txFreqs[num].enabled; num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = this->band->txFreqs[num]; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num]; } - // if we're about to send a join-request, copy the join-request channels to the next slots + // if we're about to send a JoinRequest, copy the JoinRequest channels to the next slots if(joinRequest) { size_t numJR = 0; for(; numJR < 3 && this->band->txJoinReq[num].enabled; numJR++, num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = this->band->txFreqs[num]; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num]; } } // clear all remaining channels for(; num < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; } + // make sure the Rx2 settings are back to this band's default + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; + + // make all enabled channels available for uplink selection + this->setAvailableChannels(0xFFFF); + #if RADIOLIB_DEBUG_PROTOCOL this->printChannels(); #endif - - return(RADIOLIB_ERR_NONE); } -// setup a subband and its corresponding join-request datarate +// setup a subband and its corresponding JoinRequest datarate // WARNING: subBand starts at 1 (corresponds to all populair schemes) -int16_t LoRaWANNode::setupChannelsFix(uint8_t subBand) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up fixed channels (subband %d)", subBand); +void LoRaWANNode::selectChannelPlanFix() { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up fixed channels (subband %d)", this->subBand); // clear all existing channels for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; } - // if no subband is selected by user, cycle through banks of 8 using devNonce value - if(subBand == 0) { - uint8_t numBanks8 = this->band->txSpans[0].numChannels / 8; - subBand = this->devNonce % numBanks8; - } + // get channel masks for this subband + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); - uint8_t chMaskCntl = 0; - uint16_t chMask = 0; + // apply channel mask + this->applyChannelMask(chMaskGrp0123, chMaskGrp45); + + // make sure the Rx2 settings are back to this band's default + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; - // if there are two channel spans, first set the channel from second span - if(this->band->numTxSpans == 2) { - chMaskCntl = 7; - chMask = (1 << (subBand - 1)); // set channel mask - this->applyChannelMaskFix(chMaskCntl, chMask); - } + // make all enabled channels available for uplink selection + this->setAvailableChannels(0xFFFF); - // chMask is set for 16 channels at once, so widen the Cntl value - chMaskCntl = (subBand - 1) / 2; // compensate the 1 offset - - // now select the correct bank of 8 channels - if(subBand % 2 == 0) { // even subbands - chMask = 0xFF00; - } else { - chMask = 0x00FF; // odd subbands - } - this->applyChannelMaskFix(chMaskCntl, chMask); - - return(RADIOLIB_ERR_NONE); + #if RADIOLIB_DEBUG_PROTOCOL + this->printChannels(); + #endif } -int16_t LoRaWANNode::processCFList(uint8_t* cfList) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processing CFList"); - - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // retrieve number of existing (default) channels - size_t num = 0; - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(!this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - break; +uint8_t LoRaWANNode::getAvailableChannels(uint16_t* chMask) { + uint8_t num = 0; + uint16_t mask = 0; + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // if channel is available and usable for current datarate, set corresponding bit + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available) { + if(currentDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin && + currentDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) { + num++; + mask |= (0x0001 << i); } - num++; - } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - - uint8_t freqZero[3] = { 0 }; - - // datarate range for all new channels is equal to the default channels - cmd.payload[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin; - for(uint8_t i = 0; i < 5; i++, num++) { - // if the frequency fields are all zero, there are no more channels in the CFList - if(memcmp(&cfList[i*3], freqZero, 3) == 0) { - break; - } - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - cmd.payload[0] = num; - memcpy(&cmd.payload[1], &cfList[i*3], 3); - (void)execMacCommand(&cmd); - } - } else { // RADIOLIB_LORAWAN_BAND_FIXED - // complete channel mask received, so clear all existing channels - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; - } - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - - // in case of mask-type bands, copy those frequencies that are masked true into the available TX channels - size_t numChMasks = 3 + this->band->numTxSpans; // 4 masks for bands with 2 spans, 5 spans for bands with 1 span - for(size_t chMaskCntl = 0; chMaskCntl < numChMasks; chMaskCntl++) { - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - cmd.payload[0] = 0xFF; // same datarate and payload - memcpy(&cmd.payload[1], &cfList[chMaskCntl*2], 2); // copy mask - cmd.payload[3] = chMaskCntl << 4; // set chMaskCntl, set NbTrans = 0 -> keep the same - cmd.repeat = (chMaskCntl + 1); - (void)execMacCommand(&cmd); } } + if(chMask) { + *chMask = mask; + } + return(num); +} - return(RADIOLIB_ERR_NONE); +void LoRaWANNode::setAvailableChannels(uint16_t mask) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // if channel is enabled, set to available + if(mask & (0x0001 << i) && this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = true; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = false; + } + } } int16_t LoRaWANNode::selectChannels() { - // figure out which channel IDs are enabled (chMask may have disabled some) and are valid for the current datarate - uint8_t numChannels = 0; - uint8_t channelsEnabled[RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; - for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] >= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin - && this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] <= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax) { - channelsEnabled[numChannels] = i; - numChannels++; - } + uint16_t chMask = 0x0000; + uint8_t numChannels = this->getAvailableChannels(&chMask); + + // if there are no available channels, try resetting them all to available + if(numChannels == 0) { + this->setAvailableChannels(0xFFFF); + numChannels = this->getAvailableChannels(&chMask); + + // if there are still no channels available, give up + if(numChannels == 0) { + return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); } } - if(numChannels == 0) { - return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); + + // select a random value within the number of possible channels + int chRand = this->phyLayer->random(numChannels); + + // retrieve the index of this channel by looping through the channel mask + int chIdx = -1; + while(chRand >= 0) { + chIdx++; + if(chMask & 0x0001) { + chRand--; + } + chMask >>= 1; } - // select a random ID & channel from the list of enabled and possible channels - uint8_t channelID = channelsEnabled[this->phyLayer->random(numChannels)]; - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][channelID]; + + // as we are now going to use this channel, mark unavailable for next uplink + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx].available = false; + + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + this->channels[RADIOLIB_LORAWAN_UPLINK] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx]; + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr; if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { // for dynamic bands, the downlink channel is the one matched to the uplink channel - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][channelID]; + this->channels[RADIOLIB_LORAWAN_DOWNLINK] = this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][chIdx]; } else { // RADIOLIB_LORAWAN_BAND_FIXED // for fixed bands, the downlink channel is the uplink channel ID `modulo` number of downlink channels LoRaWANChannel_t channelDn; channelDn.enabled = true; - channelDn.idx = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx % this->band->rx1Span.numChannels; + channelDn.idx = this->channels[RADIOLIB_LORAWAN_UPLINK].idx % this->band->rx1Span.numChannels; channelDn.freq = this->band->rx1Span.freqStart + channelDn.idx*this->band->rx1Span.freqStep; channelDn.drMin = this->band->rx1Span.drMin; channelDn.drMax = this->band->rx1Span.drMax; - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDn; + this->channels[RADIOLIB_LORAWAN_DOWNLINK] = channelDn; } - uint8_t drDown = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], this->rx1DrOffset, this->band->rx1DataRateBase, - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin, this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax); - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown; + uint8_t rx1Dr = this->band->rx1DrTable[currentDr][this->rx1DrOffset]; + + // if downlink dwelltime is enabled, datarate < 2 cannot be used, so clip to 2 + // only in use on AS923_x bands + if(this->dwellTimeEnabledDn && rx1Dr < 2) { + rx1Dr = 2; + } + this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr = rx1Dr; return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::setDatarate(uint8_t drUp) { - // scan through all enabled channels and check if the requested datarate is available - bool isValidDR = false; - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - LoRaWANChannel_t *chnl = &(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]); - if(chnl->enabled) { - if(drUp >= chnl->drMin && drUp <= chnl->drMax) { - isValidDR = true; - break; +bool LoRaWANNode::applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45) { + int num = 0; + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(chMaskGrp0123 & ((uint64_t)1 << i)) { + // if it should be enabled but is not currently defined, stop immediately + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + return(false); + } + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = true; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = false; + } + } + } else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED + LoRaWANChannel_t chnl; + uint8_t spanNum = 0; + int chNum = 0; + int chOfs = 0; + for(; chNum < 64; chNum++) { + if(chMaskGrp0123 & ((uint64_t)1 << chNum)) { + chnl.enabled = true; + chnl.idx = chNum; + chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep; + chnl.drMin = this->band->txSpans[spanNum].drMin; + chnl.drMax = this->band->txSpans[spanNum].drMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl; + } + } + if(this->band->numTxSpans > 1) { + spanNum += 1; + chNum = 0; + chOfs = 64; + } + for(; chNum < this->band->txSpans[spanNum].numChannels; chNum++) { + if(chMaskGrp45 & ((uint32_t)1 << chNum)) { + chnl.enabled = true; + chnl.idx = chNum + chOfs; + chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep; + chnl.drMin = this->band->txSpans[spanNum].drMin; + chnl.drMax = this->band->txSpans[spanNum].drMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl; } } } - if(!isValidDR) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No defined channel allows datarate %d", drUp); - return(RADIOLIB_ERR_INVALID_DATA_RATE); - } - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; - cmd.payload[0] = (drUp << 4); - cmd.payload[0] |= 0x0F; // keep Tx Power the same - cmd.payload[3] = (1 << 7); // set the RFU bit, which means that the channel mask gets ignored - cmd.payload[3] |= 0; // keep NbTrans the same - (void)execMacCommand(&cmd); +#if RADIOLIB_DEBUG_PROTOCOL + this->printChannels(); +#endif - // check if ACK is set for Tx Power - if((cmd.payload[0] >> 1) != 1) { - return(RADIOLIB_ERR_INVALID_DATA_RATE); - } - - return(RADIOLIB_ERR_NONE); + return(true); } -void LoRaWANNode::setADR(bool enable) { - this->adrEnabled = enable; +void LoRaWANNode::printChannels() { + for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax, + + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].idx, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].enabled, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMin, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMax + ); + } + } } -void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) { - this->dutyCycleEnabled = enable; - if(!enable) { - this->dutyCycle = 0; +uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { + if((msg == NULL) || (len == 0)) { + return(0); } - if(msPerHour == 0) { - this->dutyCycle = this->band->dutyCycle; - } else { - this->dutyCycle = msPerHour; + + RadioLibAES128Instance.init(key); + uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE]; + RadioLibAES128Instance.generateCMAC(msg, len, cmac); + return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24); +} + +bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { + if((msg == NULL) || (len < sizeof(uint32_t))) { + return(0); } + + // extract MIC from the message + uint32_t micReceived = LoRaWANNode::ntoh(&msg[len - sizeof(uint32_t)]); + + // calculate the expected value and compare + uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key); + if(micCalculated != micReceived) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MIC mismatch, expected %08x, got %08x", micCalculated, micReceived); + return(false); + } + + return(true); } // given an airtime in milliseconds, calculate the minimum uplink interval @@ -2055,93 +3143,66 @@ RadioLibTime_t LoRaWANNode::timeUntilUplink() { return(nextUplink - mod->hal->millis() + 1); } -void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) { - this->dwellTimeEnabledUp = enable; - if(msPerUplink == 0) { - this->dwellTimeUp = this->band->dwellTimeUp; - } else { - this->dwellTimeUp = msPerUplink; - } -} +uint8_t LoRaWANNode::maxUplinkLen() { + // configure the uplink channel properties + this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK], + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); -uint8_t LoRaWANNode::maxPayloadDwellTime() { - // configure current datarate - DataRate_t 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; + uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + maxPayLen -= 13; // FHDR is 13 bytes + maxPayLen -= this->fOptsUpLen; // uplink MAC commands + uint8_t payLen = (minPayLen + maxPayLen) / 2; // do some binary search to find maximum allowed payload length while(payLen != minPayLen && payLen != maxPayLen) { - if(this->phyLayer->getTimeOnAir(payLen) > this->dwellTimeUp) { + if(this->phyLayer->getTimeOnAir(payLen) / 1000 > this->dwellTimeUp) { maxPayLen = payLen; } else { minPayLen = payLen; } payLen = (minPayLen + maxPayLen) / 2; } - return(payLen - 13); // fixed 13-byte header -} - -int16_t LoRaWANNode::setTxPower(int8_t txPower) { - // only allow values within the band's (or MAC state) maximum - if(txPower > this->txPowerMax) { - return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } - // Tx Power is set in steps of two - // the selected value is rounded down to nearest multiple of two away from txPowerMax - // e.g. on EU868, max is 16; if 13 is selected then we set to 12 - uint8_t numSteps = (this->txPowerMax - txPower + 1) / (-RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM); - - LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_LINK_ADR, - .payload = { 0 }, - .len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn, - .repeat = 0, - }; - cmd.payload[0] = 0xF0; // keep datarate the same - cmd.payload[0] |= numSteps; // set the Tx Power - cmd.payload[3] = (1 << 7); // set the RFU bit, which means that the channel mask gets ignored - cmd.payload[3] |= 0; // keep NbTrans the same - (void)execMacCommand(&cmd); - - // check if ACK is set for Tx Power - if((cmd.payload[0] >> 2) != 1) { - return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); - } - - return(RADIOLIB_ERR_NONE); + return(payLen - 13 - this->fOptsUpLen); } int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { - int16_t state = RADIOLIB_ERR_UNKNOWN; + int16_t state = this->phyLayer->standby(); + if(state != RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?"); + return(state); + } uint8_t dataRateBand = this->band->dataRates[dr]; - if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { - dataRate->fsk.bitRate = 50; - dataRate->fsk.freqDev = 25; - - } else { - uint8_t bw = dataRateBand & 0x0C; - switch(bw) { - case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ): - dataRate->lora.bandwidth = 125.0; - break; - case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ): - dataRate->lora.bandwidth = 250.0; - break; - case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ): - dataRate->lora.bandwidth = 500.0; - break; - default: - dataRate->lora.bandwidth = 125.0; - } - - dataRate->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6; - dataRate->lora.codingRate = (dataRateBand & 0x03) + 5; + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_MODEM) { + case(RADIOLIB_LORAWAN_DATA_RATE_LORA): + dataRate->lora.spreadingFactor = ((dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_SF) >> 3) + 7; + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_BW) { + case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ): + dataRate->lora.bandwidth = 125.0; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ): + dataRate->lora.bandwidth = 250.0; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ): + dataRate->lora.bandwidth = 500.0; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + dataRate->lora.codingRate = 5; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_FSK): + dataRate->fsk.bitRate = 50; + dataRate->fsk.freqDev = 25; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS): + // not yet supported by DataRate_t + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); } state = this->phyLayer->checkDataRate(*dataRate); @@ -2149,852 +3210,6 @@ int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { return(state); } -int16_t LoRaWANNode::setRx2Dr(uint8_t dr) { - // this can only be configured in ABP mode - if(this->lwMode != RADIOLIB_LORAWAN_MODE_ABP) { - return(RADIOLIB_LORAWAN_INVALID_MODE); - } - - // can only configure different datarate for dynamic bands - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { - return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); - } - - // check if datarate is available in the selected band - if(this->band->dataRates[dr] == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - return(RADIOLIB_ERR_INVALID_DATA_RATE); - } - - // find and check if the datarate is available for this radio module - DataRate_t dataRate; - int16_t state = findDataRate(dr, &dataRate); - RADIOLIB_ASSERT(state); - - // passed all checks, so configure the datarate - this->rx2.drMax = dr; - - return(state); -} - -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) { - 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, - .payload = { 0 }, - .len = 0, - .repeat = 0, - }; - pushMacCommand(&cmd, &this->commandsUp); - return(true); -} - -void LoRaWANNode::macQueueToBuff(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer) { - // append all MAC replies into fOpts buffer - uint8_t* fOptsPtr = buffer; - int16_t i = 0; - for (; i < queue->numCommands; i++) { - LoRaWANMacCommand_t cmd = queue->commands[i]; - memcpy(fOptsPtr, &cmd, 1 + cmd.len); - fOptsPtr += cmd.len + 1; - } - - // pop the commands from back to front - for (; i >= 0; i--) { - if(queue->commands[i].repeat > 0) { - queue->commands[i].repeat--; - } else { - deleteMacCommand(queue->commands[i].cid, queue); - } - } - -} - -void LoRaWANNode::macBufftoQueue(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer, uint8_t len) { - bool hasADR = false; - uint8_t numADR = 0; - uint8_t lastCID = 0; - - // process the MAC command(s) - int8_t remLen = len; - uint8_t* fOptsPtr = buffer; - while(remLen > 0) { - uint8_t cid = *fOptsPtr; - uint8_t macLen = getMacPayloadLength(cid); - if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) { - // if there was an earlier ADR command but it was not the last, ignore it - if(hasADR && lastCID != RADIOLIB_LORAWAN_MAC_LINK_ADR) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Encountered non-consecutive block of ADR commands - skipping"); - remLen -= (macLen + 1); - fOptsPtr += (macLen + 1); - lastCID = cid; - continue; - } - // otherwise, set ADR flag to true and increase counter - hasADR = true; - numADR++; - } - if(macLen + 1 > remLen) - break; - LoRaWANMacCommand_t cmd = { - .cid = cid, - .payload = { 0 }, - .len = macLen, - .repeat = (cid == RADIOLIB_LORAWAN_MAC_LINK_ADR ? numADR : (uint8_t)0), - }; - memcpy(cmd.payload, fOptsPtr + 1, macLen); - pushMacCommand(&cmd, queue); - - // move in the buffer to the next command - remLen -= (macLen + 1); - fOptsPtr += (macLen + 1); - lastCID = cid; - } - -} - -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)); - queue->numCommands++; - queue->len += 1 + cmd->len; // 1 byte for command ID, len bytes for payload - - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue, uint8_t* payload) { - for(size_t index = 0; index < queue->numCommands; index++) { - if(queue->commands[index].cid == cid) { - // if a pointer to a payload is supplied, copy the command's payload over - if(payload) { - memcpy(payload, queue->commands[index].payload, queue->commands[index].len); - } - queue->len -= (1 + queue->commands[index].len); // 1 byte for command ID, len for payload - // move all subsequent commands one forward in the queue - if(index < RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1) { - memmove(&queue->commands[index], &queue->commands[index + 1], (RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - index - 1) * sizeof(LoRaWANMacCommand_t)); - } - // set the latest element to all 0 - memset(&queue->commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1], 0x00, sizeof(LoRaWANMacCommand_t)); - queue->numCommands--; - return(RADIOLIB_ERR_NONE); - } - } - - return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); -} - -bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[MAC] 0x%02x", cmd->cid); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(cmd->payload, cmd->len); - - if(cmd->cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) { - // TODO call user-provided callback for proprietary MAC commands? - return(false); - } - - switch(cmd->cid) { - case(RADIOLIB_LORAWAN_MAC_RESET): { - // get the server version - uint8_t srvVersion = cmd->payload[0]; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ResetConf: server version 1.%d", srvVersion); - if(srvVersion == this->rev) { - // valid server version, stop sending the ResetInd MAC command - deleteMacCommand(RADIOLIB_LORAWAN_MAC_RESET, &this->commandsUp); - } - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]"); - cmd->repeat = 255; // prevent the command from being deleted by setting repeat to maximum - - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_LINK_ADR): { - // get the ADR configuration - uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4; - uint8_t txSteps = cmd->payload[0] & 0x0F; - bool isInternalTxDr = cmd->payload[3] >> 7; - - uint16_t chMask = LoRaWANNode::ntoh(&cmd->payload[1]); - uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4; - uint8_t nbTransMac = cmd->payload[3] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRReq: dataRate = %d, txSteps = %d, chMask = 0x%04x, chMaskCntl = %d, nbTrans = %d", drUp, txSteps, chMask, chMaskCntl, nbTransMac); - - // try to apply the datarate configuration - int16_t state; - uint8_t drAck = 0; - if(drUp == 0x0F) { // keep the same - drAck = 1; - - } else if (this->band->dataRates[drUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - // check if the module supports this data rate - DataRate_t 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, - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax); - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown; - 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' - } - - } - - // try to apply the power configuration - uint8_t pwrAck = 0; - if(txSteps == 0x0F) { - pwrAck = 1; - - } else { - 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->txPowerSteps = 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) - if(!isInternalTxDr) { - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - chMaskAck = (uint8_t)this->applyChannelMaskDyn(chMaskCntl, chMask); - - } else { // RADIOLIB_LORAWAN_BAND_FIXED - if(cmd->repeat == 1) { - // if this is the first ADR command in the queue, clear all saved channels - // so we can apply the new channel mask - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR mask: clearing channels"); - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; - } - // clear all previous channel masks - memset(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS], 0, 16*8); - } else { - // if this is not the first ADR command, clear the ADR response that was in the queue - (void)deleteMacCommand(RADIOLIB_LORAWAN_MAC_LINK_ADR, &this->commandsUp); - } - chMaskAck = (uint8_t)this->applyChannelMaskFix(chMaskCntl, chMask); - - } - } - - if(nbTransMac) { // if there is a value for NbTrans, set this value - this->nbTrans = nbTransMac; - } - - // 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->txPowerSteps; - } - if(nbTransMac == 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) { - memcpy(&(cmd->payload[1]), &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR] + 1, 3); - } - - // if there was no channel mask (all zeroes), we should never apply that channel mask, so set RFU bit again - if(cmd->payload[1] == 0 && cmd->payload[2] == 0) { - cmd->payload[3] |= (1 << 7); - } - - // save to the single ADR MAC location - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], &(cmd->payload[0]), cmd->len); - - } else { // RADIOLIB_LORAWAN_BAND_FIXED - - // save Tx/Dr to the Link ADR position in the session buffer - uint8_t bufTxDr[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - bufTxDr[0] = cmd->payload[0]; - bufTxDr[3] = 1 << 7; - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], bufTxDr, cmd->len); - - // if RFU bit is set, this is just a change in Datarate or TxPower, in which case we don't save the channel masks - // if the RFU bit is not set, we must save this channel mask - if(!isInternalTxDr) { - // save the channel mask to the uplink channels position in session buffer, with Tx and DR set to 'same' - cmd->payload[0] = 0xFF; - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + (cmd->repeat - 1) * cmd->len, cmd->payload, cmd->len); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Saving mask to ULChannels[%d]:", (cmd->repeat - 1) * cmd->len); - RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + (cmd->repeat - 1) * cmd->len, cmd->len); - } - - } - - // send the reply - cmd->len = 1; - cmd->payload[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0); - cmd->repeat = 0; // discard any repeat value that may have been set - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRAns: status = 0x%02x", cmd->payload[0]); - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_DUTY_CYCLE): { - uint8_t maxDutyCycle = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle); - if(maxDutyCycle == 0) { - this->dutyCycle = this->band->dutyCycle; - } else { - this->dutyCycle = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000 / (RadioLibTime_t)(1UL << maxDutyCycle); - } - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], cmd->payload, cmd->len); - - cmd->len = 0; - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP): { - // get the configuration - this->rx1DrOffset = (cmd->payload[0] & 0x70) >> 4; - uint8_t rx1OffsAck = 1; - this->rx2.drMax = cmd->payload[0] & 0x0F; - uint8_t rx2Ack = 1; - uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); - this->rx2.freq = (float)freqRaw/10000.0; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupReq: rx1DrOffset = %d, rx2DataRate = %d, freq = %f", this->rx1DrOffset, this->rx2.drMax, this->rx2.freq); - - // apply the configuration - uint8_t chanAck = 0; - if(this->phyLayer->setFrequency(this->rx2.freq) == RADIOLIB_ERR_NONE) { - chanAck = 1; - this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); - } - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], cmd->payload, cmd->len); - - // TODO this should be sent repeatedly until the next downlink - cmd->len = 1; - cmd->payload[0] = (rx1OffsAck << 2) | (rx2Ack << 1) | (chanAck << 0); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupAns: status = 0x%02x", cmd->payload[0]); - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_DEV_STATUS): { - // set the uplink reply - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusReq"); - cmd->len = 2; - cmd->payload[0] = this->battLevel; - int8_t snr = this->phyLayer->getSNR(); - cmd->payload[1] = snr & 0x3F; - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", cmd->payload[0], cmd->payload[1]); - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_NEW_CHANNEL): { - // get the configuration - uint8_t chIndex = cmd->payload[0]; - uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); - float freq = (float)freqRaw/10000.0; - uint8_t maxDr = (cmd->payload[4] & 0xF0) >> 4; - uint8_t minDr = cmd->payload[4] & 0x0F; - - uint8_t newChAck = 0; - uint8_t freqAck = 0; - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].enabled = true; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].idx = chIndex; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].freq = freq; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMin = minDr; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMax = maxDr; - - // downlink channel is identical to uplink channel - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex] = this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex]; - newChAck = 1; - - // check if the frequency is possible - if(this->phyLayer->setFrequency(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].freq) == RADIOLIB_ERR_NONE) { - freqAck = 1; - this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); - } - - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("NewChannelReq:"); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chIndex].drMax, - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][chIndex].drMax - ); - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + chIndex * cmd->len, cmd->payload, cmd->len); - - // send the reply - cmd->len = 1; - cmd->payload[0] = (newChAck << 1) | (freqAck << 0); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("NewChannelAns: status = 0x%02x", cmd->payload[0]); - - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_DL_CHANNEL): { - // get the configuration - uint8_t chIndex = cmd->payload[0]; - uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); - float freq = (float)freqRaw/10000.0; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelReq: index = %d, freq = %f MHz", chIndex, freq); - uint8_t freqDlAck = 0; - uint8_t freqUlAck = 0; - - // check if the frequency is possible - if(this->phyLayer->setFrequency(freq) == RADIOLIB_ERR_NONE) { - freqDlAck = 1; - this->phyLayer->setFrequency(this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].freq); - } - - // update the downlink frequency - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx == chIndex) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq = freq; - // check if the corresponding uplink frequency is actually set - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq > 0) { - freqUlAck = 1; - } - } - } - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS] + chIndex * cmd->len, cmd->payload, cmd->len); - - // TODO send this repeatedly until a downlink is received - cmd->len = 1; - cmd->payload[0] = (freqUlAck << 1) | (freqDlAck << 0); - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelAns: status = 0x%02x", cmd->payload[0]); - - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): { - // get the configuration - uint8_t delay = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay); - - // apply the configuration - if(delay == 0) { - delay = 1; - } - this->rxDelays[0] = delay * 1000; - this->rxDelays[1] = this->rxDelays[0] + 1000; - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], cmd->payload, cmd->len); - - // send the reply - cmd->len = 0; - - // TODO send this repeatedly until a downlink is received - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP): { - uint8_t dlDwell = (cmd->payload[0] & 0x20) >> 5; - uint8_t ulDwell = (cmd->payload[0] & 0x10) >> 4; - uint8_t maxEirpRaw = cmd->payload[0] & 0x0F; - - // who the f came up with this ... - const uint8_t eirpEncoding[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 }; - this->txPowerMax = eirpEncoding[maxEirpRaw]; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("TxParamSetupReq: dlDwell = %d, ulDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, eirpEncoding[maxEirpRaw]); - - this->dwellTimeEnabledUp = ulDwell ? true : false; - this->dwellTimeUp = ulDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; - - this->dwellTimeEnabledDn = dlDwell ? true : false; - this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], cmd->payload, cmd->len); - - cmd->len = 0; - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_REKEY): { - // get the server version - uint8_t srvVersion = cmd->payload[0]; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RekeyConf: server version = 1.%d", srvVersion); - if((srvVersion > 0) && (srvVersion <= this->rev)) { - // valid server version, stop sending the ReKey MAC command - deleteMacCommand(RADIOLIB_LORAWAN_MAC_REKEY, &this->commandsUp); - } - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): { - this->adrLimitExp = (cmd->payload[0] & 0xF0) >> 4; - this->adrDelayExp = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], cmd->payload, cmd->len); - - cmd->len = 0; - return(true); - } break; - - case(RADIOLIB_LORAWAN_MAC_DEVICE_TIME): { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DeviceTimeAns: [user]"); - cmd->repeat = 255; // prevent the command from being deleted by setting repeat to maximum - - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): { - // TODO implement this - uint16_t rejoinReq = LoRaWANNode::ntoh(cmd->payload); - uint8_t period = (rejoinReq & 0x3800) >> 11; - uint8_t maxRetries = (rejoinReq & 0x0700) >> 8; - uint8_t rejoinType = (rejoinReq & 0x0070) >> 4; - uint8_t dr = rejoinReq & 0x000F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); - (void)period; - (void)maxRetries; - (void)rejoinType; - (void)dr; - return(false); - } break; - - case(RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP): { - // TODO implement this - uint8_t maxTime = (cmd->payload[0] & 0xF0) >> 4; - uint8_t maxCount = cmd->payload[0] & 0x0F; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount); - - memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], cmd->payload, cmd->len); - - cmd->len = 0; - cmd->payload[0] = (1 << 1) | 1; - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupAns: status = 0x%02x", cmd->payload[0]); - - (void)maxTime; - (void)maxCount; - return(true); - } break; - } - - return(false); -} - -void LoRaWANNode::printChannels() { - for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax, - - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].idx, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].enabled, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].freq, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMin, - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i].drMax - ); - } - } -} - -bool LoRaWANNode::applyChannelMaskDyn(uint8_t chMaskCntl, uint16_t chMask) { - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(chMaskCntl == 0) { - // apply the mask by looking at each channel bit - if(chMask & (1UL << i)) { - // if it should be enabled but is not currently defined, stop immediately - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { - return(false); - } - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; - } else { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = false; - } - - } else if(chMaskCntl == 6) { - // enable all defined channels - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; - } - } - - } - - #if RADIOLIB_DEBUG_PROTOCOL - this->printChannels(); - #endif - - return(true); -} - -bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask); - - // find out how many channels have already been configured - uint8_t idx = 0; - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq > 0) { - idx++; - } - } - - LoRaWANChannel_t chnl; - if((this->band->numTxSpans == 1 && chMaskCntl <= 5) || (this->band->numTxSpans == 2 && chMaskCntl <= 3)) { - // select channels from first span - for(uint8_t i = 0; i < 16; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - uint8_t chNum = chMaskCntl * 16 + i; // 0 through 63 or 95 - this->subBand = chNum / 8 + 1; // save configured subband in case we must reset the channels (1-based) - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[0].freqStart + chNum*this->band->txSpans[0].freqStep; - chnl.drMin = this->band->txSpans[0].drMin; - chnl.drMax = this->band->txSpans[0].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 1 && chMaskCntl == 6) { - // all channels on (but we revert to user-selected subband) - this->setupChannelsFix(this->subBand); - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 4) { - // select channels from second span - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - uint8_t chNum = chMaskCntl * 16 + i; // 64 through 71 - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 5) { - // a '1' enables a bank of 8 + 1 channels from 1st and 2nd span respectively - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - // enable bank of 8 channels from first span - for(uint8_t j = 0; j < 8; j++) { - uint8_t chNum = i * 8 + j; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[0].freqStart + chNum*this->band->txSpans[0].freqStep; - chnl.drMin = this->band->txSpans[0].drMin; - chnl.drMax = this->band->txSpans[0].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - // enable single channel from second span - uint8_t chNum = 64 + i; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 6) { - // all channels on (but we revert to selected subband) - this->setupChannelsFix(this->subBand); - - // a '1' enables a single channel from second span - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - // enable single channel from second span - uint8_t chNum = 64 + i; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - if(this->band->numTxSpans == 2 && chMaskCntl == 7) { - // all channels off (clear all channels) - chnl = RADIOLIB_LORAWAN_CHANNEL_NONE; - for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = chnl; - // downlink channels are not defined so don't need to reset - } - idx = 0; - // a '1' enables a single channel from second span - for(uint8_t i = 0; i < 8; i++) { - uint16_t mask = 1 << i; - if(mask & chMask) { - // enable single channel from second span - uint8_t chNum = 64 + i; - chnl.enabled = true; - chnl.idx = chNum; - chnl.freq = this->band->txSpans[1].freqStart + i*this->band->txSpans[1].freqStep; - chnl.drMin = this->band->txSpans[1].drMin; - chnl.drMax = this->band->txSpans[1].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx++] = chnl; - } - } - - } - - #if RADIOLIB_DEBUG_PROTOCOL - this->printChannels(); - #endif - - return(true); -} - -uint8_t LoRaWANNode::getMacPayloadLength(uint8_t cid) { - for (LoRaWANMacSpec_t entry : MacTable) { - // cppcheck warns here we should use std::find_if, but some platforms may not have that - if (entry.cid == cid) { // cppcheck-suppress useStlAlgorithm - return entry.lenDn; - } - } - // no idea about the length - return 0; -} - -int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) { - uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_LINK_CHECK_REQ, &this->commandsDown, payload); - RADIOLIB_ASSERT(state); - - if(margin) { *margin = payload[0]; } - if(gwCnt) { *gwCnt = payload[1]; } - - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) { - uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; - int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, &this->commandsDown, payload); - RADIOLIB_ASSERT(state); - - if(gpsEpoch) { - *gpsEpoch = LoRaWANNode::ntoh(&payload[0]); - if(returnUnix) { - uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980) - *gpsEpoch += unixOffset; - } - } - if(fraction) { *fraction = payload[4]; } - - return(RADIOLIB_ERR_NONE); -} - -uint32_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. -// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma -void LoRaWANNode::performCSMA() { - - // Compute initial random back-off. - // When BO is reduced to zero, the function returns and the frame is transmitted. - uint32_t BO = this->phyLayer->random(1, this->backoffMax + 1); - while (BO > 0) { - // DIFS: Check channel for DIFS_slots - bool channelFreeDuringDIFS = true; - for (uint8_t i = 0; i < this->difsSlots; i++) { - if (performCAD()) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Occupied channel during DIFS"); - channelFreeDuringDIFS = false; - // Channel is occupied during DIFS, hop to another. - this->selectChannels(); - break; - } - } - // Start reducing BO counter if DIFS slot was free. - if (channelFreeDuringDIFS) { - // Continue decrementing BO with per each CAD reporting free channel. - while (BO > 0) { - if (performCAD()) { - RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Occupied channel during BO"); - // Channel is busy during CAD, hop to another and return to DIFS state again. - this->selectChannels(); - break; // Exit loop. Go back to DIFS state. - } - BO--; // Decrement BO by one if channel is free - } - } - } -} -bool LoRaWANNode::performCAD() { - int16_t state = this->phyLayer->scanChannel(); - if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { - return true; // Channel is busy - } - return false; // Channel is free -} - void LoRaWANNode::processAES(const 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; @@ -3036,6 +3251,28 @@ void LoRaWANNode::processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_ } } +int16_t LoRaWANNode::checkBufferCommon(uint8_t *buffer, uint16_t size) { + // check if there are actually values in the buffer + size_t i = 0; + for(; i < size; i++) { + if(buffer[i]) { + break; + } + } + if(i == size) { + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + // check integrity of the whole buffer (compare checksum to included checksum) + uint16_t checkSum = LoRaWANNode::checkSum16(buffer, size - 2); + uint16_t signature = LoRaWANNode::ntoh(&buffer[size - 2]); + if(signature != checkSum) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Calculated checksum: %04x, expected: %04x", checkSum, signature); + return(RADIOLIB_ERR_CHECKSUM_MISMATCH); + } + return(RADIOLIB_ERR_NONE); +} + uint16_t LoRaWANNode::checkSum16(uint8_t *key, uint16_t keyLen) { uint16_t checkSum = 0; for(uint16_t i = 0; i < keyLen; i += 2) { diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 5c2bbee9..65574e45 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -47,42 +47,39 @@ // fPort field #define RADIOLIB_LORAWAN_FPORT_MAC_COMMAND (0x00 << 0) // 7 0 payload contains MAC commands only +#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN (0x01 << 0) // 7 0 start of user-allowed fPort range +#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX (0xDF << 0) // 7 0 end of user-allowed fPort range #define RADIOLIB_LORAWAN_FPORT_TS009 (0xE0 << 0) // 7 0 fPort used for TS009 testing +#define RADIOLIB_LORAWAN_FPORT_TS011 (0xE2 << 0) // 7 0 fPort used for TS011 Forwarding #define RADIOLIB_LORAWAN_FPORT_RESERVED (0xE0 << 0) // 7 0 fPort values equal to and larger than this are reserved -// MAC commands - only those sent from end-device to gateway -#define RADIOLIB_LORAWAN_LINK_CHECK_REQ (0x02 << 0) // 7 0 MAC command: request to check connectivity to network -#define RADIOLIB_LORAWAN_LINK_ADR_ANS (0x03 << 0) // 7 0 answer to ADR change -#define RADIOLIB_LORAWAN_DUTY_CYCLE_ANS (0x04 << 0) // 7 0 answer to duty cycle change -#define RADIOLIB_LORAWAN_RX_PARAM_SETUP_ANS (0x05 << 0) // 7 0 answer to reception slot setup request -#define RADIOLIB_LORAWAN_DEV_STATUS_ANS (0x06 << 0) // 7 0 device status information -#define RADIOLIB_LORAWAN_NEW_CHANNEL_ANS (0x07 << 0) // 7 0 acknowledges change of a radio channel -#define RADIOLIB_LORAWAN_RX_TIMING_SETUP_ANS (0x08 << 0) // 7 0 acknowledges change of a reception slots timing - -#define RADIOLIB_LORAWAN_NOPTS_LEN (8) - // data rate encoding -#define RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K (0x01 << 7) // 7 7 FSK @ 50 kbps -#define RADIOLIB_LORAWAN_DATA_RATE_SF_12 (0x06 << 4) // 6 4 LoRa spreading factor: SF12 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_11 (0x05 << 4) // 6 4 SF11 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_10 (0x04 << 4) // 6 4 SF10 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_9 (0x03 << 4) // 6 4 SF9 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_8 (0x02 << 4) // 6 4 SF8 -#define RADIOLIB_LORAWAN_DATA_RATE_SF_7 (0x01 << 4) // 6 4 SF7 -#define RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ (0x00 << 2) // 3 2 LoRa bandwidth: 500 kHz -#define RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ (0x01 << 2) // 3 2 250 kHz -#define RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ (0x02 << 2) // 3 2 125 kHz -#define RADIOLIB_LORAWAN_DATA_RATE_BW_RESERVED (0x03 << 2) // 3 2 reserved value -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_5 (0x00 << 0) // 1 0 LoRa coding rate: 4/5 -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_6 (0x01 << 0) // 1 0 4/6 -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_7 (0x02 << 0) // 1 0 4/7 -#define RADIOLIB_LORAWAN_DATA_RATE_CR_4_8 (0x03 << 0) // 1 0 4/8 +#define RADIOLIB_LORAWAN_DATA_RATE_MODEM (0x03 << 6) // 7 6 modem mask +#define RADIOLIB_LORAWAN_DATA_RATE_LORA (0x00 << 6) // 7 6 use LoRa modem +#define RADIOLIB_LORAWAN_DATA_RATE_FSK (0x01 << 6) // 7 6 use FSK modem +#define RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS (0x02 << 6) // 7 6 use LR-FHSS modem +#define RADIOLIB_LORAWAN_DATA_RATE_SF (0x07 << 3) // 5 3 LoRa spreading factor mask +#define RADIOLIB_LORAWAN_DATA_RATE_SF_12 (0x05 << 3) // 5 3 LoRa spreading factor: SF12 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_11 (0x04 << 3) // 5 3 SF11 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_10 (0x03 << 3) // 5 3 SF10 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_9 (0x02 << 3) // 5 3 SF9 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_8 (0x01 << 3) // 5 3 SF8 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_7 (0x00 << 3) // 5 3 SF7 +#define RADIOLIB_LORAWAN_DATA_RATE_BW (0x03 << 1) // 2 1 bandwith mask +#define RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ (0x00 << 1) // 2 1 125 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ (0x01 << 1) // 2 1 250 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ (0x02 << 1) // 2 1 LoRa bandwidth: 500 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ (0x00 << 1) // 2 1 137 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ (0x01 << 1) // 2 1 336 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ (0x02 << 1) // 2 1 LR-FHSS bandwidth: 1523 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 (0x00 << 0) // 0 0 LR-FHSS coding rate: 1/3 +#define RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 (0x01 << 0) // 0 0 2/3 #define RADIOLIB_LORAWAN_DATA_RATE_UNUSED (0xFF << 0) // 7 0 unused data rate -#define RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK (0x00 << 0) -#define RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK (0x01 << 0) -#define RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH (0x02 << 0) -#define RADIOLIB_LORAWAN_CHANNEL_DIR_NONE (0x03 << 0) +// channels and channel plans +#define RADIOLIB_LORAWAN_UPLINK (0x00 << 0) +#define RADIOLIB_LORAWAN_DOWNLINK (0x01 << 0) +#define RADIOLIB_LORAWAN_DIR_RX2 (0x02 << 0) #define RADIOLIB_LORAWAN_BAND_DYNAMIC (0) #define RADIOLIB_LORAWAN_BAND_FIXED (1) #define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (15) @@ -118,14 +115,18 @@ #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) +// join accept key derivation layout +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_NONCE_POS (1) // regular keys +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS (4) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS (12) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_ADDR_POS (1) // relay keys + // join accept message variables #define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_0 (0x00 << 7) // 7 7 LoRaWAN revision: 1.0 #define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1 (0x01 << 7) // 7 7 1.1 @@ -135,6 +136,9 @@ #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) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_ROOT_WOR_S_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_WOR_S_INT_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_WOR_S_ENC_KEY (0x02) // frame header layout #define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16) @@ -172,8 +176,13 @@ // unused frame counter value #define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) +// TR013 CSMA recommended values +#define RADIOLIB_LORAWAN_DIFS_DEFAULT (2) +#define RADIOLIB_LORAWAN_BACKOFF_MAX_DEFAULT (6) +#define RADIOLIB_LORAWAN_MAX_CHANGES_DEFAULT (4) + // MAC commands -#define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (16) +#define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (23) #define RADIOLIB_LORAWAN_MAC_RESET (0x01) #define RADIOLIB_LORAWAN_MAC_LINK_CHECK (0x02) @@ -204,10 +213,10 @@ #define RADIOLIB_LORAWAN_MAX_NUM_ADR_COMMANDS (8) /*! - \struct LoRaWANMacSpec_t + \struct LoRaWANMacCommand_t \brief MAC command specification structure. */ -struct LoRaWANMacSpec_t { +struct LoRaWANMacCommand_t { /*! \brief Command ID */ const uint8_t cid; @@ -216,87 +225,58 @@ struct LoRaWANMacSpec_t { /*! \brief Downlink message length */ const uint8_t lenUp; + + /*! \brief Some commands must be resent until Class A downlink received */ + const bool persist; /*! \brief Whether this MAC command can be issued by the user or not */ const bool user; }; -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 } -}; +#define RADIOLIB_LORAWAN_MAC_COMMAND_NONE { .cid = 0, .lenDn = 0, .lenUp = 0, .persist = false, .user = false } -/*! - \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[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN]; - - /*! \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]; +constexpr LoRaWANMacCommand_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS] = { + { RADIOLIB_LORAWAN_MAC_RESET, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_LINK_CHECK, 2, 0, false, true }, + { RADIOLIB_LORAWAN_MAC_LINK_ADR, 4, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, 1, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, 4, 1, true, false }, + { RADIOLIB_LORAWAN_MAC_DEV_STATUS, 0, 2, false, false }, + { RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, 5, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, 1, 0, true, false }, + { RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, 1, 0, true, false }, + { RADIOLIB_LORAWAN_MAC_DL_CHANNEL, 4, 1, true, false }, + { RADIOLIB_LORAWAN_MAC_REKEY, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, 1, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_DEVICE_TIME, 5, 0, false, true }, + { RADIOLIB_LORAWAN_MAC_FORCE_REJOIN, 2, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, false, true }, }; #define RADIOLIB_LORAWAN_NONCES_VERSION_VAL (0x0001) enum LoRaWANSchemeBase_t { 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 + 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_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_NWK_SENC_KEY = RADIOLIB_LORAWAN_SESSION_START, // 16 bytes + RADIOLIB_LORAWAN_SESSION_APP_SKEY = RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_APP_SKEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_DEV_ADDR = RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY + RADIOLIB_AES128_KEY_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 @@ -306,22 +286,23 @@ enum LoRaWANSchemeSession_t { 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_RX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE + 1, // 4 bytes + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP + 4, // 1 byte + RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + 1, // 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 + RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS*5, // 16*4 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL = RADIOLIB_LORAWAN_SESSION_DL_CHANNELS + RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS*4, // 15 bytes + RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_UL + RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN, // 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_LINK_ADR = RADIOLIB_LORAWAN_SESSION_ADR_FCNT + sizeof(uint32_t), // 14 bytes + RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS = RADIOLIB_LORAWAN_SESSION_LINK_ADR + 14, // 2 bytes + RADIOLIB_LORAWAN_SESSION_FCNT_UP = RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS + 2, // 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 }; @@ -338,18 +319,25 @@ struct LoRaWANChannel_t { /*! \brief The channel number, as specified by defaults or the network */ uint8_t idx; - /*! \brief The channel frequency */ - float freq; + /*! \brief The channel frequency (coded in 100 Hz steps) */ + uint32_t freq; /*! \brief Minimum allowed datarate for this channel */ uint8_t drMin; /*! \brief Maximum allowed datarate for this channel (inclusive) */ uint8_t drMax; + + /*! \brief Datarate currently in use on this channel */ + uint8_t dr; + + /*! \brief Whether this channel is available for channel selection */ + bool available; }; // alias for unused channel -#define RADIOLIB_LORAWAN_CHANNEL_NONE { .enabled = false, .idx = RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE, .freq = 0, .drMin = 0, .drMax = 0 } +#define RADIOLIB_LORAWAN_CHANNEL_NONE { .enabled = false, .idx = RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE, .freq = 0, \ + .drMin = 0, .drMax = 0, .dr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, .available = false } /*! \struct LoRaWANChannelSpan_t @@ -360,11 +348,11 @@ struct LoRaWANChannelSpan_t { /*! \brief Total number of channels in the span */ uint8_t numChannels; - /*! \brief Center frequency of the first channel in span */ - float freqStart; + /*! \brief Center frequency of the first channel in span (coded in 100 Hz steps) */ + uint32_t freqStart; - /*! \brief Frequency step between adjacent channels */ - float freqStep; + /*! \brief Frequency step between adjacent channels (coded in 100 Hz steps) */ + uint32_t freqStep; /*! \brief Minimum allowed datarate for all channels in this span */ uint8_t drMin; @@ -373,11 +361,11 @@ struct LoRaWANChannelSpan_t { uint8_t drMax; /*! \brief Allowed data rates for a join request message */ - uint8_t joinRequestDataRate; + uint8_t drJoinRequest; }; // alias for unused channel span -#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .numChannels = 0, .freqStart = 0, .freqStep = 0, .drMin = 0, .drMax = 0, .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED } +#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .numChannels = 0, .freqStart = 0, .freqStep = 0, .drMin = 0, .drMax = 0, .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED } /*! \struct LoRaWANBand_t @@ -390,9 +378,12 @@ struct LoRaWANBand_t { /*! \brief Whether the channels are fixed per specification, or dynamically allocated through the network (plus defaults) */ uint8_t bandType; - /*! \brief Array of allowed maximum payload lengths for each data rate */ + /*! \brief Array of allowed maximum payload lengths for each data rate (global maximum) */ uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; + /*! \brief Array of allowed maximum payload lengths for each data rate (relay compatible) */ + uint8_t payloadLenRel[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; + /*! \brief Maximum allowed output power in this band in dBm */ int8_t powerMax; @@ -408,26 +399,34 @@ struct LoRaWANBand_t { /*! \brief Maximum dwell time per downlink message in milliseconds */ RadioLibTime_t dwellTimeDn; - /*! \brief A set of default uplink (TX) channels for frequency-type bands */ + /*! \brief Whether this band implements the MAC command TxParamSetupReq */ + bool txParamSupported; + + /*! \brief A set of default uplink (TX) channels for dynamic bands */ LoRaWANChannel_t txFreqs[3]; - /*! \brief A set of possible extra channels for the Join-Request message for frequency-type bands */ + /*! \brief A set of possible extra channels for the Join-Request message for dynamic bands */ LoRaWANChannel_t txJoinReq[3]; - /*! \brief The number of TX channel spans for mask-type bands */ + /*! \brief The number of TX channel spans for fixed bands */ uint8_t numTxSpans; - /*! \brief Default uplink (TX) channel spans for mask-type bands, including Join-Request parameters */ + /*! \brief Default uplink (TX) channel spans for fixed bands, including Join-Request parameters */ LoRaWANChannelSpan_t txSpans[2]; - /*! \brief Default downlink (RX1) channel span for mask-type bands */ + /*! \brief Default downlink (RX1) channel span for fixed bands */ LoRaWANChannelSpan_t rx1Span; - /*! \brief The base downlink data rate. Used to calculate data rate changes for adaptive data rate */ - uint8_t rx1DataRateBase; + uint8_t rx1DrTable[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES][8]; /*! \brief Backup channel for downlink (RX2) window */ LoRaWANChannel_t rx2; + + /*! \brief Relay channels for WoR uplink */ + LoRaWANChannel_t txWor[2]; + + /*! \brief Relay channels for ACK downlink */ + LoRaWANChannel_t txAck[2]; /*! \brief The corresponding datarates, bandwidths and coding rates for DR index */ uint8_t dataRates[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; @@ -515,6 +514,9 @@ struct LoRaWANEvent_t { /*! \brief Port number */ uint8_t fPort; + + /*! \brief Number of uplink transmissions (ADR)*/ + uint8_t nbTrans; }; /*! @@ -532,11 +534,6 @@ class LoRaWANNode { */ LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand = 0); - /*! - \brief Clear an active session, so that the device will have to rejoin the network. - */ - void clearSession(); - /*! \brief Returns the pointer to the internal buffer that holds the LW base parameters \returns Pointer to uint8_t array of size RADIOLIB_LORAWAN_NONCES_BUF_SIZE @@ -550,6 +547,11 @@ class LoRaWANNode { */ int16_t setBufferNonces(uint8_t* persistentBuffer); + /*! + \brief Clear an active session, so that the device will have to rejoin the network. + */ + void clearSession(); + /*! \brief Returns the pointer to the internal buffer that holds the LW session parameters \returns Pointer to uint8_t array of size RADIOLIB_LORAWAN_SESSION_BUF_SIZE @@ -572,14 +574,6 @@ class LoRaWANNode { */ void beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey); - /*! - \brief Join network by restoring OTAA session or performing over-the-air activation. By this procedure, - the device will perform an exchange with the network server and set all necessary configuration. - \param joinDr The datarate at which to send the join-request and any subsequent uplinks (unless ADR is enabled) - \returns \ref status_codes - */ - int16_t activateOTAA(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, LoRaWANJoinEvent_t *joinEvent = NULL); - /*! \brief Set the device credentials and activation configuration \param addr Device address. @@ -591,6 +585,14 @@ class LoRaWANNode { */ void beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey); + /*! + \brief Join network by restoring OTAA session or performing over-the-air activation. By this procedure, + the device will perform an exchange with the network server and set all necessary configuration. + \param joinDr The datarate at which to send the join-request and any subsequent uplinks (unless ADR is enabled) + \returns \ref status_codes + */ + virtual int16_t activateOTAA(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, LoRaWANJoinEvent_t *joinEvent = NULL); + /*! \brief Join network by restoring ABP session or performing over-the-air activation. In this procedure, all necessary configuration must be provided by the user. @@ -602,88 +604,6 @@ class LoRaWANNode { /*! \brief Whether there is an ongoing session active */ bool isActivated(); - /*! - \brief Configure the Rx2 datarate for ABP mode. - This should not be needed for LoRaWAN 1.1 as it is configured through the first downlink. - \param dr The datarate to be used for listening for downlinks in Rx2. - \returns \ref status_codes - */ - int16_t setRx2Dr(uint8_t dr); - - /*! - \brief Add a MAC command to the uplink queue. - 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 \ref status_codes - */ - int16_t sendMacCommandReq(uint8_t cid); - - #if defined(RADIOLIB_BUILD_ARDUINO) - /*! - \brief Send a message to the server. - \param str Address of Arduino String that will be transmitted. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t uplink(String& str, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* event = NULL); - #endif - - /*! - \brief Send a message to the server. - \param str C-string that will be transmitted. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t uplink(const char* str, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* event = NULL); - - /*! - \brief Send a message to the server. - \param data Data to send. - \param len Length of the data. - \param fPort Port number to send the message to. - \param isConfirmed Whether to send a confirmed uplink or not. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t uplink(uint8_t* data, size_t len, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* event = NULL); - - #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. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t downlink(String& str, LoRaWANEvent_t* event = NULL); - #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. - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event = NULL); - - /*! - \brief Wait for downlink, simplified to allow for simpler sendReceive - \param event Pointer to a structure to store extra information about the event - (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. - \returns \ref status_codes - */ - int16_t downlink(LoRaWANEvent_t* event = NULL); - #if defined(RADIOLIB_BUILD_ARDUINO) /*! \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. @@ -697,7 +617,7 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \returns \ref status_codes */ - int16_t sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); #endif /*! @@ -713,7 +633,7 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \returns \ref status_codes */ - int16_t sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); /*! \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. @@ -729,7 +649,7 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \returns \ref status_codes */ - int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); /*! \brief Send a message to the server and wait for a downlink but don't bother the user with downlink contents @@ -743,7 +663,90 @@ class LoRaWANNode { (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. \returns \ref status_codes */ - int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort = 1, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + virtual int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort = 1, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + + /*! + \brief Add a MAC command to the uplink queue. + 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 \ref status_codes + */ + int16_t sendMacCommandReq(uint8_t cid); + + /*! + \brief Returns the quality of connectivity after requesting a LinkCheck MAC command. + Returns 'true' if a network response was successfully parsed. + Returns 'false' if there was no network response / parsing failed. + \param margin Link margin in dB of LinkCheckReq demodulation at gateway side. + \param gwCnt Number of gateways that received the LinkCheckReq. + \returns \ref status_codes + */ + int16_t getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt); + + /*! + \brief Returns the network time after requesting a DeviceTime MAC command. + Returns 'true' if a network response was successfully parsed. + Returns 'false' if there was no network response / parsing failed. + \param gpsEpoch Number of seconds since GPS epoch (Jan. 6th 1980) + \param fraction Fractional-second, in 1/256-second steps + \param returnUnix If true, returns Unix timestamp instead of GPS (default true) + \returns \ref status_codes + */ + int16_t getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix = true); + + /*! + \brief Set uplink datarate. This should not be used when ADR is enabled. + \param drUp Datarate to use for uplinks. + \returns \ref status_codes + */ + int16_t setDatarate(uint8_t drUp); + + /*! + \brief Configure TX power of the radio module. + \param txPower Output power during TX mode to be set in dBm. + \returns \ref status_codes + */ + int16_t setTxPower(int8_t txPower); + + /*! + \brief Configure the Rx2 datarate for ABP mode. + This should not be needed for LoRaWAN 1.1 as it is configured through the first downlink. + \param dr The datarate to be used for listening for downlinks in Rx2. + \returns \ref status_codes + */ + int16_t setRx2Dr(uint8_t dr); + + /*! + \brief Toggle ADR to on or off. + \param enable Whether to disable ADR or not. + */ + void setADR(bool enable = true); + + /*! + \brief Toggle adherence to dutyCycle limits to on or off. + \param enable Whether to adhere to dutyCycle limits or not (default true). + \param msPerHour The maximum allowed Time-on-Air per hour in milliseconds + (default 0 = maximum allowed for configured band). + */ + void setDutyCycle(bool enable = true, RadioLibTime_t msPerHour = 0); + + /*! + \brief Toggle adherence to dwellTime limits to on or off. + \param enable Whether to adhere to dwellTime limits or not (default true). + \param msPerUplink The maximum allowed Time-on-Air per uplink in milliseconds + (default 0 = maximum allowed for configured band). + */ + void setDwellTime(bool enable, RadioLibTime_t msPerUplink = 0); + + /*! + \brief Configures CSMA for LoRaWAN as per TR013, LoRa Alliance. + \param csmaEnabled Enable/disable CSMA for LoRaWAN. + \param maxChanges Maximum number of channel hops if channel is used (default 4). + \param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO (default). + \param difsSlots Num of CADs to estimate a clear CH (default 2). + */ + void setCSMA(bool csmaEnabled, uint8_t maxChanges = 4, uint8_t backoffMax = 0, uint8_t difsSlots = 2); /*! \brief Set device status. @@ -752,6 +755,15 @@ class LoRaWANNode { */ void setDeviceStatus(uint8_t battLevel); + /*! + \brief Set the exact time a transmission should occur. Note: this is the internal clock time. + On Arduino platforms, this is the usual time supplied by millis(). + If the supplied time is larger than the current time, sendReceive() or uplink() will delay + until the scheduled time. + \param tUplink Transmission time in milliseconds, based on internal clock. + */ + void scheduleTransmission(RadioLibTime_t tUplink); + /*! \brief Returns the last uplink's frame counter; also 0 if no uplink occured yet. @@ -778,25 +790,16 @@ class LoRaWANNode { void resetFCntDown(); /*! - \brief Set uplink datarate. This should not be used when ADR is enabled. - \param drUp Datarate to use for uplinks. - \returns \ref status_codes + \brief Returns the DevAddr of the device, regardless of OTAA or ABP mode + \returns 4-byte DevAddr */ - int16_t setDatarate(uint8_t drUp); + uint32_t getDevAddr(); /*! - \brief Toggle ADR to on or off. - \param enable Whether to disable ADR or not. + \brief Get the Time-on-air of the last uplink message (in milliseconds). + \returns (RadioLibTime_t) time-on-air (ToA) of last uplink message (in milliseconds). */ - void setADR(bool enable = true); - - /*! - \brief Toggle adherence to dutyCycle limits to on or off. - \param enable Whether to adhere to dutyCycle limits or not (default true). - \param msPerHour The maximum allowed Time-on-Air per hour in milliseconds - (default 0 = maximum allowed for configured band). - */ - void setDutyCycle(bool enable = true, RadioLibTime_t msPerHour = 0); + RadioLibTime_t getLastToA(); /*! \brief Calculate the minimum interval to adhere to a certain dutyCycle. @@ -810,60 +813,12 @@ class LoRaWANNode { /*! \brief Returns time in milliseconds until next uplink is available under dutyCycle limits */ RadioLibTime_t timeUntilUplink(); - /*! - \brief Toggle adherence to dwellTime limits to on or off. - \param enable Whether to adhere to dwellTime limits or not (default true). - \param msPerUplink The maximum allowed Time-on-Air per uplink in milliseconds - (default 0 = maximum allowed for configured band). - */ - void setDwellTime(bool enable, RadioLibTime_t msPerUplink = 0); - /*! \brief Returns the maximum payload given the currently present dwell time limits. WARNING: the addition of MAC commands may cause uplink errors; if you want to be sure that your payload fits within dwell time limits, subtract 16 from the result! */ - uint8_t maxPayloadDwellTime(); - - /*! - \brief Configure TX power of the radio module. - \param txPower Output power during TX mode to be set in dBm. - \returns \ref status_codes - */ - int16_t setTxPower(int8_t txPower); - - /*! - \brief Returns the quality of connectivity after requesting a LinkCheck MAC command. - Returns 'true' if a network response was successfully parsed. - Returns 'false' if there was no network response / parsing failed. - \param margin Link margin in dB of LinkCheckReq demodulation at gateway side. - \param gwCnt Number of gateways that received the LinkCheckReq. - \returns \ref status_codes - */ - int16_t getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt); - - /*! - \brief Returns the network time after requesting a DeviceTime MAC command. - Returns 'true' if a network response was successfully parsed. - Returns 'false' if there was no network response / parsing failed. - \param gpsEpoch Number of seconds since GPS epoch (Jan. 6th 1980) - \param fraction Fractional-second, in 1/256-second steps - \param returnUnix If true, returns Unix timestamp instead of GPS (default true) - \returns \ref status_codes - */ - int16_t getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix = true); - - /*! - \brief Returns the DevAddr of the device, regardless of OTAA or ABP mode - \returns 4-byte DevAddr - */ - uint32_t getDevAddr(); - - /*! - \brief Get the Time-on-air of the last uplink message (in milliseconds). - \returns (RadioLibTime_t) time-on-air (ToA) of last uplink message (in milliseconds). - */ - RadioLibTime_t getLastToA(); + uint8_t maxUplinkLen(); /*! \brief TS009 Protocol Specification Verification switch @@ -893,24 +848,16 @@ class LoRaWANNode { static int16_t checkBufferCommon(uint8_t *buffer, uint16_t size); - void activateCommon(uint8_t initialDr); - // a buffer that holds all LW base parameters that should persist at all times! uint8_t bufferNonces[RADIOLIB_LORAWAN_NONCES_BUF_SIZE] = { 0 }; // a buffer that holds all LW session parameters that preferably persist, but can be afforded to get lost uint8_t bufferSession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE] = { 0 }; - LoRaWANMacCommandQueue_t commandsUp = { - .numCommands = 0, - .len = 0, - .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } }, - }; - LoRaWANMacCommandQueue_t commandsDown = { - .numCommands = 0, - .len = 0, - .commands = { { .cid = 0, .payload = { 0 }, .len = 0, .repeat = 0, } }, - }; + uint8_t fOptsUp[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN] = { 0 }; + uint8_t fOptsDown[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN] = { 0 }; + uint8_t fOptsUpLen = 0; + uint8_t fOptsDownLen = 0; uint16_t lwMode = RADIOLIB_LORAWAN_MODE_NONE; uint8_t lwClass = RADIOLIB_LORAWAN_CLASS_A; @@ -955,6 +902,7 @@ class LoRaWANNode { // ADR is enabled by default bool adrEnabled = true; + bool adrAckReq = false; // duty cycle is set upon initialization and activated in regions that impose this bool dutyCycleEnabled = false; @@ -965,11 +913,17 @@ class LoRaWANNode { uint16_t dwellTimeUp = 0; bool dwellTimeEnabledDn = false; uint16_t dwellTimeDn = 0; - - // enable/disable CSMA for LoRaWAN - bool enableCSMA; - // number of backoff slots to be decremented after DIFS phase. 0 to disable BO. + RadioLibTime_t tUplink = 0; // scheduled uplink transmission time (internal clock) + RadioLibTime_t tDownlink = 0; // time at end of downlink reception + + // enable/disable CSMA for LoRaWAN + bool csmaEnabled; + + // maximum number of channel hops during CSMA + uint8_t maxChanges; + + // number of backoff slots to be checked after DIFS phase. // A random BO avoids collisions in the case where two or more nodes start the CSMA // process at the same time. uint8_t backoffMax; @@ -978,16 +932,15 @@ class LoRaWANNode { uint8_t difsSlots; // available channel frequencies from list passed during OTA activation - LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; + LoRaWANChannel_t channelPlan[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; - // currently configured channels for TX and RX1 - LoRaWANChannel_t currentChannels[2] = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE }; + // currently configured channels for TX, RX1, RX2 + LoRaWANChannel_t channels[3] = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }; - // currently configured datarates for TX and RX1 - uint8_t dataRates[2] = { RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED }; - - // Rx2 channel properties - may be changed by MAC command - LoRaWANChannel_t rx2 = RADIOLIB_LORAWAN_CHANNEL_NONE; + // delays between the uplink and RX1/2 windows + // the first field is meaningless, but is used for offsetting for Rx windows 1 and 2 + RadioLibTime_t rxDelays[3] = { 0, RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS }; // offset between TX and RX1 (such that RX1 has equal or lower DR) uint8_t rx1DrOffset = 0; @@ -1004,9 +957,6 @@ class LoRaWANNode { // timestamp when the Rx1/2 windows were closed (timeout or uplink received) RadioLibTime_t rxDelayEnd = 0; - // delays between the uplink and RX1/2 windows - RadioLibTime_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS }; - // device status - battery level uint8_t battLevel = 0xFF; @@ -1016,14 +966,109 @@ class LoRaWANNode { // save the selected sub-band in case this must be restored in ADR control uint8_t subBand = 0; - // initalize the Nonces buffer after beginX() has been called - void createNonces(); + // allow port 226 for devices implementing TS011 + bool TS011 = false; // this will reset the device credentials, so the device starts completely new void clearNonces(); - // wait for, open and listen during Rx1 and Rx2 windows; only performs listening - int16_t downlinkCommon(); + // start a fresh session using default parameters + void createSession(uint16_t lwMode, uint8_t initialDr); + + // setup Join-Request payload + void composeJoinRequest(uint8_t* joinRequestMsg); + + // extract Join-Accept payload and start a new session + int16_t processJoinAccept(LoRaWANJoinEvent_t *joinEvent); + + // a join-accept can piggy-back a set of channels or channel masks + void processCFList(uint8_t* cfList); + + int16_t isValidUplink(uint8_t* len, uint8_t fPort); + + // perform ADR backoff + void adrBackoff(); + + // create an encrypted uplink buffer, composing metadata, user data and MAC data + void composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t lenOut, uint8_t fPort, bool isConfirmed); + + // generate and set the MIC of an uplink buffer (depends on selected channels) + void micUplink(uint8_t* inOut, uint8_t lenInOut); + + // transmit uplink buffer on a specified channel + int16_t transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len); + + // wait for, open and listen during receive windows; only performs listening + int16_t receiveCommon(uint8_t dir, LoRaWANChannel_t* dlChannels, RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference); + + // extract downlink payload and process MAC commands + int16_t parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event = NULL); + + // execute mac command, return the number of processed bytes for sequential processing + bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn); + bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut); + + // possible override for additional MAC commands that are not in the base specification + virtual bool derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut); + + void preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt); + + void postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen); + + static int16_t getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd); + + // possible override for additional MAC commands that are not in the base specification + static int16_t derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd); + + // + static int16_t getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive = false); + + static bool isPersistentMacCommand(uint8_t cid, uint8_t dir); + + // push MAC command to queue, done by copy + static int16_t pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir); + + static int16_t getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir); + + // delete a specific MAC command from queue, indicated by the command ID + static int16_t deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir); + + static void clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir); + + // configure the common physical layer properties (frequency, sync word etc.) + int16_t setPhyProperties(LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre = 0); + + // Performs CSMA as per LoRa Alliance Technical Recommendation 13 (TR-013). + bool csmaChannelClear(uint8_t difs, uint8_t numBackoff); + + // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. + bool cadChannelClear(); + + void getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45); + + // setup uplink/downlink channel data rates and frequencies + // for dynamic channels, there is a small set of predefined channels + // in case of JoinRequest, add some optional extra frequencies + void selectChannelPlanDyn(bool joinRequest = false); + + // setup uplink/downlink channel data rates and frequencies + // for fixed bands, we only allow one sub-band at a time to be selected + void selectChannelPlanFix(); + + // get the number of available channels, + // along with a 16-bit mask indicating which channels can be used next for uplink/downlink + uint8_t getAvailableChannels(uint16_t* mask); + + // (re)set/restore which channels can be used next for uplink/downlink + void setAvailableChannels(uint16_t mask); + + // select a set of random TX/RX channels for up- and downlink + virtual int16_t selectChannels(); + + bool applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45); + + // print the available channels through debug + void printChannels(); // method to generate message integrity code uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key); @@ -1032,74 +1077,10 @@ 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 common physical layer properties (preamble, sync word etc.) - // channels must be configured separately by setupChannelsDyn()! - int16_t setPhyProperties(uint8_t dir); - - // print the available channels through debug - void printChannels(); - - // setup uplink/downlink channel data rates and frequencies - // for dynamic channels, there is a small set of predefined channels - // in case of JoinRequest, add some optional extra frequencies - int16_t setupChannelsDyn(bool joinRequest = false); - - // setup uplink/downlink channel data rates and frequencies - // for fixed bands, we only allow one sub-band at a time to be selected - int16_t setupChannelsFix(uint8_t subBand); - - // a join-accept can piggy-back a set of channels or channel masks - int16_t processCFList(uint8_t* cfList); - - // select a set of random TX/RX channels for up- and downlink - int16_t selectChannels(); - // find the first usable data rate for the given band int16_t findDataRate(uint8_t dr, DataRate_t* dataRate); - // restore all available channels from persistent storage - int16_t restoreChannels(); - - // parse a MAC command queue into a buffer (uplinks) - void macQueueToBuff(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer); - - // parse a MAC buffer into a command queue (downlinks) - void macBufftoQueue(LoRaWANMacCommandQueue_t* queue, uint8_t* buffer, uint8_t len); - - // push MAC command to queue, done by copy - int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue); - - // delete a specific MAC command from queue, indicated by the command ID - // if a payload pointer is supplied, this returns the payload of the MAC command - int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue, uint8_t* payload = NULL); - - // execute mac command, return the number of processed bytes for sequential processing - bool execMacCommand(LoRaWANMacCommand_t* cmd); - - // apply a channel mask to a set of readily defined channels (dynamic bands only) - bool applyChannelMaskDyn(uint8_t chMaskCntl, uint16_t chMask); - - // define or delete channels from a fixed set of channels (fixed bands only) - bool applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask); - - // get the payload length for a specific MAC command - uint8_t getMacPayloadLength(uint8_t cid); - - /*! - \brief Configures CSMA for LoRaWAN as per TR-13, LoRa Alliance. - \param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO. - \param difsSlots Num of CADs to estimate a clear CH. - \param enableCSMA enable/disable CSMA for LoRaWAN. - */ - void setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA = false); - - // Performs CSMA as per LoRa Alliance Technical Recommendation 13 (TR-013). - void performCSMA(); - - // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. - bool performCAD(); - - // function to encrypt and decrypt payloads + // function to encrypt and decrypt payloads (regular uplink/downlink) void processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter); // 16-bit checksum method that takes a uint8_t array of even length and calculates the checksum @@ -1112,6 +1093,9 @@ class LoRaWANNode { // host-to-network conversion method - takes data from host variable and and converts it to network packet endians template static void hton(uint8_t* buff, T val, size_t size = 0); + + friend class LoRaWANRelay; + friend class LoRaWANRelayedNode; }; #endif diff --git a/src/protocols/LoRaWAN/LoRaWANBands.cpp b/src/protocols/LoRaWAN/LoRaWANBands.cpp index f67b8573..4e4fd981 100644 --- a/src/protocols/LoRaWAN/LoRaWANBands.cpp +++ b/src/protocols/LoRaWAN/LoRaWANBands.cpp @@ -20,16 +20,18 @@ const LoRaWANBand_t* LoRaWANBands[RADIOLIB_LORAWAN_NUM_SUPPORTED_BANDS] = { const LoRaWANBand_t EU868 = { .bandNum = BandEU868, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 250, 250, 58, 123, 58, 123, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 59, 123, 230, 230, 230, 230, 58, 123, 58, 123, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 868.100, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 868.300, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 868.500, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 8681000, .drMin = 0, .drMax = 5, .dr = 5 }, + { .enabled = true, .idx = 1, .freq = 8683000, .drMin = 0, .drMax = 5, .dr = 5 }, + { .enabled = true, .idx = 2, .freq = 8685000, .drMin = 0, .drMax = 5, .dr = 5 }, }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -42,21 +44,45 @@ const LoRaWANBand_t EU868 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 869.525, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 8695250, .drMin = 0, .drMax = 0, .dr = 0 }, + .txWor = { + { .enabled = true, .idx = 0, .freq = 8651000, .drMin = 2, .drMax = 2, .dr = 2 }, + { .enabled = false, .idx = 1, .freq = 8655000, .drMin = 2, .drMax = 2, .dr = 2 } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 8653000, .drMin = 2, .drMax = 2, .dr = 2 }, + { .enabled = false, .idx = 1, .freq = 8659000, .drMin = 2, .drMax = 2, .dr = 2 } + }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED @@ -66,12 +92,14 @@ const LoRaWANBand_t EU868 = { const LoRaWANBand_t US915 = { .bandNum = BandUS915, .bandType = RADIOLIB_LORAWAN_BAND_FIXED, - .payloadLenMax = { 19, 61, 133, 250, 250, 0, 0, 0, 41, 117, 230, 230, 230, 230, 0 }, + .payloadLenMax = { 19, 61, 133, 250, 250, 58, 133, 0, 61, 137, 250, 250, 250, 250, 0 }, + .payloadLenRel = { 19, 61, 133, 230, 230, 58, 133, 0, 61, 137, 230, 230, 230, 230, 0 }, .powerMax = 30, .powerNumSteps = 10, .dutyCycle = 0, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -86,46 +114,62 @@ const LoRaWANBand_t US915 = { .txSpans = { { .numChannels = 64, - .freqStart = 902.300, - .freqStep = 0.200, + .freqStart = 9023000, + .freqStep = 2000, .drMin = 0, .drMax = 3, - .joinRequestDataRate = 0 + .drJoinRequest = 0 }, { .numChannels = 8, - .freqStart = 903.000, - .freqStep = 1.600, + .freqStart = 9030000, + .freqStep = 16000, .drMin = 4, .drMax = 4, - .joinRequestDataRate = 4 + .drJoinRequest = 4 } }, .rx1Span = { .numChannels = 8, - .freqStart = 923.300, - .freqStep = 0.600, + .freqStart = 9233000, + .freqStep = 6000, .drMin = 8, .drMax = 13, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED }, - .rx1DataRateBase = 10, - .rx2 = { .enabled = true, .idx = 0, .freq = 923.300, .drMin = 8, .drMax = 8 }, + .rx1DrTable = { + { 10, 9, 8, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 11, 10, 9, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 12, 11, 10, 9, 0xFF, 0xFF, 0xFF, 0xFF }, + { 13, 12, 11, 10, 0xFF, 0xFF, 0xFF, 0xFF }, + { 13, 13, 12, 11, 0xFF, 0xFF, 0xFF, 0xFF }, + { 10, 9, 8, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 11, 10, 9, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9233000, .drMin = 8, .drMax = 8 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; @@ -133,16 +177,18 @@ const LoRaWANBand_t US915 = { const LoRaWANBand_t EU433 = { .bandNum = BandEU433, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 12, .powerNumSteps = 5, .dutyCycle = 36000, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 433.175, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 433.375, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 433.575, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 4331750, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 4333750, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 2, .freq = 4335750, .drMin = 0, .drMax = 5 }, }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -155,17 +201,33 @@ const LoRaWANBand_t EU433 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 434.665, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 4346650, .drMin = 0, .drMax = 0 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -179,12 +241,14 @@ const LoRaWANBand_t EU433 = { const LoRaWANBand_t AU915 = { .bandNum = BandAU915, .bandType = RADIOLIB_LORAWAN_BAND_FIXED, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 0, 41, 117, 230, 230, 230, 230, 0 }, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 250, 58, 61, 137, 250, 250, 250, 250, 0 }, + .payloadLenRel = { 59, 59, 59, 123, 230, 230, 230, 58, 61, 137, 230, 230, 230, 230, 0 }, .powerMax = 30, .powerNumSteps = 10, .dutyCycle = 0, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = 0, + .txParamSupported = true, // conflict: not implemented according to RP v1.1 .txFreqs = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -199,46 +263,62 @@ const LoRaWANBand_t AU915 = { .txSpans = { { .numChannels = 64, - .freqStart = 915.200, - .freqStep = 0.200, + .freqStart = 9152000, + .freqStep = 2000, .drMin = 0, .drMax = 5, - .joinRequestDataRate = 2 + .drJoinRequest = 2 }, { .numChannels = 8, - .freqStart = 915.900, - .freqStep = 1.600, + .freqStart = 9159000, + .freqStep = 16000, .drMin = 6, .drMax = 6, - .joinRequestDataRate = 6 + .drJoinRequest = 6 } }, .rx1Span = { .numChannels = 8, - .freqStart = 923.300, - .freqStep = 0.600, + .freqStart = 9233000, + .freqStep = 6000, .drMin = 8, .drMax = 13, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED }, - .rx1DataRateBase = 8, - .rx2 = { .enabled = true, .idx = 0, .freq = 923.300, .drMin = 8, .drMax = 8 }, + .rx1DrTable = { + { 8, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 9, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 10, 9, 8, 8, 8, 8, 0xFF, 0xFF }, + { 11, 10, 9, 8, 8, 8, 0xFF, 0xFF }, + { 12, 11, 10, 9, 8, 8, 0xFF, 0xFF }, + { 13, 12, 11, 10, 9, 8, 0xFF, 0xFF }, + { 13, 13, 12, 11, 10, 9, 0xFF, 0xFF }, + { 9, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9233000, .drMin = 8, .drMax = 8 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED } }; @@ -246,12 +326,14 @@ const LoRaWANBand_t AU915 = { const LoRaWANBand_t CN500 = { .bandNum = BandCN500, .bandType = RADIOLIB_LORAWAN_BAND_FIXED, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 19, .powerNumSteps = 7, .dutyCycle = 0, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -266,31 +348,47 @@ const LoRaWANBand_t CN500 = { .txSpans = { { .numChannels = 96, - .freqStart = 470.300, - .freqStep = 0.200, + .freqStart = 4703000, + .freqStep = 2000, .drMin = 0, .drMax = 5, - .joinRequestDataRate = 0 + .drJoinRequest = 0 }, RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = { .numChannels = 48, - .freqStart = 500.300, - .freqStep = 0.200, + .freqStart = 5003000, + .freqStep = 2000, .drMin = 0, .drMax = 5, - .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED }, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 505.300, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 1, 1, 1, 1, 1, 0xFF, 0xFF }, + { 2, 1, 1, 1, 1, 1, 0xFF, 0xFF }, + { 3, 2, 1, 1, 1, 1, 0xFF, 0xFF }, + { 4, 3, 2, 1, 1, 1, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 1, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 5053000, .drMin = 0, .drMax = 0 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -306,15 +404,17 @@ const LoRaWANBand_t CN500 = { const LoRaWANBand_t AS923 = { .bandNum = BandAS923, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 123, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 923.200, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 923.400, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9232000, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 9234000, .drMin = 0, .drMax = 5 }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -328,17 +428,33 @@ const LoRaWANBand_t AS923 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 923.200, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9232000, .drMin = 2, .drMax = 2 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -352,15 +468,17 @@ const LoRaWANBand_t AS923 = { const LoRaWANBand_t AS923_2 = { .bandNum = BandAS923_2, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 123, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 921.400, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 921.600, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9214000, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 9216000, .drMin = 0, .drMax = 5 }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -374,17 +492,33 @@ const LoRaWANBand_t AS923_2 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 921.400, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9214000, .drMin = 2, .drMax = 2 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -398,15 +532,17 @@ const LoRaWANBand_t AS923_2 = { const LoRaWANBand_t AS923_3 = { .bandNum = BandAS923_3, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 123, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 916.600, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 916.800, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9166000, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 9168000, .drMin = 0, .drMax = 5 }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -420,17 +556,33 @@ const LoRaWANBand_t AS923_3 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 916.600, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9166000, .drMin = 2, .drMax = 2 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -444,15 +596,17 @@ const LoRaWANBand_t AS923_3 = { const LoRaWANBand_t AS923_4 = { .bandNum = BandAS923_4, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 123, 123, 250, 250, 250, 250, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 123, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 16, .powerNumSteps = 7, .dutyCycle = 36000, .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 917.300, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 917.500, .drMin = 0, .drMax = 5}, + { .enabled = true, .idx = 0, .freq = 9173000, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 9175000, .drMin = 0, .drMax = 5 }, RADIOLIB_LORAWAN_CHANNEL_NONE }, .txJoinReq = { @@ -466,17 +620,33 @@ const LoRaWANBand_t AS923_4 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 917.300, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9173000, .drMin = 2, .drMax = 2 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -490,16 +660,18 @@ const LoRaWANBand_t AS923_4 = { const LoRaWANBand_t KR920 = { .bandNum = BandKR920, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 14, .powerNumSteps = 7, .dutyCycle = 0, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 922.100, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 922.300, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 922.500, .drMin = 0, .drMax = 5} + { .enabled = true, .idx = 0, .freq = 9221000, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 9223000, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 2, .freq = 9225000, .drMin = 0, .drMax = 5 } }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -512,15 +684,31 @@ const LoRaWANBand_t KR920 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 921.900, .drMin = 0, .drMax = 0 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9219000, .drMin = 0, .drMax = 0 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, @@ -536,16 +724,18 @@ const LoRaWANBand_t KR920 = { const LoRaWANBand_t IN865 = { .bandNum = BandIN865, .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, - .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenMax = { 59, 59, 59, 123, 250, 250, 0, 250, 0, 0, 0, 0, 0, 0, 0 }, + .payloadLenRel = { 59, 59, 59, 123, 230, 230, 0, 230, 0, 0, 0, 0, 0, 0, 0 }, .powerMax = 30, .powerNumSteps = 10, .dutyCycle = 0, .dwellTimeUp = 0, .dwellTimeDn = 0, + .txParamSupported = false, .txFreqs = { - { .enabled = true, .idx = 0, .freq = 865.0625, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 1, .freq = 865.4025, .drMin = 0, .drMax = 5}, - { .enabled = true, .idx = 2, .freq = 865.9850, .drMin = 0, .drMax = 5} + { .enabled = true, .idx = 0, .freq = 8650625, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 1, .freq = 8654025, .drMin = 0, .drMax = 5 }, + { .enabled = true, .idx = 2, .freq = 8659850, .drMin = 0, .drMax = 5 } }, .txJoinReq = { RADIOLIB_LORAWAN_CHANNEL_NONE, @@ -558,17 +748,33 @@ const LoRaWANBand_t IN865 = { RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE }, .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, - .rx1DataRateBase = 0, - .rx2 = { .enabled = true, .idx = 0, .freq = 866.550, .drMin = 2, .drMax = 2 }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, + { 1, 0, 0, 0, 0, 0, 2, 3 }, + { 2, 1, 0, 0, 0, 0, 3, 4 }, + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 5 }, + { 5, 4, 3, 2, 1, 0, 5, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 8665500, .drMin = 2, .drMax = 2 }, .dataRates = { - RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, - RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ | RADIOLIB_LORAWAN_DATA_RATE_CR_4_5, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, - RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K, + RADIOLIB_LORAWAN_DATA_RATE_FSK, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED, RADIOLIB_LORAWAN_DATA_RATE_UNUSED,