[LoRaWAN] Improve band & ADR logic, allow setting ADR, DR, subband, update examples

This commit is contained in:
StevenCellist 2023-11-03 22:32:45 +01:00
parent 6c093b2491
commit 4138dacb19
11 changed files with 410 additions and 181 deletions

View file

@ -87,17 +87,28 @@ void setup() {
// and can be set to NULL // and can be set to NULL
// some frequency bands only use a subset of the available channels // some frequency bands only use a subset of the available channels
// you can set the starting channel and their number // you can select the specific band or set the first channel and last channel
// for example, the following corresponds to US915 FSB2 in TTN // for example, either of the following corresponds to US915 FSB2 in TTN
/* /*
node.startChannel = 8; node.selectSubband(2);
node.numChannels = 8; node.selectSubband(8, 16);
*/ */
// now we can start the activation // now we can start the activation
// this can take up to 20 seconds, and requires a LoRaWAN gateway in range // this can take up to 20 seconds, and requires a LoRaWAN gateway in range
// a specific starting-datarate can be selected in dynamic bands (e.g. EU868):
/*
uint8_t joinDr = 8;
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, joinDr);
*/
// a specific band can be selected for joining in fixed bands (e.g. US915):
/*
uint8_t subband = 2;
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, subband);
*/
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... ")); Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
if(state == RADIOLIB_ERR_NONE) { if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!")); Serial.println(F("success!"));
} else { } else {
@ -107,11 +118,11 @@ void setup() {
} }
// after the device has been activated, // after the device has been activated,
// network can be rejoined after device power cycle // the session can be restored without rejoining after device power cycle
// by calling "begin" // on EEPROM-enabled boards by calling "restore"
/* /*
Serial.print(F("[LoRaWAN] Resuming previous session ... ")); Serial.print(F("[LoRaWAN] Resuming previous session ... "));
state = node.begin(); state = node.restore();
if(state == RADIOLIB_ERR_NONE) { if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!")); Serial.println(F("success!"));
} else { } else {
@ -178,6 +189,12 @@ void loop() {
Serial.println(state); Serial.println(state);
} }
// on EEPROM enabled boards, you can save the current session
// by calling "saveSession" which allows retrieving the session after reboot or deepsleep
/*
node.saveSession();
*/
// wait before sending another packet // wait before sending another packet
delay(10000); delay(30000);
} }

View file

@ -83,16 +83,27 @@ void setup() {
// and can be set to NULL // and can be set to NULL
// some frequency bands only use a subset of the available channels // some frequency bands only use a subset of the available channels
// you can set the starting channel and their number // you can select the specific band or set the first channel and last channel
// for example, the following corresponds to US915 FSB2 in TTN // for example, either of the following corresponds to US915 FSB2 in TTN
/* /*
node.startChannel = 8; node.selectSubband(2);
node.numChannels = 8; node.selectSubband(8, 16);
*/
// if using EU868 on ABP in TTN, you need to set the SF for RX2 window manually
/*
node.rx2.drMax = 3;
*/
// to start a LoRaWAN v1.1 session, the user should also provide
// fNwkSIntKey and sNwkSIntKey similar to nwkSKey and appSKey
/*
state = node.beginABP(devAddr, nwkSKey, appSKey, fNwkSIntKey, sNwkSIntKey);
*/ */
// start the device by directly providing the encryption keys and device address // start the device by directly providing the encryption keys and device address
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... ")); Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey); state = node.beginABP(devAddr, nwkSKey, appSKey);
if(state == RADIOLIB_ERR_NONE) { if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!")); Serial.println(F("success!"));
} else { } else {
@ -102,11 +113,11 @@ void setup() {
} }
// after the device has been activated, // after the device has been activated,
// network can be rejoined after device power cycle // the session can be restored without rejoining after device power cycle
// by calling "begin" // on EEPROM-enabled boards by calling "restore"
/* /*
Serial.print(F("[LoRaWAN] Resuming previous session ... ")); Serial.print(F("[LoRaWAN] Resuming previous session ... "));
state = node.begin(); state = node.restore();
if(state == RADIOLIB_ERR_NONE) { if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!")); Serial.println(F("success!"));
} else { } else {
@ -173,6 +184,12 @@ void loop() {
Serial.println(state); Serial.println(state);
} }
// on EEPROM enabled boards, you can save the current session
// by calling "saveSession" which allows retrieving the session after reboot or deepsleep
/*
node.saveSession();
*/
// wait before sending another packet // wait before sending another packet
delay(10000); delay(10000);
} }

View file

@ -553,6 +553,11 @@
*/ */
#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

