Porównaj commity

...

6 Commity

Autor SHA1 Wiadomość Data
jgromes f61be0d273 [SX126x] Added public method to set PA ramp time (#1054) 2024-04-05 17:30:05 +02:00
jgromes 263f7883cf [SX126x] Use integer frequency for band selection 2024-04-04 21:31:09 +02:00
jgromes a387b3b706 [SX126x] Fix image calibration (#1051) 2024-04-04 21:28:17 +02:00
jgromes 5d741779a1 [SX126x] Use predefined image calibration bands (#1051) 2024-04-04 21:27:12 +02:00
StevenCellist aa46a0c8b3 [LoRaWAN] Hide broken CSMA 2024-04-02 22:25:50 +02:00
StevenCellist a4ad32e6ff [LoRaWAN] Fix downlink crashes (#1049), remove redundant parameter 2024-04-02 22:24:06 +02:00
11 zmienionych plików z 145 dodań i 54 usunięć

Wyświetl plik

@ -60,13 +60,9 @@ void setup() {
// Disable the ADR algorithm (on by default which is preferable)
node.setADR(false);
// Set a fixed datarate & make it persistent (not normal)
// Set a fixed datarate
node.setDatarate(4);
// Enable CSMA which tries to minimize packet loss by searching
// for a free channel before actually sending an uplink
node.setCSMA(6, 2, true);
// Manages uplink intervals to the TTN Fair Use Policy
node.setDutyCycle(true, 1250);

Wyświetl plik

@ -225,6 +225,7 @@ spectralScanStart KEYWORD2
spectralScanAbort KEYWORD2
spectralScanGetStatus KEYWORD2
spectralScanGetResult KEYWORD2
setPaRampTime KEYWORD2
# nRF24
setIrqAction KEYWORD2

Wyświetl plik

@ -17,9 +17,8 @@ int16_t SX1261::setOutputPower(int8_t power) {
state = SX126x::setPaConfig(0x04, RADIOLIB_SX126X_PA_CONFIG_SX1261, 0x00);
RADIOLIB_ASSERT(state);
// set output power
/// \todo power ramp time configuration
state = SX126x::setTxParams(power);
// set output power with default 200us ramp
state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U);
RADIOLIB_ASSERT(state);
// restore OCP configuration

Wyświetl plik

@ -51,13 +51,46 @@ int16_t SX1262::setFrequency(float freq) {
return(setFrequency(freq, true));
}
int16_t SX1262::setFrequency(float freq, bool calibrate, float band) {
int16_t SX1262::setFrequency(float freq, bool calibrate) {
RADIOLIB_CHECK_RANGE(freq, 150.0, 960.0, RADIOLIB_ERR_INVALID_FREQUENCY);
// calibrate image rejection
if(calibrate) {
int16_t state = SX126x::calibrateImage(freq - band, freq + band);
uint8_t data[2] = { 0, 0 };
// try to match the frequency ranges
int freqBand = (int)freq;
if((freqBand >= 902) && (freqBand <= 928)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_2;
} else if((freqBand >= 863) && (freqBand <= 870)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_2;
} else if((freqBand >= 779) && (freqBand <= 787)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_2;
} else if((freqBand >= 470) && (freqBand <= 510)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_2;
} else if((freqBand >= 430) && (freqBand <= 440)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_2;
}
int16_t state;
if(data[0]) {
// matched with predefined ranges, do the calibration
state = SX126x::calibrateImage(data);
} else {
// if nothing matched, try custom calibration - the may or may not work
RADIOLIB_DEBUG_BASIC_PRINTLN("Failed to match predefined frequency range, trying custom");
state = SX126x::calibrateImageRejection(freq - 4.0f, freq + 4.0f);
}
RADIOLIB_ASSERT(state);
}
// set frequency
@ -76,9 +109,8 @@ int16_t SX1262::setOutputPower(int8_t power) {
state = SX126x::setPaConfig(0x04, RADIOLIB_SX126X_PA_CONFIG_SX1262);
RADIOLIB_ASSERT(state);
// set output power
/// \todo power ramp time configuration
state = SX126x::setTxParams(power);
// set output power with default 200us ramp
state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U);
RADIOLIB_ASSERT(state);
// restore OCP configuration

Wyświetl plik

@ -75,12 +75,9 @@ class SX1262: public SX126x {
\brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz.
\param freq Carrier frequency to be set in MHz.
\param calibrate Run image calibration.
\param band Half bandwidth for image calibration. For example,
if carrier is 434 MHz and band is set to 4 MHz, then the image will be calibrate
for band 430 - 438 MHz. Unused if calibrate is set to false, defaults to 4 MHz
\returns \ref status_codes
*/
int16_t setFrequency(float freq, bool calibrate, float band = 4);
int16_t setFrequency(float freq, bool calibrate);
/*!
\brief Sets output power. Allowed values are in range from -9 to 22 dBm.

Wyświetl plik

@ -52,13 +52,40 @@ int16_t SX1268::setFrequency(float freq) {
}
/// \todo integers only (all modules - frequency, data rate, bandwidth etc.)
int16_t SX1268::setFrequency(float freq, bool calibrate, float band) {
int16_t SX1268::setFrequency(float freq, bool calibrate) {
RADIOLIB_CHECK_RANGE(freq, 410.0, 810.0, RADIOLIB_ERR_INVALID_FREQUENCY);
// calibrate image rejection
if(calibrate) {
int16_t state = SX126x::calibrateImage(freq - band, freq + band);
uint8_t data[2] = { 0, 0 };
// try to match the frequency ranges
int freqBand = (int)freq;
if((freqBand >= 779) && (freqBand <= 787)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_2;
} else if((freqBand >= 470) && (freqBand <= 510)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_2;
} else if((freqBand >= 430) && (freqBand <= 440)) {
data[0] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_1;
data[1] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_2;
}
int16_t state;
if(data[0]) {
// matched with predefined ranges, do the calibration
state = SX126x::calibrateImage(data);
} else {
// if nothing matched, try custom calibration - the may or may not work
RADIOLIB_DEBUG_BASIC_PRINTLN("Failed to match predefined frequency range, trying custom");
state = SX126x::calibrateImageRejection(freq - 4.0f, freq + 4.0f);
}
RADIOLIB_ASSERT(state);
}
// set frequency
@ -77,9 +104,8 @@ int16_t SX1268::setOutputPower(int8_t power) {
state = SX126x::setPaConfig(0x04, RADIOLIB_SX126X_PA_CONFIG_SX1268);
RADIOLIB_ASSERT(state);
// set output power
/// \todo power ramp time configuration
state = SX126x::setTxParams(power);
// set output power with default 200us ramp
state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U);
RADIOLIB_ASSERT(state);
// restore OCP configuration

Wyświetl plik

@ -74,12 +74,9 @@ class SX1268: public SX126x {
\brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz.
\param freq Carrier frequency to be set in MHz.
\param calibrate Run image calibration.
\param band Half bandwidth for image calibration. For example,
if carrier is 434 MHz and band is set to 4 MHz, then the image will be calibrate
for band 430 - 438 MHz. Unused if calibrate is set to false, defaults to 4 MHz
\returns \ref status_codes
*/
int16_t setFrequency(float freq, bool calibrate, float band = 4);
int16_t setFrequency(float freq, bool calibrate);
/*!
\brief Sets output power. Allowed values are in range from -9 to 22 dBm.

Wyświetl plik

@ -1841,8 +1841,17 @@ int16_t SX126x::setRfFrequency(uint32_t frf) {
return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data, 4));
}
int16_t SX126x::calibrateImage(float freqMin, float freqMax) {
int16_t SX126x::calibrateImageRejection(float freqMin, float freqMax) {
// calculate the calibration coefficients and calibrate image
uint8_t data[] = { (uint8_t)floor((freqMin - 1.0f) / 4.0f), (uint8_t)ceil((freqMax + 1.0f) / 4.0f) };
return(this->calibrateImage(data));
}
int16_t SX126x::setPaRampTime(uint8_t rampTime) {
return(this->setTxParams(this->pwr, rampTime));
}
int16_t SX126x::calibrateImage(uint8_t* data) {
int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE, data, 2);
// if something failed, show the device errors
@ -1865,7 +1874,11 @@ uint8_t SX126x::getPacketType() {
int16_t SX126x::setTxParams(uint8_t pwr, uint8_t rampTime) {
uint8_t data[] = { pwr, rampTime };
return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data, 2));
int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data, 2);
if(state == RADIOLIB_ERR_NONE) {
this->pwr = pwr;
}
return(state);
}
int16_t SX126x::setPacketMode(uint8_t mode, uint8_t len) {

Wyświetl plik

@ -188,6 +188,18 @@
#define RADIOLIB_SX126X_CALIBRATE_RC64K_ON 0b00000001 // 0 0 enabled
#define RADIOLIB_SX126X_CALIBRATE_ALL 0b01111111 // 6 0 calibrate all blocks
//RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE
#define RADIOLIB_SX126X_CAL_IMG_430_MHZ_1 0x6B
#define RADIOLIB_SX126X_CAL_IMG_430_MHZ_2 0x6F
#define RADIOLIB_SX126X_CAL_IMG_470_MHZ_1 0x75
#define RADIOLIB_SX126X_CAL_IMG_470_MHZ_2 0x81
#define RADIOLIB_SX126X_CAL_IMG_779_MHZ_1 0xC1
#define RADIOLIB_SX126X_CAL_IMG_779_MHZ_2 0xC5
#define RADIOLIB_SX126X_CAL_IMG_863_MHZ_1 0xD7
#define RADIOLIB_SX126X_CAL_IMG_863_MHZ_2 0xDB
#define RADIOLIB_SX126X_CAL_IMG_902_MHZ_1 0xE1
#define RADIOLIB_SX126X_CAL_IMG_902_MHZ_2 0xE9
//RADIOLIB_SX126X_CMD_SET_PA_CONFIG
#define RADIOLIB_SX126X_PA_CONFIG_HP_MAX 0x07
#define RADIOLIB_SX126X_PA_CONFIG_PA_LUT 0x01
@ -1102,6 +1114,21 @@ class SX126x: public PhysicalLayer {
*/
int16_t setPaConfig(uint8_t paDutyCycle, uint8_t deviceSel, uint8_t hpMax = RADIOLIB_SX126X_PA_CONFIG_HP_MAX, uint8_t paLut = RADIOLIB_SX126X_PA_CONFIG_PA_LUT);
/*!
\brief Perform image rejection calibration for the specified frequency band.
WARNING: Use at your own risk! Setting incorrect values may lead to decreased performance
\param freqMin Frequency band lower bound.
\param freqMax Frequency band upper bound.
\returns \ref status_codes
*/
int16_t calibrateImageRejection(float freqMin, float freqMax);
/*!
\brief Set PA ramp-up time. Set to 200us by default.
\returns \ref status_codes
*/
int16_t setPaRampTime(uint8_t rampTime);
#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL
protected:
#endif
@ -1119,9 +1146,9 @@ class SX126x: public PhysicalLayer {
int16_t setDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask = RADIOLIB_SX126X_IRQ_NONE, uint16_t dio3Mask = RADIOLIB_SX126X_IRQ_NONE);
virtual int16_t clearIrqStatus(uint16_t clearIrqParams = RADIOLIB_SX126X_IRQ_ALL);
int16_t setRfFrequency(uint32_t frf);
int16_t calibrateImage(float freqMin, float freqMax);
int16_t calibrateImage(uint8_t* data);
uint8_t getPacketType();
int16_t setTxParams(uint8_t power, uint8_t rampTime = RADIOLIB_SX126X_PA_RAMP_200U);
int16_t setTxParams(uint8_t power, uint8_t rampTime);
int16_t setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro);
int16_t setModulationParamsFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev);
int16_t setPacketParams(uint16_t preambleLen, uint8_t crcType, uint8_t payloadLen, uint8_t hdrType, uint8_t invertIQ);
@ -1166,6 +1193,7 @@ class SX126x: public PhysicalLayer {
float dataRateMeasured = 0;
uint32_t tcxoDelay = 0;
uint8_t pwr = 0;
size_t implicitLen = 0;
uint8_t invertIQEnabled = RADIOLIB_SX126X_LORA_IQ_STANDARD;

Wyświetl plik

@ -583,7 +583,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
uint32_t joinNonceNew = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinNoncePrev: %d, JoinNonce: %d", this->joinNonce, joinNonceNew);
// JoinNonce received must be greater than the last JoinNonce heard, else error
// JoinNonce received must be greater than the last JoinNonce heard, else error
if((this->joinNonce > 0) && (joinNonceNew <= this->joinNonce)) {
return(RADIOLIB_ERR_JOIN_NONCE_INVALID);
}
@ -898,11 +898,9 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// check if there are some MAC commands to piggyback (only when piggybacking onto a application-frame)
uint8_t foptsLen = 0;
size_t foptsBufSize = 0;
if(this->commandsUp.numCommands > 0 && port != RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
// there are, assume the maximum possible FOpts len for buffer allocation
foptsLen = this->commandsUp.len;
foptsBufSize = 15;
}
// check maximum payload len as defined in phy
@ -984,7 +982,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// build the uplink message
// the first 16 bytes are reserved for MIC calculation blocks
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsBufSize);
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
#if RADIOLIB_STATIC_ONLY
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#else
@ -1021,8 +1019,12 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf
// check if we have some MAC commands to append
if(foptsLen > 0) {
#if RADIOLIB_STATIC_ONLY
// assume maximum possible buffer size
uint8_t foptsBuff[15];
uint8_t foptsBuff[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN];
#else
uint8_t foptsBuff[foptsLen];
#endif
uint8_t* foptsPtr = foptsBuff;
// append all MAC replies into fopts buffer
@ -1491,7 +1493,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event)
#endif
// if FOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink
if(this->commandsUp.len > 15) {
if(this->commandsUp.len > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) {
size_t foptsBufSize = this->commandsUp.len;
#if RADIOLIB_STATIC_ONLY
uint8_t foptsBuff[RADIOLIB_STATIC_ARRAY_SIZE];
@ -1549,7 +1551,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event)
}
// a downlink was received, so reset the ADR counter to the last uplink's fcnt
this->adrFcnt = this->fcntUp - 1;
this->adrFcnt = this->getFcntUp();
// pass the extra info if requested
if(event) {
@ -2180,13 +2182,13 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
// per spec, all these configuration should only be set if all ACKs are set, otherwise retain previous state
// but we don't bother and try to set each individual command
uint8_t drUp = (cmd->payload[0] & 0xF0) >> 4;
uint8_t txPower = cmd->payload[0] & 0x0F;
uint8_t txSteps = cmd->payload[0] & 0x0F;
bool isInternalTxDr = cmd->payload[3] >> 7;
uint16_t chMask = LoRaWANNode::ntoh<uint16_t>(&cmd->payload[1]);
uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4;
uint8_t nbTrans = cmd->payload[3] & 0x0F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRReq: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %d, nbTrans = %d", drUp, txPower, chMask, chMaskCntl, nbTrans);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkADRReq: dataRate = %d, txSteps = %d, chMask = 0x%04x, chMaskCntl = %d, nbTrans = %d", drUp, txSteps, chMask, chMaskCntl, nbTrans);
// apply the configuration
uint8_t drAck = 0;
@ -2216,14 +2218,14 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
// try to apply the power configuration
uint8_t pwrAck = 0;
if(txPower == 0x0F) {
if(txSteps == 0x0F) {
pwrAck = 1;
// replace the 'placeholder' with the current actual value for saving
cmd->payload[0] = (cmd->payload[0] & 0xF0) | this->txPowerCur;
} else {
int8_t pwr = this->txPowerMax - 2*txPower;
int8_t pwr = this->txPowerMax - 2*txSteps;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: TX = %d dBm", pwr);
state = RADIOLIB_ERR_INVALID_OUTPUT_POWER;
while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) {
@ -2233,7 +2235,7 @@ bool LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
// only acknowledge if the requested datarate was succesfully configured
if(state == RADIOLIB_ERR_NONE) {
pwrAck = 1;
this->txPowerCur = txPower;
this->txPowerCur = txSteps;
}
}
@ -2765,7 +2767,7 @@ uint8_t LoRaWANNode::getMacPayloadLength(uint8_t cid) {
}
int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) {
uint8_t payload[5];
uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 };
int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_LINK_CHECK_REQ, &this->commandsDown, payload);
RADIOLIB_ASSERT(state);
@ -2776,14 +2778,14 @@ int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) {
}
int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) {
uint8_t payload[5];
uint8_t payload[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 };
int16_t state = deleteMacCommand(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, &this->commandsDown, payload);
RADIOLIB_ASSERT(state);
if(gpsEpoch) {
*gpsEpoch = LoRaWANNode::ntoh<uint32_t>(&payload[0]);
if(returnUnix) {
uint32_t unixOffset = 315964800 - 18; // 18 leap seconds since GPS epoch (Jan. 6th 1980)
uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980)
*gpsEpoch += unixOffset;
}
}

Wyświetl plik

@ -136,7 +136,7 @@
#define RADIOLIB_LORAWAN_FHDR_FCNT_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 6)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK (0x0F)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 16)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (15)
#define RADIOLIB_LORAWAN_FHDR_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8 + (FOPTS))
#define RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 9 + (FOPTS))
#define RADIOLIB_LORAWAN_FRAME_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
@ -808,14 +808,6 @@ class LoRaWANNode {
*/
int16_t setTxPower(int8_t txPower);
/*!
\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 Returns the quality of connectivity after requesting a LinkCheck MAC command.
Returns 'true' if a network response was successfully parsed.
@ -1017,6 +1009,14 @@ class LoRaWANNode {
// get the payload length for a specific MAC command
uint8_t getMacPayloadLength(uint8_t cid);
/*!
\brief Configures CSMA for LoRaWAN as per TR-13, LoRa Alliance.
\param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO.
\param difsSlots Num of CADs to estimate a clear CH.
\param enableCSMA enable/disable CSMA for LoRaWAN.
*/
void setCSMA(uint8_t backoffMax, uint8_t difsSlots, bool enableCSMA = false);
// Performs CSMA as per LoRa Alliance Technical Recommendation 13 (TR-013).
void performCSMA();