[LoRaWAN] Rework channel logic

This commit is contained in:
StevenCellist 2024-01-13 00:05:25 +01:00
parent d0979ce853
commit 0bba68f3ae
4 changed files with 439 additions and 348 deletions

View file

@ -294,6 +294,7 @@ wipe KEYWORD2
restore KEYWORD2 restore KEYWORD2
beginOTAA KEYWORD2 beginOTAA KEYWORD2
beginABP KEYWORD2 beginABP KEYWORD2
isJoined KEYWORD2
saveSession KEYWORD2 saveSession KEYWORD2
sendMacCommandReq KEYWORD2 sendMacCommandReq KEYWORD2
uplink KEYWORD2 uplink KEYWORD2
@ -312,7 +313,6 @@ timeUntilUplink KEYWORD2
setDwellTime KEYWORD2 setDwellTime KEYWORD2
maxPayloadDwellTime KEYWORD2 maxPayloadDwellTime KEYWORD2
setTxPower KEYWORD2 setTxPower KEYWORD2
selectSubband KEYWORD2
setCSMA KEYWORD2 setCSMA KEYWORD2
getMacLinkCheckAns KEYWORD2 getMacLinkCheckAns KEYWORD2
getMacDeviceTimeAns KEYWORD2 getMacDeviceTimeAns KEYWORD2

View file

@ -553,11 +553,6 @@
*/ */
#define RADIOLIB_ERR_A_FCNT_DOWN_INVALID (-1114) #define RADIOLIB_ERR_A_FCNT_DOWN_INVALID (-1114)
/*!
\brief Datarate requested by user is invalid.
*/
#define RADIOLIB_ERR_DATA_RATE_INVALID (-1115)
/*! /*!
\} \}
*/ */

View file

