diff --git a/keywords.txt b/keywords.txt index 3f2da44a..ad416f4e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -294,6 +294,7 @@ wipe KEYWORD2 restore KEYWORD2 beginOTAA KEYWORD2 beginABP KEYWORD2 +isJoined KEYWORD2 saveSession KEYWORD2 sendMacCommandReq KEYWORD2 uplink KEYWORD2 @@ -312,7 +313,6 @@ timeUntilUplink KEYWORD2 setDwellTime KEYWORD2 maxPayloadDwellTime KEYWORD2 setTxPower KEYWORD2 -selectSubband KEYWORD2 setCSMA KEYWORD2 getMacLinkCheckAns KEYWORD2 getMacDeviceTimeAns KEYWORD2 diff --git a/src/TypeDef.h b/src/TypeDef.h index 64d1a1a2..f82e787b 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -553,11 +553,6 @@ */ #define RADIOLIB_ERR_A_FCNT_DOWN_INVALID (-1114) -/*! - \brief Datarate requested by user is invalid. -*/ -#define RADIOLIB_ERR_DATA_RATE_INVALID (-1115) - /*! \} */ diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 6e7e0d6c..ba2c6be0 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -28,24 +28,12 @@ uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_ return(dr); } -LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) { +LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand) { this->phyLayer = phy; this->band = band; this->rx2 = this->band->rx2; - this->dutyCycle = this->band->dutyCycle; - if(this->dutyCycle > 0) { - this->dutyCycleEnabled = true; - } - this->dwellTimeUp = this->band->dwellTimeUp; - if(this->dwellTimeUp > 0) { - this->dwellTimeEnabledUp = true; - } - this->dwellTimeDn = this->band->dwellTimeDn; - if(this->dwellTimeDn) { - this->dwellTimeEnabledDn = true; - } this->txPowerMax = this->band->powerMax; - this->txPowerCur = 0; // start at 0 offset = full power + this->subBand = subBand; this->difsSlots = 2; this->backoffMax = 6; this->enableCSMA = false; @@ -63,7 +51,6 @@ void LoRaWANNode::wipe() { mod->hal->wipePersistentStorage(); } -// TODO do not return status code, but return LoRaWAN mode (OTAA, ABP, none) int16_t LoRaWANNode::restore() { // if already joined, ignore if(this->activeMode != RADIOLIB_LORAWAN_MODE_NONE) { @@ -218,7 +205,11 @@ int16_t LoRaWANNode::restoreFcntUp() { int16_t LoRaWANNode::restoreChannels() { // first do the default channels - this->setupChannels(nullptr); + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->setupChannelsDyn(false); + } else { // RADIOLIB_LORAWAN_BAND_FIXED + this->setupChannelsFix(this->subBand); + } Module* mod = this->phyLayer->getMod(); uint8_t bufferZeroes[5] = { 0 }; @@ -252,16 +243,17 @@ int16_t LoRaWANNode::restoreChannels() { } } - } else { - uint8_t numBytes = 8 * MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; + } else { // RADIOLIB_LORAWAN_BAND_FIXED + uint8_t numADRCommands = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID); + uint8_t numBytes = numADRCommands * MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; uint8_t buffer[numBytes] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), buffer, numBytes); LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - for(int i = 0; i < 8; i++) { + for(int i = 0; i < numADRCommands; i++) { + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; memcpy(cmd.payload, &buffer[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) { @@ -273,13 +265,32 @@ int16_t LoRaWANNode::restoreChannels() { } #endif -void LoRaWANNode::beginCommon() { +void LoRaWANNode::beginCommon(uint8_t joinDr) { LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - cmd.payload[0] = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4); + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + uint8_t drUp = 0; + // if join datarate is user-specified and valid, select that value; otherwise use + if(joinDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + if(joinDr >= this->band->txFreqs[0].drMin && joinDr <= this->band->txFreqs[0].drMax) { + drUp = joinDr; + } else { + RADIOLIB_DEBUG_PRINTLN("Datarate %d is not valid (min: %d, max %d) - using default", + joinDr, this->band->txFreqs[0].drMin, this->band->txFreqs[0].drMax); + joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + } + } + if(joinDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + drUp = (this->band->txFreqs[0].drMin + this->band->txFreqs[0].drMax) / 2; + } + cmd.payload[0] = (drUp << 4); + } else { + uint8_t drJr = this->band->txSpans[0].joinRequestDataRate; + cmd.payload[0] = (drJr << 4); + } 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 + 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; @@ -393,17 +404,25 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe int16_t state = RADIOLIB_ERR_NONE; - // setup uplink/downlink frequencies and datarates - state = this->selectChannelsJR(this->devNonce, joinDr); + // setup join-request uplink/downlink frequencies and datarates + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + state = this->setupChannelsDyn(true); + } else { + state = this->setupChannelsFix(this->subBand); + } RADIOLIB_ASSERT(state); // setup all MAC properties to default values - this->beginCommon(); + this->beginCommon(joinDr); // set the physical layer configuration state = this->setPhyProperties(); RADIOLIB_ASSERT(state); + // select a random pair of Tx/Rx channels + state = this->selectChannels(); + RADIOLIB_ASSERT(state); + // configure for uplink with default configuration state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); RADIOLIB_ASSERT(state); @@ -532,14 +551,17 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe cmd.payload[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]; (void)execMacCommand(&cmd); + // in case of dynamic band, setup the default channels first + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->setupChannelsDyn(false); + } // process CFlist if present if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) { 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->setupChannels(cfList); - } else { - this->setupChannels(nullptr); - } + this->processCFList(cfList); + } + // if no 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 }; @@ -678,9 +700,12 @@ int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, int16_t state = RADIOLIB_ERR_NONE; - // calculate initial datarate - in case of fixed bands, this requires a subband to be selected - // downlink datarate is calculated using a specific uplink channel, so don't care here - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = (this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][0].drMax + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][0].drMin) / 2; + // 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->beginCommon(); @@ -869,8 +894,9 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf // check maximum payload len as defined in phy if(len > this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]) { - // len = this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]; return(RADIOLIB_ERR_PACKET_TOO_LONG); + // if testing with TS008 specification verification protocol, don't throw error but clip the message + // len = this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]; } // increase frame counter by one @@ -919,13 +945,11 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf break; case(3): { if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - this->setupChannels(nullptr); // revert to default frequencies + this->setupChannelsDyn(false); // revert to default frequencies } else { - // if a subband was selected by user, go back to its default state + // go back to default selected subband // hopefully it'll help something, but probably not; at least we tried.. - if(this->selectedSubband >= 0) { - this->selectSubband(this->selectedSubband); - } + this->setupChannelsFix(this->subBand); } adrStage = 0; // nothing else to do, so end the cycle } @@ -1645,216 +1669,135 @@ int16_t LoRaWANNode::setPhyProperties() { return(state); } -int16_t LoRaWANNode::setupChannels(uint8_t* cfList) { - RADIOLIB_DEBUG_PRINTLN("Setting up channels"); +int16_t LoRaWANNode::setupChannelsDyn(bool joinRequest) { + RADIOLIB_DEBUG_PRINTLN("Setting up dynamic channels"); - // in case of frequency list-type band, copy the default TX channels into the available channels, with RX1 = TX - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - RADIOLIB_DEBUG_PRINTLN("Dynamic band"); - size_t num = 0; - // copy the default defined channels into the first slots - for(; num < 3 && this->band->txFreqs[num].enabled; num++) { + 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]; + RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); + } + + // if we're about to send a join-request, copy the join-request 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]; RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); } - // if there is a cflist present, parse its frequencies into the next five slots, with datarate range copied from default channel 0 - if(cfList != nullptr) { - RADIOLIB_DEBUG_PRINTLN("CFList present"); - LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; - cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn; - // 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++) { - cmd.payload[0] = num; - memcpy(&cmd.payload[1], &cfList[i*3], 3); - (void)execMacCommand(&cmd); - } - } - for(; num < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; num++) { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; - } - - - } else { // RADIOLIB_LORAWAN_BAND_FIXED - if(cfList != nullptr) { - RADIOLIB_DEBUG_PRINTLN("CFList present"); - LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; - cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; - cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; - cmd.payload[0] = 0xFF; // same datarate and payload - - // 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.payload[3] = chMaskCntl << 4; // NbTrans = 0 -> keep the same - memcpy(&cmd.payload[1], &cfList[chMaskCntl*2], 2); - (void)execMacCommand(&cmd); - } - } - } - for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - RADIOLIB_DEBUG_PRINTLN("UL: %d %d %5.2f (%d - %d) | DL: %d %d %5.2f (%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 - ); - } - return(RADIOLIB_ERR_NONE); -} - -int16_t LoRaWANNode::selectSubband(uint8_t idx) { - int16_t state = this->selectSubband((idx - 1) * 8, idx * 8 - 1); - return(state); -} - -int16_t LoRaWANNode::selectSubband(uint8_t startChannel, uint8_t endChannel) { - if(this->activeMode != RADIOLIB_LORAWAN_MODE_NONE) { - RADIOLIB_DEBUG_PRINTLN("There is already an active session - cannot change subband"); - return(RADIOLIB_ERR_INVALID_CHANNEL); - } - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - RADIOLIB_DEBUG_PRINTLN("This is a dynamic band plan which does not support subbands"); - return(RADIOLIB_ERR_INVALID_CHANNEL); - } - this->selectedSubband = startChannel % 8; // save selected subband - assumed a block of 8 channels - - uint8_t numChannels = endChannel - startChannel + 1; - if(startChannel > this->band->txSpans[0].numChannels) { - RADIOLIB_DEBUG_PRINTLN("There are only %d channels available in this band", this->band->txSpans[0].numChannels); - return(RADIOLIB_ERR_INVALID_CHANNEL); - } - if(startChannel + numChannels > this->band->txSpans[0].numChannels) { - numChannels = this->band->txSpans[0].numChannels - startChannel; - RADIOLIB_DEBUG_PRINTLN("Could only select %d channels due to end of band", numChannels); - } - if(numChannels > RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { - numChannels = RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; - RADIOLIB_DEBUG_PRINTLN("Could only select %d channels due to specified limit", numChannels); } - LoRaWANChannel_t chnl; - for(size_t chNum = 0; chNum < numChannels; chNum++) { - chnl.enabled = true; - chnl.idx = startChannel + chNum; - chnl.freq = this->band->txSpans[0].freqStart + chnl.idx*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][chNum] = chnl; - // downlink channel is dynamically calculated on each uplink in selectChannels() - RADIOLIB_DEBUG_PRINTLN("Channel UL %d frequency = %f MHz", chNum, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chNum].freq); - } return(RADIOLIB_ERR_NONE); } -int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t joinDr) { - LoRaWANChannel_t channelUp; - LoRaWANChannel_t channelDown; - uint8_t drUp; - uint8_t drDown; - if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - // count the number of available channels for a join-request (default channels + join-request channels) - uint8_t numJRChannels = 0; - for(size_t i = 0; i < 3; i++) { - if(this->band->txFreqs[i].enabled) { - numJRChannels++; - } - if(this->band->txJoinReq[i].enabled) { - numJRChannels++; - } - } - - // cycle through the available channels (seed with devNonce) - uint8_t channelId = devNonce % numJRChannels; - - // find the channel whose index is selected - for(size_t i = 0; i < 3; i++) { - if(this->band->txFreqs[i].idx == channelId) { - channelUp = this->band->txFreqs[i]; - break; - } - if(this->band->txJoinReq[i].idx == channelId) { - channelUp = this->band->txJoinReq[i]; - } - } - - // if join datarate is user-specified and valid, select that value; otherwise use - if(joinDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - if(joinDr >= channelUp.drMin && joinDr <= channelUp.drMax) { - drUp = joinDr; - } else { - RADIOLIB_DEBUG_PRINTLN("Datarate %d is not valid (min: %d, max %d) - using default", joinDr, channelUp.drMin, channelUp.drMax); - joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; - } - } - if(joinDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { - drUp = int((channelUp.drMax + channelUp.drMin) / 2); - } - - // derive the downlink channel and datarate from the uplink channel and datarate - channelDown = channelUp; - drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, channelDown.drMin, channelDown.drMax); - - } else { // RADIOLIB_LORAWAN_BAND_FIXED - uint8_t spanID = 0; - uint8_t channelID = 0; - uint8_t numEnabledChannels = 0; - // if there are any predefined channels because user selected a subband, select one of these channels - for(; numEnabledChannels < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; numEnabledChannels++) { - if(this->availableChannels[numEnabledChannels][RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].enabled == false) { - break; - } - } - if(numEnabledChannels > 0) { - uint8_t channelID = this->phyLayer->random(numEnabledChannels); - channelUp = this->availableChannels[channelID][RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; - spanID = channelUp.idx / this->band->txSpans[0].numChannels; - channelID = channelUp.idx; - - } else { // no pre-selected subband, cycle through size-8 (or size-9) blocks - channelUp.enabled = true; - uint8_t numBlocks = this->band->txSpans[0].numChannels / 8; // calculate number of 8-channel blocks - uint8_t numBlockChannels = 8 + (this->band->numTxSpans == 2 ? 1 : 0); // add a 9th channel if there's a second span - uint8_t blockID = devNonce % numBlocks; // currently selected block (seed with devNonce) - channelID = this->phyLayer->random(numBlockChannels); // select randomly from these 8 or 9 channels - RADIOLIB_DEBUG_PRINTLN("blocks: %d, channels/block: %d, blockID: %d, channelID: %d", numBlocks, numBlockChannels, blockID, channelID); - - // if channel 0-7 is selected, retrieve this channel from span 0; otherwise span 1 - if(channelID < 8) { - spanID = 0; - channelUp.idx = blockID * 8 + channelID; - } else { - spanID = 1; - channelUp.idx = blockID; - } - channelUp.freq = this->band->txSpans[spanID].freqStart + channelUp.idx*this->band->txSpans[spanID].freqStep; - } - - // for fixed channel plans, the user-specified datarate is ignored and span-specific value must be used - drUp = this->band->txSpans[spanID].joinRequestDataRate; - - // derive the downlink channel and datarate from the uplink channel and datarate - channelDown.enabled = true; - channelDown.idx = channelID % this->band->rx1Span.numChannels; - channelDown.freq = this->band->rx1Span.freqStart + channelDown.idx*this->band->rx1Span.freqStep; - channelDown.drMin = this->band->rx1Span.drMin; - channelDown.drMax = this->band->rx1Span.drMax; - drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase, channelDown.drMin, channelDown.drMax); - +// setup a subband and its corresponding join-request datarate +// WARNING: subBand starts at 1 (corresponds to all populair schemes) +int16_t LoRaWANNode::setupChannelsFix(uint8_t subBand) { + RADIOLIB_DEBUG_PRINTLN("Setting up fixed channels"); + // 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 + uint8_t drJR = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + if(rand <= 8) { + drJR = this->band->txSpans[0].joinRequestDataRate; // if one of the first 8 channels, select datarate of span 0 + } else { + drJR = this->band->txSpans[1].joinRequestDataRate; // if ninth channel, select datarate of span 1 + } + + // 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; + } + + // chMask is set for 16 channels at once, so widen the Cntl value + uint8_t chMaskCntl = (subBand - 1) / 2; // compensate the 1 offset + + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; + cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; + + // if there are two channel spans, first set the channel from second span + if(this->band->numTxSpans == 2) { + cmd.payload[0] = (drJR << 4); // set join-request datarate + cmd.payload[0] |= 0; // set Tx power to maximum + // enable channel that belongs to this subband + cmd.payload[1] = (1 << (subBand - 1)); // set channel mask + cmd.payload[2] = 0; + cmd.payload[3] = (7 << 4); // set the chMaskCntl value to all channels off + cmd.payload[3] |= 0; // keep NbTrans the same + (void)execMacCommand(&cmd, false); + } + + cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; + cmd.payload[0] = (drJR << 4); // set join-request datarate + cmd.payload[0] |= 0; // set Tx power to maximum + // now select the correct bank of 8 channels + // 0x00 0xFF channel mask for subband = 2, 4.. (even) + // 0xFF 0x00 channel mask for subband = 1, 3.. (odd) + if(subBand % 2 == 0) { + cmd.payload[1] = 0x00; + cmd.payload[2] = 0xFF; + } else { + cmd.payload[1] = 0xFF; + cmd.payload[2] = 0x00; + } + cmd.payload[3] = (chMaskCntl << 4); // set the chMaskCntl value + cmd.payload[3] |= 0; // keep NbTrans the same + (void)execMacCommand(&cmd, false); + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::processCFList(uint8_t* cfList) { + RADIOLIB_DEBUG_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; + } + num++; + } + + LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; + cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + // 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++) { + 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 = { 0, 0, 0, 0 }; + cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cmd.payload[0] = 0xFF; // same datarate and payload + + // 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[3] = chMaskCntl << 4; // NbTrans = 0 -> keep the same + memcpy(&cmd.payload[1], &cfList[chMaskCntl*2], 2); + (void)execMacCommand(&cmd); + // save the response as a MAC answer, as this signals execMacCommand() to store the masks contiguously + pushMacCommand(&cmd, &this->commandsUp); + } + // delete the ADR response + (void)deleteMacCommand(RADIOLIB_LORAWAN_MAC_LINK_ADR, &this->commandsUp); } - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = channelUp; - this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDown; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp; - this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown; return(RADIOLIB_ERR_NONE); } @@ -1869,9 +1812,7 @@ int16_t LoRaWANNode::selectChannels() { && this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] <= this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax) { channelsEnabled[numChannels] = i; numChannels++; - } - } else { - break; + } } } if(numChannels == 0) { @@ -1886,7 +1827,7 @@ int16_t LoRaWANNode::selectChannels() { // 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]; - } else { // RADIOLIB_LORAWAN_BAND_FIXED + } 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; @@ -1918,7 +1859,7 @@ int16_t LoRaWANNode::setDatarate(uint8_t drUp, bool saveToEeprom) { } if(!isValidDR) { RADIOLIB_DEBUG_PRINTLN("No defined channel allows datarate %d", drUp); - return(RADIOLIB_ERR_DATA_RATE_INVALID); + return(RADIOLIB_ERR_INVALID_DATA_RATE); } LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; @@ -1926,13 +1867,13 @@ int16_t LoRaWANNode::setDatarate(uint8_t drUp, bool saveToEeprom) { cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; 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] = (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, saveToEeprom); // check if ACK is set for Tx Power if((cmd.payload[0] >> 1) != 1) { - return(RADIOLIB_ERR_DATA_RATE_INVALID); + return(RADIOLIB_ERR_INVALID_DATA_RATE); } return(RADIOLIB_ERR_NONE); @@ -2016,7 +1957,7 @@ int16_t LoRaWANNode::setTxPower(int8_t txPower, bool saveToEeprom) { cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; cmd.payload[0] = 0xF0; // keep datarate the same cmd.payload[0] |= txPowerNew; // set the Tx Power - cmd.payload[3] |= (1 << 7); // set the RFU bit, which means that the channel mask gets ignored + 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, saveToEeprom); @@ -2227,65 +2168,17 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { // (which is set on the internal MAC command when creating new session) if((cmd->payload[3] >> 7) == 0) { if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - if(chMaskCntl == 0) { - // if chMaskCntl == 0, apply the mask by looking at each channel bit - RADIOLIB_DEBUG_PRINTLN("ADR channel %d: %d --> %d", this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, (chMask >> i) & 0x01); - 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) { - chMaskAck = 0; - break; - } - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; - } else { - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = false; - } + chMaskAck = (uint8_t)this->applyChannelMaskDyn(chMaskCntl, chMask); - } else if(chMaskCntl == 6) { - // 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; - } - } - - } - } else { // RADIOLIB_LORAWAN_BAND_FIXED - // delete any prior ADR responses from the uplink queue, but do not care if none is present yet + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // if there was already an ADR response in the uplink MAC queue, + // this is a consecutive ADR command, so we delete the prior response int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_MAC_LINK_ADR, &this->commandsUp); if(state == RADIOLIB_ERR_NONE) { - isSuccessive = true; // if we found an ADR Ans in the uplink MAC queue, this is a successive ADR MAC request + isSuccessive = true; } - RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask); - uint8_t num = 0; - uint8_t chNum = chMaskCntl*16; - uint8_t chSpan = 0; - for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { - RADIOLIB_DEBUG_PRINTLN("chNum: %d, chSpan: %d, i: %d, mask: %d", chNum, chSpan, i, chMask & (1UL << i)); - // if we must roll over to next span, reset chNum and move to next channel span - if(chNum >= this->band->txSpans[chSpan].numChannels) { - chNum = 0; - chSpan++; - } + chMaskAck = (uint8_t)this->applyChannelMaskFix(chMaskCntl, chMask, !isSuccessive); - if(chMask & (1UL << i)) { - if(chSpan >= this->band->numTxSpans) { - RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!"); - return(RADIOLIB_ERR_UNKNOWN); - } - LoRaWANChannel_t chnl; - chnl.enabled = true; - chnl.idx = chMaskCntl*16 + i; - chnl.freq = this->band->txSpans[chSpan].freqStart + chNum*this->band->txSpans[chSpan].freqStep; - chnl.drMin = this->band->txSpans[chSpan].drMin; - chnl.drMax = this->band->txSpans[chSpan].drMax; - this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; - // downlink channels are dynamically calculated on each uplink in selectChannels() - RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", num, chnl.idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); - num++; - } - chNum++; - } } } @@ -2299,7 +2192,6 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { if(saveToEeprom) { uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; 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((cmd->payload[3] >> 7) == 1) { mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID) + 1, &(cmd->payload[1]), 3); @@ -2310,23 +2202,31 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { } else { // read how many ADR masks are already stored - uint8_t macNumADR = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID); - - // if RFU bit is set, this is just a change in Datarate or TxPower, so read ADR command and overwrite first byte + uint8_t numMacADR = mod->hal->getPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID); + RADIOLIB_DEBUG_PRINTLN("[1] Successive: %d, numMacADR: %d, RFU: %d, payload: %02X %02X %02X %02X", + isSuccessive, numMacADR, (cmd->payload[3] >> 7), + cmd->payload[0], cmd->payload[1], cmd->payload[2], cmd->payload[3]); + // if RFU bit is set, this is just a change in Datarate or TxPower + // so read bytes 1..3 from last stored ADR command into the current MAC payload and re-store it if((cmd->payload[3] >> 7) == 1) { - mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + macNumADR * payLen + 1, &(cmd->payload[1]), 3); - } else { - if(isSuccessive) { - // saved another ADR mask, so increase counter - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, macNumADR + 1); - } else { - // this is the first ADR mask in this downlink, so (re)set counter to 1 - mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, 1); + if(numMacADR > 0) { + mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (numMacADR - 1) * payLen + 1, &(cmd->payload[1]), 3); + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (numMacADR - 1) * payLen, &(cmd->payload[0]), payLen); } + + } else { + // if no previous mask was processed, reset counter to 0 + if(!isSuccessive) { + numMacADR = 0; + } + // save to the uplink channel location, to the numMacADR-th slot of 4 bytes + mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + numMacADR * payLen, &(cmd->payload[0]), payLen); + // saved an ADR mask, so increase counter + mod->hal->setPersistentParameter(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, numMacADR + 1); } - - // save to the uplink channel location, to the macNumADR-th slot of 4 bytes - mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + macNumADR * payLen, &(cmd->payload[0]), payLen); + RADIOLIB_DEBUG_PRINTLN("[2] Successive: %d, numMacADR: %d, RFU: %d, payload: %02X %02X %02X %02X", + isSuccessive, numMacADR, (cmd->payload[3] >> 7), + cmd->payload[0], cmd->payload[1], cmd->payload[2], cmd->payload[3]); } } #endif @@ -2627,6 +2527,204 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) { return(false); } +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 + RADIOLIB_DEBUG_PRINTLN("ADR channel %d: %d --> %d", this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, (chMask >> i) & 0x01); + 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; + } + } + + } + + for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + RADIOLIB_DEBUG_PRINTLN("UL: %d %d %5.2f (%d - %d) | DL: %d %d %5.2f (%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 + ); + } + + return(true); +} + +bool LoRaWANNode::applyChannelMaskFix(uint8_t chMaskCntl, uint16_t chMask, bool clear) { + RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask); + if(clear) { + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + } + // 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++; + } + } + + if((this->band->numTxSpans == 1 && chMaskCntl <= 5) || (this->band->numTxSpans == 2 && chMaskCntl <= 3)) { + // select channels from first span + LoRaWANChannel_t chnl; + 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; // keep track of configured subband in case we must reset the channels + 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; + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", chnl.idx, idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx-1].freq); + } + } + + } + 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 + LoRaWANChannel_t chnl; + 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; + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", chnl.idx, idx-1, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx-1].freq); + } + } + + } + if(this->band->numTxSpans == 2 && chMaskCntl == 5) { + // a '1' enables a bank of 8 + 1 channels from 1st and 2nd span respectively + LoRaWANChannel_t chnl; + 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; i++) { + 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; + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", chnl.idx, idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx-1].freq); + } + // 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; + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", chnl.idx, idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx-1].freq); + } + } + + } + if(this->band->numTxSpans == 2 && chMaskCntl == 6) { + // all channels on (but we revert to selected subband) + if(this->subBand >= 0) { + this->setupChannelsFix(this->subBand); + } + // a '1' enables a single channel from second span + LoRaWANChannel_t chnl; + 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; + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", chnl.idx, idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx-1].freq); + } + } + + } + if(this->band->numTxSpans == 2 && chMaskCntl == 7) { + // all channels off (clear all channels) + LoRaWANChannel_t 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; + RADIOLIB_DEBUG_PRINTLN("Channel UL %d (%d) frequency = %f MHz", chnl.idx, idx, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][idx-1].freq); + } + } + + } + + for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + RADIOLIB_DEBUG_PRINTLN("UL: %d %d %5.2f (%d - %d) | DL: %d %d %5.2f (%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 + ); + } + + return(true); +} + uint8_t LoRaWANNode::getMacPayloadLength(uint8_t cid) { for (LoRaWANMacSpec_t entry : MacTable) { if (entry.cid == cid) { @@ -2752,7 +2850,10 @@ void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out } uint16_t LoRaWANNode::checkSum16(uint8_t *key, uint8_t keyLen) { - uint16_t buf16[RADIOLIB_AES128_KEY_SIZE/2] = { 0 }; + if(keyLen > RADIOLIB_AES128_KEY_SIZE / 2) { + keyLen = RADIOLIB_AES128_KEY_SIZE / 2; + } + uint16_t buf16[RADIOLIB_AES128_KEY_SIZE / 2] = { 0 }; uint8_t bufLen = keyLen / 2; memcpy(buf16, key, keyLen); uint16_t checkSum = 0; diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index de4b3ea0..e62ddb24 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -79,7 +79,7 @@ #define RADIOLIB_LORAWAN_BAND_DYNAMIC (0) #define RADIOLIB_LORAWAN_BAND_FIXED (1) #define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (15) -#define RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE (0xFF >> 1) // reserve first bit for enable-flag +#define RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE (0xFF >> 0) // recommended default settings #define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000) @@ -403,8 +403,9 @@ class LoRaWANNode { \brief Default constructor. \param phy Pointer to the PhysicalLayer radio module. \param band Pointer to the LoRaWAN band to use. + \param subBand The subband to be used (starting from 1!) */ - LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band); + LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand = 0); #if !defined(RADIOLIB_EEPROM_UNSUPPORTED) /*! @@ -650,23 +651,6 @@ class LoRaWANNode { */ int16_t setTxPower(int8_t txPower, bool saveToEeprom = false); - /*! - \brief Select a single subband (8 channels) for fixed bands such as US915. - Only available before joining a network. - \param idx The subband to be used (starting from 1!) - \returns \ref status_codes - */ - int16_t selectSubband(uint8_t idx); - - /*! - \brief Select a set of channels for fixed bands such as US915. - Only available before joining a network. - \param startChannel The first channel of the band to be used (inclusive) - \param endChannel The last channel of the band to be used (inclusive) - \returns \ref status_codes - */ - int16_t selectSubband(uint8_t startChannel, uint8_t endChannel); - /*! \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. @@ -702,7 +686,7 @@ class LoRaWANNode { PhysicalLayer* phyLayer = NULL; const LoRaWANBand_t* band = NULL; - void beginCommon(); + void beginCommon(uint8_t joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED); LoRaWANMacCommandQueue_t commandsUp = { .numCommands = 0, @@ -803,7 +787,7 @@ class LoRaWANNode { bool isMACPayload = false; // save the selected subband in case this must be restored in ADR control - int8_t selectedSubband = -1; + int8_t subBand = -1; #if !defined(RADIOLIB_EEPROM_UNSUPPORTED) /*! @@ -832,15 +816,20 @@ class LoRaWANNode { 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 setupChannels()! + // channels must be configured separately by setupChannelsDyn()! int16_t setPhyProperties(); // setup uplink/downlink channel data rates and frequencies - // will attempt to randomly select based on currently used band plan - int16_t setupChannels(uint8_t* cfList); + // 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); - // select a set of semi-random TX/RX channels for the join-request and -accept message - int16_t selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband); + // setup uplink/downlink channel data rates and frequencies + // for fixed bands, we only allow one subband 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(); @@ -864,6 +853,12 @@ class LoRaWANNode { // execute mac command, return the number of processed bytes for sequential processing bool execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom = true); + // 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, bool clear); + // get the payload length for a specific MAC command uint8_t getMacPayloadLength(uint8_t cid);