[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
uint8_t joinDR = 4;
// Optionally provide a custom sleep function - see config.h
//node.setSleepFunction(customDelay);
// Setup the OTAA session information
node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);

View file

@ -142,4 +142,19 @@ void arrayDump(uint8_t *buffer, uint16_t len) {
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

View file

@ -379,6 +379,7 @@ getLastToA KEYWORD2
dutyCycleInterval KEYWORD2
timeUntilUplink KEYWORD2
getMaxPayloadLen KEYWORD2
setSleepFunction KEYWORD2
#######################################
# 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);
}
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(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)
// must be present after any confirmed frame, so we force this here
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
@ -927,7 +926,7 @@ int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent)
if(this->tUplink > tNow) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
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);
// sleep for the duration of the transmission
mod->hal->delay(toa);
this->sleepDelay(toa);
RadioLibTime_t txEnd = mod->hal->millis();
// 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) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
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);
// sleep for the duration of the transmission
mod->hal->delay(toa);
this->sleepDelay(toa);
RadioLibTime_t txEnd = mod->hal->millis();
// 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,
// wait until last window closes to prevent very bad stuff
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
this->rxDelayEnd = mod->hal->millis();
@ -1444,7 +1443,7 @@ int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChanne
if(waitLen > this->scanGuard) {
waitLen -= this->scanGuard;
}
mod->hal->delay(waitLen);
this->sleepDelay(waitLen);
// open Rx window by starting receive with specified timeout
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));
// 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);
// 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);
}
void LoRaWANNode::setSleepFunction(SleepCb_t cb) {
this->sleepCb = cb;
}
int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) {
int16_t state = this->phyLayer->standby();
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) {
// check if there are actually values in the buffer
size_t i = 0;

View file

@ -210,6 +210,9 @@
#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
\brief MAC command specification structure.
@ -832,6 +835,18 @@ class LoRaWANNode {
*/
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
(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
bool TS011 = false;
SleepCb_t sleepCb = nullptr;
// this will reset the device credentials, so the device starts completely new
void clearNonces();
@ -1102,6 +1119,9 @@ class LoRaWANNode {
// 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);
// 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
static uint16_t checkSum16(const uint8_t *key, uint16_t keyLen);