diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index a312a011..7c6f8baa 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -1702,27 +1702,41 @@ int16_t SX126x::setRx(uint32_t timeout) { return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RX, data, 3, true, false)); } + int16_t SX126x::setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) { - // default CAD parameters for assigned SF as per Semtech AN1200.48, Rev 2.1, Page 50 - uint8_t detPeakValues[8] = { 22, 22, 22, 22, 23, 24, 25, 28}; - uint8_t symbolNumValues[8] = { RADIOLIB_SX126X_CAD_ON_2_SYMB, - RADIOLIB_SX126X_CAD_ON_2_SYMB, - RADIOLIB_SX126X_CAD_ON_2_SYMB, - RADIOLIB_SX126X_CAD_ON_2_SYMB, - RADIOLIB_SX126X_CAD_ON_4_SYMB, - RADIOLIB_SX126X_CAD_ON_4_SYMB, - RADIOLIB_SX126X_CAD_ON_4_SYMB, - RADIOLIB_SX126X_CAD_ON_4_SYMB }; + // default CAD parameters are shown in Semtech AN1200.48, page 41. + uint8_t detPeakValues[6] = { 22, 22, 24, 25, 26, 30}; + + // CAD parameters aren't available for SF-6. Just to be safe. + if(this->spreadingFactor < 7) { + this->spreadingFactor = 7; + } else if(this->spreadingFactor > 12) { + this->spreadingFactor = 12; + } + // build the packet uint8_t data[7]; - data[0] = symbolNumValues[this->spreadingFactor - 5]; - data[1] = detPeakValues[this->spreadingFactor - 5]; + data[0] = RADIOLIB_SX126X_CAD_ON_2_SYMB; + data[1] = detPeakValues[this->spreadingFactor - 7]; data[2] = RADIOLIB_SX126X_CAD_PARAM_DET_MIN; data[3] = RADIOLIB_SX126X_CAD_GOTO_STDBY; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; + + /* + CAD Configuration Note: + The default CAD configuration applied by `scanChannel` overrides the optimal SF-specific configurations, leading to suboptimal detection. + I.e., anything that is not RADIOLIB_SX126X_CAD_PARAM_DEFAULT is overridden. But CAD settings are SF specific. + To address this, the user override has been commented out, ensuring consistent application of the optimal CAD settings as + per Semtech's Application Note AN1200.48 (page 41) for the 125KHz setting. This approach significantly reduces false CAD occurrences. + Testing has shown that there is no reason for a user to change CAD settings for anything other than most optimal ones described in AN1200.48 . + However, this change deos not respect CAD configs from the LoRaWAN layer. Future considerations or use cases might require revisiting this decision. + Hence this note. +*/ + +/* // set user-provided values if(symbolNum != RADIOLIB_SX126X_CAD_PARAM_DEFAULT) { data[0] = symbolNum; @@ -1736,6 +1750,9 @@ int16_t SX126x::setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) { data[2] = detMin; } +*/ + + // configure parameters int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data, 7); RADIOLIB_ASSERT(state); @@ -2030,7 +2047,7 @@ int16_t SX126x::config(uint8_t modem) { state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, data, 1); RADIOLIB_ASSERT(state); - // set some CAD parameters - will be overwritten whel calling CAD anyway + // set some CAD parameters - will be overwritten when calling CAD anyway data[0] = RADIOLIB_SX126X_CAD_ON_8_SYMB; data[1] = this->spreadingFactor + 13; data[2] = RADIOLIB_SX126X_CAD_PARAM_DET_MIN; diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index b903343c..30ffe5ee 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -37,6 +37,10 @@ LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) { this->startChannel = -1; this->numChannels = -1; this->backupFreq = this->band->backupChannel.freqStart; + this->difsSlots = 2; + this->backoffMax = 6; + this->enableCSMA = false; + } void LoRaWANNode::wipe() { @@ -44,6 +48,13 @@ void LoRaWANNode::wipe() { mod->hal->wipePersistentStorage(); } +void LoRaWANNode::setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA) { + this->backoffMax = backoffMax; + this->difsSlots = difsSlots; + this->enableCSMA = enableCSMA; +} + + int16_t LoRaWANNode::restoreOTAA() { int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); @@ -637,6 +648,11 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF); } + // perform CSMA if enabled. + if (enableCSMA) { + performCSMA(); + } + RADIOLIB_DEBUG_PRINTLN("uplinkMsg:"); RADIOLIB_DEBUG_HEXDUMP(uplinkMsg, uplinkMsgLen); @@ -1762,4 +1778,52 @@ void LoRaWANNode::hton(uint8_t* buff, T val, size_t size) { } } -#endif +// The following function enables LMAC, a CSMA scheme for LoRa as specified +// in the LoRa Alliance Technical Recommendation #13. +// A user may enable CSMA to provide frames an additional layer of protection from interference. +// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma +void LoRaWANNode::performCSMA() { + + // Compute initial random back-off. + // When BO is reduced to zero, the function returns and the frame is transmitted. + uint32_t BO = this->phyLayer->random(1, this->backoffMax + 1); + + while (BO > 0) { + // DIFS: Check channel for DIFS_slots + bool channelFreeDuringDIFS = true; + for (uint8_t i = 0; i < this->difsSlots; i++) { + if (performCAD()) { + RADIOLIB_DEBUG_PRINTLN("OCCUPIED CHANNEL DURING DIFS"); + channelFreeDuringDIFS = false; + // Channel is occupied during DIFS, hop to another. + this->setupChannels(); + break; + } + } + + // Start reducing BO counter if DIFS slot was free. + if (channelFreeDuringDIFS) { + // Continue decrementing BO with per each CAD reporting free channel. + while (BO > 0) { + if (performCAD()) { + RADIOLIB_DEBUG_PRINTLN("OCCUPIED CHANNEL DURING BO"); + // Channel is busy during CAD, hop to another and return to DIFS state again. + this->setupChannels(); + break; // Exit loop. Go back to DIFS state. + } + BO--; // Decrement BO by one if channel is free + } + } + } +} + +bool LoRaWANNode::performCAD() { + int16_t state = this->phyLayer->scanChannel(); + + if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { + return true; // Channel is busy + } + return false; // Channel is free +} + +#endif \ No newline at end of file diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index 65efe7eb..82b08127 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -296,6 +296,17 @@ class LoRaWANNode { (e.g. 8 for US915 FSB2 used by TTN). By default -1 (no channel offset). */ int8_t numChannels; + /*! \brief Num of Back Off(BO) slots to be decremented after DIFS phase. 0 to disable BO. + A random BO avoids collisions in the case where two or more nodes start the CSMA + process at the same time. */ + uint8_t backoffMax; + + /*! \brief Num of CADs to estimate a clear CH. */ + uint8_t difsSlots; + + /*! \brief enable/disable CSMA for LoRaWAN. */ + bool enableCSMA; + /*! \brief Default constructor. \param phy Pointer to the PhysicalLayer radio module. @@ -309,6 +320,14 @@ class LoRaWANNode { */ void wipe(); + /*! + \brief Configures CSMA for LoRaWAN as per TR-13, LoRa Alliance. + \param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO. + \param difsSlots Num of CADs to estimate a clear CH. + \param enableCSMA enable/disable CSMA for LoRaWAN. + */ + void setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA = false); + /*! \brief Restore OTAA session by loading information from persistent storage. \returns \ref status_codes @@ -502,6 +521,12 @@ class LoRaWANNode { // host-to-network conversion method - takes data from host variable and and converts it to network packet endians template static void hton(uint8_t* buff, T val, size_t size = 0); + + // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. + bool performCAD(); + + // Performs CSMA as per LoRa Alliance Technical Reccomendation 13 (TR-013). + void performCSMA(); }; #endif