@ -1443,13 +1443,17 @@ uint32_t SX126x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) {
} }
int16_t SX126x::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) { int16_t SX126x::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) {
irqFlags = 0b0000000000000010; // RxDone irqFlags = RADIOLIB_SX126X_IRQ_RX_DEFAULT; // flags that can appear in the IRQ register
irqMask = 0b0000000000000010; irqMask = RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; // flags that will trigger DIO0
irqFlags |= 0b0000001000000000; // RxTimeout
irqMask |= 0b0000001000000000;
return(RADIOLIB_ERR_NONE); return(RADIOLIB_ERR_NONE);
} }
bool SX126x::isRxTimeout() {
uint16_t irq = getIrqStatus();
bool rxTimedOut = irq & RADIOLIB_SX126X_IRQ_TIMEOUT;
return(rxTimedOut);
}
int16_t SX126x::implicitHeader(size_t len) { int16_t SX126x::implicitHeader(size_t len) {
return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, len)); return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, len));
} }

View file

@ -965,6 +965,12 @@ class SX126x: public PhysicalLayer {
*/ */
int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask);
/*!
\brief Check whether the IRQ bit for RxTimeout is set
\returns \ref RxTimeout IRQ is set
*/
bool isRxTimeout();
/*! /*!
\brief Set implicit header mode for future reception/transmission. \brief Set implicit header mode for future reception/transmission.
\param len Payload length in bytes. \param len Payload length in bytes.

View file

@ -1245,10 +1245,8 @@ uint32_t SX127x::getTimeOnAir(size_t len) {
} }
uint32_t SX127x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) { uint32_t SX127x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) {
(void)numSymbols; // not used for these modules (void)timeoutUs;
// numSymbols += (109 / 4) + 1; numSymbols = 20;
float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth;
numSymbols = timeoutUs / symbolLength + 1;
return(numSymbols); return(numSymbols);
} }

View file

@ -174,7 +174,7 @@ int16_t LoRaWANNode::restoreChannels() {
} }
#endif #endif
int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) { int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, uint8_t drJoinSubband, bool force) {
// check if we actually need to send the join request // check if we actually need to send the join request
Module* mod = this->phyLayer->getMod(); Module* mod = this->phyLayer->getMod();
@ -191,7 +191,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// setup uplink/downlink frequencies and datarates // setup uplink/downlink frequencies and datarates
state = this->selectChannelsJR(this->devNonce); state = this->selectChannelsJR(this->devNonce, drJoinSubband);
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// configure for uplink with default configuration // configure for uplink with default configuration
@ -617,16 +617,16 @@ int16_t LoRaWANNode::saveChannels() {
#if defined(RADIOLIB_BUILD_ARDUINO) #if defined(RADIOLIB_BUILD_ARDUINO)
int16_t LoRaWANNode::uplink(String& str, uint8_t port, bool isConfirmed, bool adrEnabled) { int16_t LoRaWANNode::uplink(String& str, uint8_t port, bool isConfirmed) {
return(this->uplink(str.c_str(), port, isConfirmed, adrEnabled)); return(this->uplink(str.c_str(), port, isConfirmed));
} }
#endif #endif
int16_t LoRaWANNode::uplink(const char* str, uint8_t port, bool isConfirmed, bool adrEnabled) { int16_t LoRaWANNode::uplink(const char* str, uint8_t port, bool isConfirmed) {
return(this->uplink((uint8_t*)str, strlen(str), port, isConfirmed, adrEnabled)); return(this->uplink((uint8_t*)str, strlen(str), port, isConfirmed));
} }
int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed, bool adrEnabled) { int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed) {
Module* mod = this->phyLayer->getMod(); Module* mod = this->phyLayer->getMod();
// check if sufficient time has elapsed since the last uplink // check if sufficient time has elapsed since the last uplink
@ -734,7 +734,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// length of fopts will be added later // length of fopts will be added later
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00;
if(adrEnabled) { if(this->adrEnabled) {
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED;
if(adrAckReq) { if(adrAckReq) {
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ;
@ -1284,22 +1284,20 @@ int16_t LoRaWANNode::setPhyProperties() {
} }
int16_t LoRaWANNode::setupChannels(uint8_t* cfList) { int16_t LoRaWANNode::setupChannels(uint8_t* cfList) {
uint8_t num = 0; size_t num = 0;
LoRaWANChannel_t chnl;
// in case of frequency list-type band, copy the default TX channels into the available channels, with RX1 = TX // in case of frequency list-type band, copy the default TX channels into the available channels, with RX1 = TX
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
for(uint8_t i = 0; i < 3; i++) { // copy the default defined channels into the first slots
chnl = this->band->txFreqs[i]; for(; num < 3 && this->band->txFreqs[num].enabled; num++) {
if(chnl.enabled) { availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = this->band->txFreqs[num];
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = this->band->txFreqs[num];
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = chnl; RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].idx, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
num++;
}
} }
// 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(cfList != nullptr) {
for(uint8_t i = 0; i < 5; i++) { for(uint8_t i = 0; i < 5; i++, num++) {
LoRaWANChannel_t chnl;
chnl.enabled = true; chnl.enabled = true;
chnl.idx = num; chnl.idx = num;
uint32_t freq = LoRaWANNode::ntoh<uint32_t>(&cfList[3*i], 3); uint32_t freq = LoRaWANNode::ntoh<uint32_t>(&cfList[3*i], 3);
@ -1308,97 +1306,175 @@ int16_t LoRaWANNode::setupChannels(uint8_t* cfList) {
chnl.drMax = this->band->txFreqs[0].drMax; // drMax is equal for all channels chnl.drMax = this->band->txFreqs[0].drMax; // drMax is equal for all channels
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl;
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = chnl; availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][num] = chnl;
RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", chnl.idx, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
num++;
} }
} }
} else { // RADIOLIB_LORAWAN_CFLIST_TYPE_MASK
uint8_t chSpan = 0;
uint8_t chNum = 0;
// in case of mask-type bands, copy those frequencies that are masked true into the available TX channels
for(uint8_t i = 0; i < 5; i++) {
uint16_t mask = LoRaWANNode::ntoh<uint16_t>(&cfList[2*i]);
RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", i, mask);
for(uint8_t j = 0; j < 16; j++) {
// 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++;
}
if(mask & (1UL << j)) { } else { // RADIOLIB_LORAWAN_BAND_FIXED
if(chSpan >= this->band->numTxSpans || chNum >= this->band->txSpans[chSpan].numChannels) { if(cfList != nullptr) {
RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!"); uint8_t chSpan = 0;
return(RADIOLIB_ERR_UNKNOWN); uint8_t chNum = 0;
// in case of mask-type bands, copy those frequencies that are masked true into the available TX channels
for(size_t chMaskCntl = 0; chMaskCntl < 5; chMaskCntl++) {
uint16_t mask = LoRaWANNode::ntoh<uint16_t>(&cfList[2*chMaskCntl]);
RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, mask);
for(size_t i = 0; i < 16; 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++;
} }
chnl.enabled = true;
chnl.idx = i*16 + j; if(mask & (1UL << i)) {
chnl.freq = this->band->txSpans[chSpan].freqStart + chNum*this->band->txSpans[chSpan].freqStep; if(chSpan >= this->band->numTxSpans) {
chnl.drMin = this->band->txSpans[chSpan].drMin; RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!");
chnl.drMax = this->band->txSpans[chSpan].drMax; return(RADIOLIB_ERR_UNKNOWN);
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl; }
RADIOLIB_DEBUG_PRINTLN("Channel UL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq); LoRaWANChannel_t chnl;
num++; 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;
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num] = chnl;
// downlink channels are dynamically calculated on each uplink in selectChannels()
RADIOLIB_DEBUG_PRINTLN("Channel UL %d frequency = %f MHz", num, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][num].freq);
num++;
}
chNum++;
} }
chNum++;
} }
} }
} }
return(RADIOLIB_ERR_NONE); return(RADIOLIB_ERR_NONE);
} }
int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce) { int16_t LoRaWANNode::selectSubband(uint8_t idx) {
LoRaWANChannel_t channelUp; int16_t state = this->selectSubband((idx - 1) * 8, idx * 8);
LoRaWANChannel_t channelDn; return(state);
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { }
// count the number of available channels for a join-request
uint8_t numJRChannels = 0;
for(uint8_t i = 0; i < 3; i++) {
if(this->band->txFreqs[i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
numJRChannels++;
}
if(this->band->txJoinReq[i].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
numJRChannels++;
}
}
uint8_t channelId = devNonce % numJRChannels; // cycle through channels (seed with devNonce)
if(channelId < 3) {
channelUp = this->band->txFreqs[channelId];
} else {
channelUp = this->band->txJoinReq[channelId - 3];
}
channelDn = channelUp; // RX1 is equal to TX
// configure data rates for TX and RX1: for TX the (floored) average of min and max; for RX1 identical with base offset int16_t LoRaWANNode::selectSubband(uint8_t startChannel, uint8_t endChannel) {
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = int((channelUp.drMax + channelUp.drMin) / 2); if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], RADIOLIB_DEBUG_PRINTLN("This is a dynamic band plan which does not support subbands");
this->rx1DrOffset, this->band->rx1DataRateBase, channelDn.drMin, channelDn.drMax); return(RADIOLIB_ERR_INVALID_CHANNEL);
} else { // RADIOLIB_LORAWAN_CFLIST_TYPE_MASK }
uint8_t numChannels = endChannel - startChannel;
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;
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, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][chNum].freq);
}
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband) {
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(drJoinSubband != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
if(drJoinSubband >= channelUp.drMin && drJoinSubband <= channelUp.drMax) {
drUp = drJoinSubband;
} else {
RADIOLIB_DEBUG_PRINTLN("Datarate %d is not valid (min: %d, max %d) - using default", drJoinSubband, channelUp.drMin, channelUp.drMax);
drJoinSubband = RADIOLIB_LORAWAN_DATA_RATE_UNUSED;
}
}
if(drJoinSubband == 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
channelUp.enabled = true; channelUp.enabled = true;
uint8_t numBlocks = this->band->txSpans[0].numChannels / 8; // calculate number of 8-channel blocks uint8_t numBlocks = this->band->txSpans[0].numChannels / 8; // calculate number of 8-channel blocks
uint8_t numBlockChannels = 8 + this->band->txSpans[1].numChannels > 0 ? 1 : 0; // add a 9th channel if there's a second span 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) uint8_t blockID = devNonce % numBlocks; // currently selected block (seed with devNonce)
// if the user defined a specific subband, use that
if(drJoinSubband == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
blockID = (drJoinSubband - 1);
}
uint8_t channelID = this->phyLayer->random(numBlockChannels); // select randomly from these 8 or 9 channels uint8_t 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
uint8_t spanID; uint8_t spanID;
if(channelID < 8) { if(channelID < 8) {
spanID = 0; spanID = 0;
channelUp.idx = blockID * numBlockChannels + channelID; channelUp.idx = blockID * 8 + channelID;
} else { } else {
spanID = 1; spanID = 1;
channelUp.idx = blockID; channelUp.idx = blockID;
} }
channelUp.freq = this->band->txSpans[spanID].freqStart + channelUp.idx*this->band->txSpans[spanID].freqStep; channelUp.freq = this->band->txSpans[spanID].freqStart + channelUp.idx*this->band->txSpans[spanID].freqStep;
channelDn.idx = blockID % this->band->rx1Span.numChannels; // for fixed channel plans, the user-specified datarate is ignored and span-specific value must be used
channelDn.freq = this->band->rx1Span.freqStart + channelDn.idx*this->band->rx1Span.freqStep; 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);
// configure data rates for TX and RX1: for TX the specified value for this band; for RX1 identical with base offset
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = this->band->txSpans[spanID].joinRequestDataRate;
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK],
this->rx1DrOffset, this->band->rx1DataRateBase, channelDn.drMin, channelDn.drMax);
} }
this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = channelUp; this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = channelUp;
this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDn; 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);
} }
@ -1418,14 +1494,20 @@ int16_t LoRaWANNode::selectChannels() {
break; break;
} }
} }
if(numChannels == 0) {
RADIOLIB_DEBUG_PRINTLN("There are no channels defined - are you in ABP mode with no defined subband?");
return(RADIOLIB_ERR_INVALID_CHANNEL);
}
// select a random ID & channel from the list of enabled and possible channels // select a random ID & channel from the list of enabled and possible channels
uint8_t channelID = channelsEnabled[this->phyLayer->random(numChannels)]; uint8_t channelID = channelsEnabled[this->phyLayer->random(numChannels)];
this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][channelID]; this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][channelID];
// in case of frequency list-type band, downlink is equal to uplink, otherwise retrieve `modulo` numChannels if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { // for dynamic bands, the downlink channel is the one matched to the uplink channel
this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][channelID]; this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][channelID];
} else { // RADIOLIB_LORAWAN_CFLIST_TYPE_MASK
} else { // RADIOLIB_LORAWAN_BAND_FIXED
// for fixed bands, the downlink channel is the uplink channel ID `modulo` number of downlink channels
LoRaWANChannel_t channelDn; LoRaWANChannel_t channelDn;
channelDn.enabled = true; channelDn.enabled = true;
channelDn.idx = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx % this->band->rx1Span.numChannels; channelDn.idx = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx % this->band->rx1Span.numChannels;
@ -1433,10 +1515,34 @@ int16_t LoRaWANNode::selectChannels() {
channelDn.drMin = this->band->rx1Span.drMin; channelDn.drMin = this->band->rx1Span.drMin;
channelDn.drMax = this->band->rx1Span.drMax; channelDn.drMax = this->band->rx1Span.drMax;
this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDn; this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = channelDn;
uint8_t drDown = getDownlinkDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], this->rx1DrOffset,
this->band->rx1DataRateBase, channelDn.drMin, channelDn.drMax);
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown;
} }
return(RADIOLIB_ERR_NONE); return(RADIOLIB_ERR_NONE);
} }
int16_t LoRaWANNode::setDatarate(uint8_t drUp) {
if(drUp < this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMin) {
return(RADIOLIB_ERR_DATA_RATE_INVALID);
}
if(drUp > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].drMax) {
return(RADIOLIB_ERR_DATA_RATE_INVALID);
}
uint8_t drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase,
this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin, this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax);
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp;
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown;
return(RADIOLIB_ERR_NONE);
}
void LoRaWANNode::setADR(bool enable) {
this->adrEnabled = enable;
}
int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) {
uint8_t dataRateBand = this->band->dataRates[dr]; uint8_t dataRateBand = this->band->dataRates[dr];
@ -1550,27 +1656,23 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
case(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR): { case(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR): {
// get the ADR configuration // get the ADR configuration
// TODO all these configuration should only be set if all ACKs are set, otherwise retain previous state // TODO all these configuration should only be set if all ACKs are set, otherwise retain previous state (per spec)
uint8_t dr = (cmd->payload[0] & 0xF0) >> 4; uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4;
uint8_t txPower = cmd->payload[0] & 0x0F; uint8_t txPower = cmd->payload[0] & 0x0F;
uint16_t chMask = LoRaWANNode::ntoh<uint16_t>(&cmd->payload[1]); uint16_t chMask = LoRaWANNode::ntoh<uint16_t>(&cmd->payload[1]);
uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4; uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4;
uint8_t nbTrans = cmd->payload[3] & 0x0F; uint8_t nbTrans = cmd->payload[3] & 0x0F;
RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%02x, chMaskCntl = %02x, nbTrans = %d", dr, txPower, chMask, chMaskCntl, nbTrans); RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%02x, chMaskCntl = %02x, nbTrans = %d", drUp, txPower, chMask, chMaskCntl, nbTrans);
// apply the configuration // apply the configuration
uint8_t drAck = 0; uint8_t drAck = 0;
if(dr == 0x0F) { if(drUp == 0x0F) {
drAck = 1; drAck = 1;
} else if (this->band->dataRates[dr] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { } else if (this->band->dataRates[drUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = dr; uint8_t drDown = getDownlinkDataRate(drUp, this->rx1DrOffset, this->band->rx1DataRateBase,
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = dr + this->band->rx1DataRateBase - this->rx1DrOffset; this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin, this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax);
if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] < this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin) { this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = drUp;
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMin; this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = drDown;
} else if(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] > this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax) {
this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK].drMax;
}
drAck = 1; drAck = 1;
} }
@ -1587,28 +1689,66 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
this->txPwrCur = pwr; this->txPwrCur = pwr;
} }
this->nbTrans = nbTrans;
// TODO implement channel mask
uint8_t chMaskAck = 1; uint8_t chMaskAck = 1;
(void)chMaskCntl; if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
for(uint8_t i = 0; i < 16; i++) { if(chMaskCntl == 0) {
// check if this channel ID should be enabled // if chMaskCntl == 0, apply the mask by looking at each channel bit
RADIOLIB_DEBUG_PRINTLN("ADR channel %d: %d --> %d", i, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, (chMask >> i) & 0x01); RADIOLIB_DEBUG_PRINTLN("ADR channel %d: %d --> %d", i, this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled, (chMask >> i) & 0x01);
if(chMask & (1UL << i)) { if(chMask & (1UL << i)) {
// if it should be enabled but is not currently defined, stop immediately // if it should be enabled but is not currently defined, stop immediately
if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled == false) { if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
chMaskAck = 0; chMaskAck = 0;
break; break;
}
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true;
} else {
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = false;
} }
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true;
} else {
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = false;
}
}
} else {
} 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 about if none is present yet
(void)deleteMacCommand(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR, &this->commandsUp);
RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", chMaskCntl, chMask);
uint8_t chNum = chMaskCntl*16;
uint8_t chSpan = 0;
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; 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++;
}
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;
availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = chnl;
// downlink channels are dynamically calculated on each uplink in selectChannels()
RADIOLIB_DEBUG_PRINTLN("Channel UL %d frequency = %f MHz", i, availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq);
}
chNum++;
}
} }
// TODO should we actually save the channels because the masks may have changed stuff?
// this may wear the storage quickly on more mobile devices / changing RF environment
this->nbTrans = nbTrans;
// send the reply // send the reply
cmd->len = 1; cmd->len = 1;
@ -1679,8 +1819,8 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
// find first empty channel and configure this as the new channel // find first empty channel and configure this as the new channel
if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx == 0) { if(this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx == 0) {
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true; this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].enabled = true;
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx = chIndex; this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].idx = chIndex;
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq = freq; this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].freq = freq;
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin = minDr; this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMin = minDr;
this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax = maxDr; this->availableChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i].drMax = maxDr;

View file

@ -69,8 +69,8 @@
#define RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK (0x01 << 0) #define RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK (0x01 << 0)
#define RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH (0x02 << 0) #define RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH (0x02 << 0)
#define RADIOLIB_LORAWAN_CHANNEL_DIR_NONE (0x03 << 0) #define RADIOLIB_LORAWAN_CHANNEL_DIR_NONE (0x03 << 0)
#define RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES (0) #define RADIOLIB_LORAWAN_BAND_DYNAMIC (0)
#define RADIOLIB_LORAWAN_CFLIST_TYPE_MASK (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 < 0) #define RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE (0xFF < 0)
@ -239,6 +239,9 @@ struct LoRaWANChannelSpan_t {
\brief Structure to save information about LoRaWAN band \brief Structure to save information about LoRaWAN band
*/ */
struct LoRaWANBand_t { struct LoRaWANBand_t {
/*! \brief Whether the channels are fixed per specification, or dynamically allocated through the network (plus defaults) */
uint8_t bandType;
/*! \brief Array of allowed maximum payload lengths for each data rate */ /*! \brief Array of allowed maximum payload lengths for each data rate */
uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES];
@ -248,9 +251,6 @@ struct LoRaWANBand_t {
/*! \brief Number of power steps in this band */ /*! \brief Number of power steps in this band */
int8_t powerNumSteps; int8_t powerNumSteps;
/*! \brief Whether the optional channels are defined as list of frequencies or bit mask */
uint8_t cfListType;
/*! \brief A set of default uplink (TX) channels for frequency-type bands */ /*! \brief A set of default uplink (TX) channels for frequency-type bands */
LoRaWANChannel_t txFreqs[3]; LoRaWANChannel_t txFreqs[3];
@ -357,13 +357,6 @@ class LoRaWANNode {
\returns \ref status_codes \returns \ref status_codes
*/ */
int16_t restore(); int16_t restore();
/*!
\brief Restore frame counter for uplinks from persistent storage.
Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling.
\returns \ref status_codes
*/
int16_t restoreFcntUp();
#endif #endif
/*! /*!
@ -373,10 +366,12 @@ class LoRaWANNode {
\param devEUI 8-byte device identifier. \param devEUI 8-byte device identifier.
\param nwkKey Pointer to the network AES-128 key. \param nwkKey Pointer to the network AES-128 key.
\param appKey Pointer to the application AES-128 key. \param appKey Pointer to the application AES-128 key.
\param drJoinSubband (OTAA:) The datarate at which to send the join-request; (ABP:) the subband at which to send the join-request
\param force Set to true to force joining even if previously joined. \param force Set to true to force joining even if previously joined.
\returns \ref status_codes \returns \ref status_codes
*/ */
int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false); int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, uint8_t joinDrSubband = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, bool force = false);
/*! /*!
\brief Join network by performing activation by personalization. \brief Join network by performing activation by personalization.
@ -398,23 +393,15 @@ class LoRaWANNode {
*/ */
int16_t saveSession(); int16_t saveSession();
/*!
\brief Save the current uplink frame counter.
Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling.
\returns \ref status_codes
*/
int16_t saveFcntUp();
#if defined(RADIOLIB_BUILD_ARDUINO) #if defined(RADIOLIB_BUILD_ARDUINO)
/*! /*!
\brief Send a message to the server. \brief Send a message to the server.
\param str Address of Arduino String that will be transmitted. \param str Address of Arduino String that will be transmitted.
\param port Port number to send the message to. \param port Port number to send the message to.
\param isConfirmed Whether to send a confirmed uplink or not. \param isConfirmed Whether to send a confirmed uplink or not.
\param adrEnabled Whether ADR is enabled or not.
\returns \ref status_codes \returns \ref status_codes
*/ */
int16_t uplink(String& str, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); int16_t uplink(String& str, uint8_t port, bool isConfirmed = false);
#endif #endif
/*! /*!
@ -422,10 +409,9 @@ class LoRaWANNode {
\param str C-string that will be transmitted. \param str C-string that will be transmitted.
\param port Port number to send the message to. \param port Port number to send the message to.
\param isConfirmed Whether to send a confirmed uplink or not. \param isConfirmed Whether to send a confirmed uplink or not.
\param adrEnabled Whether ADR is enabled or not.
\returns \ref status_codes \returns \ref status_codes
*/ */
int16_t uplink(const char* str, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); int16_t uplink(const char* str, uint8_t port, bool isConfirmed = false);
/*! /*!
\brief Send a message to the server. \brief Send a message to the server.
@ -433,10 +419,9 @@ class LoRaWANNode {
\param len Length of the data. \param len Length of the data.
\param port Port number to send the message to. \param port Port number to send the message to.
\param isConfirmed Whether to send a confirmed uplink or not. \param isConfirmed Whether to send a confirmed uplink or not.
\param adrEnabled Whether ADR is enabled or not.
\returns \ref status_codes \returns \ref status_codes
*/ */
int16_t uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); int16_t uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed = false);
/*! /*!
\brief Wait for, open and listen during Rx1 and Rx2 windows; only performs listening \brief Wait for, open and listen during Rx1 and Rx2 windows; only performs listening
@ -468,6 +453,34 @@ class LoRaWANNode {
*/ */
void setDeviceStatus(uint8_t battLevel); void setDeviceStatus(uint8_t battLevel);
/*!
\brief Set uplink datarate. This should _not_ be used when ADR is enabled.
\param dr Datarate to use for uplinks
\returns \ref status_codes
*/
int16_t setDatarate(uint8_t drUp);
/*!
\brief Toggle ADR to on or off
\param enable Whether to disable ADR or not
*/
void setADR(bool enable = true);
/*!
\brief Select a single subband (8 channels) for fixed bands such as US915
\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
\param startChannel The first channel of the band to be used (inclusive)
\param endChannel The last channel of the band to be used (exclusive)
\returns \ref status_codes
*/
int16_t selectSubband(uint8_t startChannel, uint8_t endChannel);
#if !defined(RADIOLIB_GODMODE) #if !defined(RADIOLIB_GODMODE)
private: private:
#endif #endif
@ -511,6 +524,9 @@ class LoRaWANNode {
uint32_t confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; uint32_t confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE;
uint32_t adrFcnt = 0; uint32_t adrFcnt = 0;
// ADR is enabled by default
bool adrEnabled = true;
// available channel frequencies from list passed during OTA activation // available channel frequencies from list passed during OTA activation
LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS] = { { 0 }, { 0 } }; LoRaWANChannel_t availableChannels[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS] = { { 0 }, { 0 } };
@ -535,6 +551,22 @@ class LoRaWANNode {
// indicates whether an uplink has MAC commands as payload // indicates whether an uplink has MAC commands as payload
bool isMACPayload = false; bool isMACPayload = false;
#if !defined(RADIOLIB_EEPROM_UNSUPPORTED)
/*!
\brief Save the current uplink frame counter.
Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling.
\returns \ref status_codes
*/
int16_t saveFcntUp();
/*!
\brief Restore frame counter for uplinks from persistent storage.
Note that the usable frame counter width is 'only' 30 bits for highly efficient wear-levelling.
\returns \ref status_codes
*/
int16_t restoreFcntUp();
#endif
// method to generate message integrity code // method to generate message integrity code
uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key); uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key);
@ -551,7 +583,7 @@ class LoRaWANNode {
int16_t setupChannels(uint8_t* cfList); int16_t setupChannels(uint8_t* cfList);
// select a set of semi-random TX/RX channels for the join-request and -accept message // select a set of semi-random TX/RX channels for the join-request and -accept message
int16_t selectChannelsJR(uint16_t devNonce); int16_t selectChannelsJR(uint16_t devNonce, uint8_t drJoinSubband);
// 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();

View file

@ -13,10 +13,10 @@ uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_
} }
const LoRaWANBand_t EU868 = { const LoRaWANBand_t EU868 = {
.bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 16, .powerMax = 16,
.powerNumSteps = 7, .powerNumSteps = 7,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.txFreqs = { .txFreqs = {
{ .enabled = true, .idx = 0, .freq = 868.100, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 0, .freq = 868.100, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 868.300, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 1, .freq = 868.300, .drMin = 0, .drMax = 5},
@ -55,10 +55,10 @@ const LoRaWANBand_t EU868 = {
}; };
const LoRaWANBand_t US915 = { const LoRaWANBand_t US915 = {
.bandType = RADIOLIB_LORAWAN_BAND_FIXED,
.payloadLenMax = { 19, 61, 133, 250, 250, 0, 0, 0, 41, 117, 230, 230, 230, 230, 0 }, .payloadLenMax = { 19, 61, 133, 250, 250, 0, 0, 0, 41, 117, 230, 230, 230, 230, 0 },
.powerMax = 30, .powerMax = 30,
.powerNumSteps = 10, .powerNumSteps = 10,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK,
.txFreqs = { .txFreqs = {
RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE,
RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE,
@ -118,10 +118,10 @@ const LoRaWANBand_t US915 = {
}; };
const LoRaWANBand_t CN780 = { const LoRaWANBand_t CN780 = {
.bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 250, 230, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 250, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 12, .powerMax = 12,
.powerNumSteps = 5, .powerNumSteps = 5,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.txFreqs = { .txFreqs = {
{ .enabled = true, .idx = 0, .freq = 779.500, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 0, .freq = 779.500, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 779.700, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 1, .freq = 779.700, .drMin = 0, .drMax = 5},
@ -160,10 +160,10 @@ const LoRaWANBand_t CN780 = {
}; };
const LoRaWANBand_t EU433 = { const LoRaWANBand_t EU433 = {
.bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 12, .powerMax = 12,
.powerNumSteps = 5, .powerNumSteps = 5,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.txFreqs = { .txFreqs = {
{ .enabled = true, .idx = 0, .freq = 433.175, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 0, .freq = 433.175, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 433.375, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 1, .freq = 433.375, .drMin = 0, .drMax = 5},
@ -202,10 +202,10 @@ const LoRaWANBand_t EU433 = {
}; };
const LoRaWANBand_t AU915 = { const LoRaWANBand_t AU915 = {
.bandType = RADIOLIB_LORAWAN_BAND_FIXED,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 0, 41, 117, 230, 230, 230, 230, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 0, 41, 117, 230, 230, 230, 230, 0 },
.powerMax = 30, .powerMax = 30,
.powerNumSteps = 10, .powerNumSteps = 10,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK,
.txFreqs = { .txFreqs = {
RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE,
RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE,
@ -265,10 +265,10 @@ const LoRaWANBand_t AU915 = {
}; };
const LoRaWANBand_t CN500 = { const LoRaWANBand_t CN500 = {
.bandType = RADIOLIB_LORAWAN_BAND_FIXED,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 19, .powerMax = 19,
.powerNumSteps = 7, .powerNumSteps = 7,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK,
.txFreqs = { .txFreqs = {
RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE,
RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE,
@ -321,10 +321,10 @@ const LoRaWANBand_t CN500 = {
}; };
const LoRaWANBand_t AS923 = { const LoRaWANBand_t AS923 = {
.bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 16, .powerMax = 16,
.powerNumSteps = 7, .powerNumSteps = 7,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.txFreqs = { .txFreqs = {
{ .enabled = true, .idx = 0, .freq = 923.200, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 0, .freq = 923.200, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 923.400, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 1, .freq = 923.400, .drMin = 0, .drMax = 5},
@ -363,10 +363,10 @@ const LoRaWANBand_t AS923 = {
}; };
const LoRaWANBand_t KR920 = { const LoRaWANBand_t KR920 = {
.bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 14, .powerMax = 14,
.powerNumSteps = 7, .powerNumSteps = 7,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.txFreqs = { .txFreqs = {
{ .enabled = true, .idx = 0, .freq = 922.100, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 0, .freq = 922.100, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 922.300, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 1, .freq = 922.300, .drMin = 0, .drMax = 5},
@ -405,10 +405,10 @@ const LoRaWANBand_t KR920 = {
}; };
const LoRaWANBand_t IN865 = { const LoRaWANBand_t IN865 = {
.bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC,
.payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 }, .payloadLenMax = { 59, 59, 59, 123, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 30, .powerMax = 30,
.powerNumSteps = 10, .powerNumSteps = 10,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.txFreqs = { .txFreqs = {
{ .enabled = true, .idx = 0, .freq = 865.0625, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 0, .freq = 865.0625, .drMin = 0, .drMax = 5},
{ .enabled = true, .idx = 1, .freq = 865.4025, .drMin = 0, .drMax = 5}, { .enabled = true, .idx = 1, .freq = 865.4025, .drMin = 0, .drMax = 5},

View file

@ -306,6 +306,10 @@ int16_t PhysicalLayer::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask)
return(RADIOLIB_ERR_UNSUPPORTED); return(RADIOLIB_ERR_UNSUPPORTED);
} }
bool PhysicalLayer::isRxTimeout() {
return(false);
}
int16_t PhysicalLayer::startChannelScan() { int16_t PhysicalLayer::startChannelScan() {
return(RADIOLIB_ERR_UNSUPPORTED); return(RADIOLIB_ERR_UNSUPPORTED);
} }

View file

@ -326,6 +326,12 @@ class PhysicalLayer {
*/ */
virtual int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); virtual int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask);
/*!
\brief Check whether the IRQ bit for RxTimeout is set
\returns \ref RxTimeout IRQ is set
*/
virtual bool isRxTimeout();
/*! /*!
\brief Interrupt-driven channel activity detection method. interrupt will be activated \brief Interrupt-driven channel activity detection method. interrupt will be activated
when packet is detected. Must be implemented in module class. when packet is detected. Must be implemented in module class.