[LoRaWAN] Add methods to allow user-provided sleep function (#1410)

* [LoRaWAN] Add methods to allow user-provided sleep function

* Add example sleep function

* [LoRaWAN] Switch all delay calls to sleepDelay

* [LoRaWAN] Remove unused variable

---------

Co-authored-by: StevenCellist <steven@boonstoppel.nu>
This commit is contained in:
Jan Gromeš 2025-02-06 07:00:03 +01:00 committed by GitHub
parent 45de7978dc
commit 8c2c7b6cb5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 63 additions and 9 deletions

View file

@ -48,6 +48,9 @@ void setup() {
// Override the default join rate // Override the default join rate
uint8_t joinDR = 4; uint8_t joinDR = 4;
// Optionally provide a custom sleep function - see config.h
//node.setSleepFunction(customDelay);
// Setup the OTAA session information // Setup the OTAA session information
node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);

View file

@ -142,4 +142,19 @@ void arrayDump(uint8_t *buffer, uint16_t len) {
Serial.println(); Serial.println();
} }
// Custom delay function:
// Communication over LoRaWAN includes a lot of delays.
// By default, RadioLib will use the Arduino delay() function,
// which will waste a lot of power. However, you can put your
// microcontroller to sleep instead by customizing the function below,
// and providing it to RadioLib via "node.setSleepFunction".
// NOTE: You ahve to ensure that this function is timed precisely, and
// does actually wait for the amount of time specified!
// Failure to do so will result in missed downlinks or failed join!
void customDelay(RadioLibTime_t ms) {
// this is just an example, so we use the Arduino delay() function,
// but you can put your microcontroller to sleep here
::delay(ms);
}
#endif #endif

View file

@ -379,6 +379,7 @@ getLastToA KEYWORD2
dutyCycleInterval KEYWORD2 dutyCycleInterval KEYWORD2
timeUntilUplink KEYWORD2 timeUntilUplink KEYWORD2
getMaxPayloadLen KEYWORD2 getMaxPayloadLen KEYWORD2
setSleepFunction KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)

View file