@ -28,24 +28,12 @@ uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_
return(dr); 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->phyLayer = phy;
this->band = band; this->band = band;
this->rx2 = this->band->rx2; 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->txPowerMax = this->band->powerMax;
this->txPowerCur = 0; // start at 0 offset = full power this->subBand = subBand;
this->difsSlots = 2; this->difsSlots = 2;
this->backoffMax = 6; this->backoffMax = 6;
this->enableCSMA = false; this->enableCSMA = false;
@ -63,7 +51,6 @@ void LoRaWANNode::wipe() {
mod->hal->wipePersistentStorage(); mod->hal->wipePersistentStorage();
} }
// TODO do not return status code, but return LoRaWAN mode (OTAA, ABP, none)
int16_t LoRaWANNode::restore() { int16_t LoRaWANNode::restore() {
// if already joined, ignore // if already joined, ignore
if(this->activeMode != RADIOLIB_LORAWAN_MODE_NONE) { if(this->activeMode != RADIOLIB_LORAWAN_MODE_NONE) {
@ -218,7 +205,11 @@ int16_t LoRaWANNode::restoreFcntUp() {
int16_t LoRaWANNode::restoreChannels() { int16_t LoRaWANNode::restoreChannels() {
// first do the default channels // 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(); Module* mod = this->phyLayer->getMod();
uint8_t bufferZeroes[5] = { 0 }; uint8_t bufferZeroes[5] = { 0 };
@ -252,16 +243,17 @@ int16_t LoRaWANNode::restoreChannels() {
} }
} }
} else { } else { // RADIOLIB_LORAWAN_BAND_FIXED
uint8_t numBytes = 8 * MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; uint8_t numADRCommands = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID);
uint8_t numBytes = numADRCommands * MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn;
uint8_t buffer[numBytes] = { 0 }; uint8_t buffer[numBytes] = { 0 };
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), buffer, numBytes); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID), buffer, numBytes);
LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 };
cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; 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); memcpy(cmd.payload, &buffer[i * cmd.len], cmd.len);
// there COULD, according to spec, be an all zeroes ADR command - meh // there COULD, according to spec, be an all zeroes ADR command - meh
if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) { if(memcmp(cmd.payload, bufferZeroes, cmd.len) != 0) {
@ -273,13 +265,32 @@ int16_t LoRaWANNode::restoreChannels() {
} }
#endif #endif
void LoRaWANNode::beginCommon() { void LoRaWANNode::beginCommon(uint8_t joinDr) {
LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 }; LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 };
cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; 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[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); (void)execMacCommand(&cmd);
cmd.cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; 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; int16_t state = RADIOLIB_ERR_NONE;
// setup uplink/downlink frequencies and datarates // setup join-request uplink/downlink frequencies and datarates
state = this->selectChannelsJR(this->devNonce, joinDr); if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
state = this->setupChannelsDyn(true);
} else {
state = this->setupChannelsFix(this->subBand);
}
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// setup all MAC properties to default values // setup all MAC properties to default values
this->beginCommon(); this->beginCommon(joinDr);
// set the physical layer configuration // set the physical layer configuration
state = this->setPhyProperties(); state = this->setPhyProperties();
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// select a random pair of Tx/Rx channels
state = this->selectChannels();
RADIOLIB_ASSERT(state);
// configure for uplink with default configuration // configure for uplink with default configuration
state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK);
RADIOLIB_ASSERT(state); 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]; cmd.payload[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS];
(void)execMacCommand(&cmd); (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 // process CFlist if present
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) { if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 }; 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); memcpy(&cfList[0], &joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS], RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN);
this->setupChannels(cfList); this->processCFList(cfList);
} else {
this->setupChannels(nullptr);
} }
// if no CFList was received, default or subband are already setup so don't need to do anything else
// prepare buffer for key derivation // prepare buffer for key derivation
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; 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; int16_t state = RADIOLIB_ERR_NONE;
// calculate initial datarate - in case of fixed bands, this requires a subband to be selected // setup the uplink/downlink channels and initial datarate
// downlink datarate is calculated using a specific uplink channel, so don't care here if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
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; this->setupChannelsDyn();
} else {
this->setupChannelsFix(this->subBand);
}
// setup all MAC properties to default values // setup all MAC properties to default values
this->beginCommon(); 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 // check maximum payload len as defined in phy
if(len > this->band->payloadLenMax[this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]]) { 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); 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 // 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; break;
case(3): { case(3): {
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
this->setupChannels(nullptr); // revert to default frequencies this->setupChannelsDyn(false); // revert to default frequencies
} else { } 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.. // hopefully it'll help something, but probably not; at least we tried..
if(this->selectedSubband >= 0) { this->setupChannelsFix(this->subBand);
this->selectSubband(this->selectedSubband);
}
} }
adrStage = 0; // nothing else to do, so end the cycle adrStage = 0; // nothing else to do, so end the cycle
} }
@ -1645,216 +1669,135 @@ int16_t LoRaWANNode::setPhyProperties() {
return(state); return(state);
} }
int16_t LoRaWANNode::setupChannels(uint8_t* cfList) { int16_t LoRaWANNode::setupChannelsDyn(bool joinRequest) {
RADIOLIB_DEBUG_PRINTLN("Setting up channels"); 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; size_t num = 0;
// copy the default defined channels into the first slots // copy the default defined channels into the first slots (where Tx = Rx)
for(; num < 3 && this->band->txFreqs[num].enabled; num++) { 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_UPLINK][num] = this->band->txFreqs[num];
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][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); 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) { // if we're about to send a join-request, copy the join-request channels to the next slots
RADIOLIB_DEBUG_PRINTLN("CFList present"); 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);
}
}
return(RADIOLIB_ERR_NONE);
}
// 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 }; LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 };
cmd.cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; 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 // 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; cmd.payload[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin;
for(uint8_t i = 0; i < 5; i++, num++) { for(uint8_t i = 0; i < 5; i++, num++) {
cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_NEW_CHANNEL].lenDn;
cmd.payload[0] = num; cmd.payload[0] = num;
memcpy(&cmd.payload[1], &cfList[i*3], 3); memcpy(&cmd.payload[1], &cfList[i*3], 3);
(void)execMacCommand(&cmd); (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 } else { // RADIOLIB_LORAWAN_BAND_FIXED
if(cfList != nullptr) { // complete channel mask received, so clear all existing channels
RADIOLIB_DEBUG_PRINTLN("CFList present"); 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 }; LoRaWANMacCommand_t cmd = { 0, 0, 0, 0 };
cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; cmd.cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
cmd.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn;
cmd.payload[0] = 0xFF; // same datarate and payload 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 // 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 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++) { 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 cmd.payload[3] = chMaskCntl << 4; // NbTrans = 0 -> keep the same
memcpy(&cmd.payload[1], &cfList[chMaskCntl*2], 2); memcpy(&cmd.payload[1], &cfList[chMaskCntl*2], 2);
(void)execMacCommand(&cmd); (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);
} }
}
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);
}
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); return(RADIOLIB_ERR_NONE);
} }
@ -1870,8 +1813,6 @@ int16_t LoRaWANNode::selectChannels() {
channelsEnabled[numChannels] = i; channelsEnabled[numChannels] = i;
numChannels++; numChannels++;
} }
} else {
break;
} }
} }
if(numChannels == 0) { if(numChannels == 0) {
@ -1918,7 +1859,7 @@ int16_t LoRaWANNode::setDatarate(uint8_t drUp, bool saveToEeprom) {
} }
if(!isValidDR) { if(!isValidDR) {
RADIOLIB_DEBUG_PRINTLN("No defined channel allows datarate %d", drUp); 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 }; 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.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn;
cmd.payload[0] = (drUp << 4); cmd.payload[0] = (drUp << 4);
cmd.payload[0] |= 0x0F; // keep Tx Power the same 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 cmd.payload[3] |= 0; // keep NbTrans the same
(void)execMacCommand(&cmd, saveToEeprom); (void)execMacCommand(&cmd, saveToEeprom);
// check if ACK is set for Tx Power // check if ACK is set for Tx Power
if((cmd.payload[0] >> 1) != 1) { if((cmd.payload[0] >> 1) != 1) {
return(RADIOLIB_ERR_DATA_RATE_INVALID); return(RADIOLIB_ERR_INVALID_DATA_RATE);
} }
return(RADIOLIB_ERR_NONE); 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.len = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn;
cmd.payload[0] = 0xF0; // keep datarate the same cmd.payload[0] = 0xF0; // keep datarate the same
cmd.payload[0] |= txPowerNew; // set the Tx Power 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 cmd.payload[3] |= 0; // keep NbTrans the same
(void)execMacCommand(&cmd, saveToEeprom); (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) // (which is set on the internal MAC command when creating new session)
if((cmd->payload[3] >> 7) == 0) { if((cmd->payload[3] >> 7) == 0) {
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { chMaskAck = (uint8_t)this->applyChannelMaskDyn(chMaskCntl, chMask);
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;
}
} 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 } else { // RADIOLIB_LORAWAN_BAND_FIXED
// delete any prior ADR responses from the uplink queue, but do not care if none is present yet // 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); int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_MAC_LINK_ADR, &this->commandsUp);
if(state == RADIOLIB_ERR_NONE) { 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) { if(saveToEeprom) {
uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn; uint8_t payLen = MacTable[RADIOLIB_LORAWAN_MAC_LINK_ADR].lenDn;
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { 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 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) { if((cmd->payload[3] >> 7) == 1) {
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_LINK_ADR_ID) + 1, &(cmd->payload[1]), 3); 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 { } else {
// read how many ADR masks are already stored // read how many ADR masks are already stored
uint8_t macNumADR = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID); uint8_t numMacADR = mod->hal->getPersistentParameter<uint8_t>(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID);
RADIOLIB_DEBUG_PRINTLN("[1] Successive: %d, numMacADR: %d, RFU: %d, payload: %02X %02X %02X %02X",
// if RFU bit is set, this is just a change in Datarate or TxPower, so read ADR command and overwrite first byte 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) { 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); if(numMacADR > 0) {
} else { mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (numMacADR - 1) * payLen + 1, &(cmd->payload[1]), 3);
if(isSuccessive) { mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + (numMacADR - 1) * payLen, &(cmd->payload[0]), payLen);
// saved another ADR mask, so increase counter
mod->hal->setPersistentParameter<uint8_t>(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<uint8_t>(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, 1);
}
} }
// save to the uplink channel location, to the macNumADR-th slot of 4 bytes } else {
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_UL_CHANNELS_ID) + macNumADR * payLen, &(cmd->payload[0]), payLen); // 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<uint8_t>(RADIOLIB_EEPROM_LORAWAN_NUM_ADR_MASKS_ID, numMacADR + 1);
}
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 #endif
@ -2627,6 +2527,204 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom) {
return(false); 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) { uint8_t LoRaWANNode::getMacPayloadLength(uint8_t cid) {
for (LoRaWANMacSpec_t entry : MacTable) { for (LoRaWANMacSpec_t entry : MacTable) {
if (entry.cid == cid) { 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 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; uint8_t bufLen = keyLen / 2;
memcpy(buf16, key, keyLen); memcpy(buf16, key, keyLen);
uint16_t checkSum = 0; uint16_t checkSum = 0;

View file

@ -79,7 +79,7 @@
#define RADIOLIB_LORAWAN_BAND_DYNAMIC (0) #define RADIOLIB_LORAWAN_BAND_DYNAMIC (0)
#define RADIOLIB_LORAWAN_BAND_FIXED (1) #define RADIOLIB_LORAWAN_BAND_FIXED (1)
#define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (15) #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 // recommended default settings
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000) #define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000)
@ -403,8 +403,9 @@ class LoRaWANNode {
\brief Default constructor. \brief Default constructor.
\param phy Pointer to the PhysicalLayer radio module. \param phy Pointer to the PhysicalLayer radio module.
\param band Pointer to the LoRaWAN band to use. \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) #if !defined(RADIOLIB_EEPROM_UNSUPPORTED)
/*! /*!
@ -650,23 +651,6 @@ class LoRaWANNode {
*/ */
int16_t setTxPower(int8_t txPower, bool saveToEeprom = false); 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. \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 backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO.
@ -702,7 +686,7 @@ class LoRaWANNode {
PhysicalLayer* phyLayer = NULL; PhysicalLayer* phyLayer = NULL;
const LoRaWANBand_t* band = NULL; const LoRaWANBand_t* band = NULL;
void beginCommon(); void beginCommon(uint8_t joinDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED);
LoRaWANMacCommandQueue_t commandsUp = { LoRaWANMacCommandQueue_t commandsUp = {
.numCommands = 0, .numCommands = 0,
@ -803,7 +787,7 @@ class LoRaWANNode {
bool isMACPayload = false; bool isMACPayload = false;
// save the selected subband in case this must be restored in ADR control // 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) #if !defined(RADIOLIB_EEPROM_UNSUPPORTED)
/*! /*!
@ -832,15 +816,20 @@ class LoRaWANNode {
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key); bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
// configure the common physical layer properties (preamble, sync word etc.) // 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(); int16_t setPhyProperties();
// setup uplink/downlink channel data rates and frequencies // setup uplink/downlink channel data rates and frequencies
// will attempt to randomly select based on currently used band plan // for dynamic channels, there is a small set of predefined channels
int16_t setupChannels(uint8_t* cfList); // 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 // setup uplink/downlink channel data rates and frequencies
int16_t selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband); // 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 // select a set of random TX/RX channels for up- and downlink
int16_t selectChannels(); int16_t selectChannels();
@ -864,6 +853,12 @@ class LoRaWANNode {
// execute mac command, return the number of processed bytes for sequential processing // execute mac command, return the number of processed bytes for sequential processing
bool execMacCommand(LoRaWANMacCommand_t* cmd, bool saveToEeprom = true); 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 // get the payload length for a specific MAC command
uint8_t getMacPayloadLength(uint8_t cid); uint8_t getMacPayloadLength(uint8_t cid);