@ -67,7 +67,6 @@ int16_t LoRaWANNode::sendReceive(const uint8_t* dataUp, size_t lenUp, uint8_t fP
return(RADIOLIB_ERR_NULL_POINTER); return(RADIOLIB_ERR_NULL_POINTER);
} }
int16_t state = RADIOLIB_ERR_UNKNOWN; int16_t state = RADIOLIB_ERR_UNKNOWN;
Module* mod = this->phyLayer->getMod();
// if after (at) ADR_ACK_LIMIT frames no RekeyConf was received, revert to Join state // if after (at) ADR_ACK_LIMIT frames no RekeyConf was received, revert to Join state
if(this->fCntUp == (1UL << this->adrLimitExp)) { if(this->fCntUp == (1UL << this->adrLimitExp)) {
@ -147,7 +146,7 @@ int16_t LoRaWANNode::sendReceive(const uint8_t* dataUp, size_t lenUp, uint8_t fP
// RETRANSMIT_TIMEOUT is 2s +/- 1s (RP v1.0.4) // RETRANSMIT_TIMEOUT is 2s +/- 1s (RP v1.0.4)
// must be present after any confirmed frame, so we force this here // must be present after any confirmed frame, so we force this here
if(isConfirmed) { if(isConfirmed) {
mod->hal->delay(this->phyLayer->random(1000, 3000)); this->sleepDelay(this->phyLayer->random(1000, 3000));
} }
// if an error occured or a downlink was received, stop retransmission // if an error occured or a downlink was received, stop retransmission
@ -927,7 +926,7 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent)
if(this->tUplink > tNow) { if(this->tUplink > tNow) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow)); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
if(this->tUplink > mod->hal->millis()) { if(this->tUplink > mod->hal->millis()) {
mod->hal->delay(this->tUplink - mod->hal->millis()); this->sleepDelay(this->tUplink - mod->hal->millis());
} }
} }
@ -936,7 +935,7 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent)
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// sleep for the duration of the transmission // sleep for the duration of the transmission
mod->hal->delay(toa); this->sleepDelay(toa);
RadioLibTime_t txEnd = mod->hal->millis(); RadioLibTime_t txEnd = mod->hal->millis();
// wait for an additional transmission duration as Tx timeout period // wait for an additional transmission duration as Tx timeout period
@ -1342,7 +1341,7 @@ int16_t LoRaWANNode::transmitUplink(const LoRaWANChannel_t* chnl, uint8_t* in, u
if(this->tUplink > tNow) { if(this->tUplink > tNow) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow)); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
if(this->tUplink > mod->hal->millis()) { if(this->tUplink > mod->hal->millis()) {
mod->hal->delay(this->tUplink - mod->hal->millis()); this->sleepDelay(this->tUplink - mod->hal->millis());
} }
} }
@ -1351,7 +1350,7 @@ int16_t LoRaWANNode::transmitUplink(const LoRaWANChannel_t* chnl, uint8_t* in, u
RADIOLIB_ASSERT(state); RADIOLIB_ASSERT(state);
// sleep for the duration of the transmission // sleep for the duration of the transmission
mod->hal->delay(toa); this->sleepDelay(toa);
RadioLibTime_t txEnd = mod->hal->millis(); RadioLibTime_t txEnd = mod->hal->millis();
// wait for an additional transmission duration as Tx timeout period // wait for an additional transmission duration as Tx timeout period
@ -1398,7 +1397,7 @@ int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChanne
// if function was called while Rx windows are in progress, // if function was called while Rx windows are in progress,
// wait until last window closes to prevent very bad stuff // wait until last window closes to prevent very bad stuff
if(now < tReference + dlDelays[numWindows]) { if(now < tReference + dlDelays[numWindows]) {
mod->hal->delay(dlDelays[numWindows] + tReference - now); this->sleepDelay(dlDelays[numWindows] + tReference - now);
} }
// update the end timestamp in case user got stuck between uplink and downlink // update the end timestamp in case user got stuck between uplink and downlink
this->rxDelayEnd = mod->hal->millis(); this->rxDelayEnd = mod->hal->millis();
@ -1444,7 +1443,7 @@ int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChanne
if(waitLen > this->scanGuard) { if(waitLen > this->scanGuard) {
waitLen -= this->scanGuard; waitLen -= this->scanGuard;
} }
mod->hal->delay(waitLen); this->sleepDelay(waitLen);
// open Rx window by starting receive with specified timeout // open Rx window by starting receive with specified timeout
state = this->phyLayer->launchMode(); state = this->phyLayer->launchMode();
@ -1453,7 +1452,7 @@ int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChanne
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", window, (int)(timeoutHost / 1000 + 2)); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", window, (int)(timeoutHost / 1000 + 2));
// wait for the timeout to complete (and a small additional delay) // wait for the timeout to complete (and a small additional delay)
mod->hal->delay(timeoutHost / 1000 + 2); this->sleepDelay(timeoutHost / 1000 + this->scanGuard / 2);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", window); RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", window);
// if the IRQ bit for Rx Timeout is not set, something is received, so stop the windows // if the IRQ bit for Rx Timeout is not set, something is received, so stop the windows
@ -3376,6 +3375,10 @@ uint8_t LoRaWANNode::getMaxPayloadLen() {
return(curLen - 13 - this->fOptsUpLen); return(curLen - 13 - this->fOptsUpLen);
} }
void LoRaWANNode::setSleepFunction(SleepCb_t cb) {
this->sleepCb = cb;
}
int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) {
int16_t state = this->phyLayer->standby(); int16_t state = this->phyLayer->standby();
if(state != RADIOLIB_ERR_NONE) { if(state != RADIOLIB_ERR_NONE) {
@ -3500,6 +3503,18 @@ void LoRaWANNode::processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_
} }
} }
void LoRaWANNode::sleepDelay(RadioLibTime_t ms) {
// if the user did not provide sleep callback, or the duration is short, just call delay
if((this->sleepCb == nullptr) || (ms <= RADIOLIB_LORAWAN_DELAY_SLEEP_THRESHOLD)) {
Module* mod = this->phyLayer->getMod();
mod->hal->delay(ms);
return;
}
// otherwise, call the user-provided callback
this->sleepCb(ms);
}
int16_t LoRaWANNode::checkBufferCommon(const uint8_t *buffer, uint16_t size) { int16_t LoRaWANNode::checkBufferCommon(const uint8_t *buffer, uint16_t size) {
// check if there are actually values in the buffer // check if there are actually values in the buffer
size_t i = 0; size_t i = 0;

View file

@ -210,6 +210,9 @@
#define RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE (250) #define RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE (250)
// threshold at which sleeping via user callback enabled, in ms
#define RADIOLIB_LORAWAN_DELAY_SLEEP_THRESHOLD (50)
/*! /*!
\struct LoRaWANMacCommand_t \struct LoRaWANMacCommand_t
\brief MAC command specification structure. \brief MAC command specification structure.
@ -832,6 +835,18 @@ class LoRaWANNode {
*/ */
uint8_t getMaxPayloadLen(); uint8_t getMaxPayloadLen();
/*! \brief Callback to a user-provided sleep function. */
typedef void (*SleepCb_t)(RadioLibTime_t ms);
/*!
\brief Set custom delay/sleep function callback. If set, LoRaWAN node will call
this function to wait for periods of time longer than RADIOLIB_LORAWAN_DELAY_SLEEP_THRESHOLD.
This can be used to lower the power consumption by putting the host microcontroller to sleep.
NOTE: Since this method will call a user-provided function, it is up to the user to ensure
that the time duration spent in that sleep function is accurate to at least 1 ms!
*/
void setSleepFunction(SleepCb_t cb);
/*! /*!
\brief TS009 Protocol Specification Verification switch \brief TS009 Protocol Specification Verification switch
(allows FPort 224 and cuts off uplink payload instead of rejecting if maximum length exceeded). (allows FPort 224 and cuts off uplink payload instead of rejecting if maximum length exceeded).
@ -973,6 +988,8 @@ class LoRaWANNode {
// allow port 226 for devices implementing TS011 // allow port 226 for devices implementing TS011
bool TS011 = false; bool TS011 = false;
SleepCb_t sleepCb = nullptr;
// this will reset the device credentials, so the device starts completely new // this will reset the device credentials, so the device starts completely new
void clearNonces(); void clearNonces();
@ -1102,6 +1119,9 @@ class LoRaWANNode {
// function to encrypt and decrypt payloads (regular uplink/downlink) // function to encrypt and decrypt payloads (regular uplink/downlink)
void processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter); void processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter);
// function that allows sleeping via user-provided callback
void sleepDelay(RadioLibTime_t ms);
// 16-bit checksum method that takes a uint8_t array of even length and calculates the checksum // 16-bit checksum method that takes a uint8_t array of even length and calculates the checksum
static uint16_t checkSum16(const uint8_t *key, uint16_t keyLen); static uint16_t checkSum16(const uint8_t *key, uint16_t keyLen);