RadioLibSmol/examples/LoRaWAN/LoRaWAN_ESP32/LoRaWAN_ESP32.ino
2024-03-23 17:00:13 +00:00

196 lines
7.7 KiB
C++

/*
This demonstrates how to save the join information in to permanent memory
so that if the power fails, batteries run out or are changed, the rejoin
is more efficient & happens sooner due to the way that LoRaWAN secures
the join process - see the wiki for more details.
This is typically useful for devices that need more power than a battery
driven sensor - something like a air quality monitor or GPS based device that
is likely to use up it's power source resulting in loss of the session.
The relevant code is flagged with a ##### comment
Saving the entire session is possible but not demonstrated here - it has
implications for flash wearing and complications with which parts of the
session may have changed after an uplink. So it is assumed that the device
is going in to deep-sleep, as below, between normal uplinks.
*/
#if !defined(ESP32)
#pragma error ("This is not the example your device is looking for - ESP32 only")
#endif
// ##### Load the ESP32 preferences facilites
#include <Preferences.h>
Preferences store;
// LoRaWAN config, credentials & pinmap
#include "config.h"
#include <RadioLib.h>
// Utilities & vars to support ESP32 deep-sleep. The RTC_DATA_ATTR attribute
// puts these in to the RTC memory which is preserved during deep-sleep
RTC_DATA_ATTR uint16_t bootCount = 1;
RTC_DATA_ATTR uint16_t bootCountSinceUnsuccessfulJoin = 0;
RTC_DATA_ATTR uint8_t LWsession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE];
// Abbreviated version from the Arduino-ESP32 package, see
// https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/deepsleep.html
// for the complete set of options
void print_wakeup_reason() {
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println(F("Wake from sleep"));
} else {
Serial.print(F("Wake not caused by deep sleep: "));
Serial.println(wakeup_reason);
}
Serial.print(F("Boot count: "));
Serial.println(bootCount++);
}
// Put device in to lowest power deep-sleep mode
void gotoSleep(uint32_t seconds) {
esp_sleep_enable_timer_wakeup(seconds * 1000UL * 1000UL); // Function uses uS
Serial.println(F("Sleeping\n"));
Serial.flush();
esp_deep_sleep_start();
// If this appears in the serial debug, we didn't go to sleep!
// So take defensive action so we don't continually uplink
Serial.println(F("\n\n### Sleep failed, delay of 5 minutes & then restart ###\n"));
delay(5UL * 60UL * 1000UL);
ESP.restart();
}
// Setup & execute all device functions ...
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial to be initalised
delay(2000); // Give time to switch to the serial monitor
Serial.println(F("\nSetup"));
print_wakeup_reason();
int16_t state = 0; // return value for calls to RadioLib
// Setup the radio based on the pinmap (connections) in config.h
Serial.println(F("Initalise the radio"));
state = radio.begin();
debug(state != RADIOLIB_ERR_NONE, F("Initalise radio failed"), state, true);
Serial.println(F("Recalling LoRaWAN nonces & session"));
// ##### Setup the flash storage
store.begin("radiolib");
// ##### If we have previously saved nonces, restore them
if (store.isKey("nonces")) {
uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE];// Create somewhere to store nonces
store.getBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);// Get them to the store
state = node.setBufferNonces(buffer); // Send them to LoRaWAN
debug(state != RADIOLIB_ERR_NONE, F("Restoring nonces buffer failed"), state, false);
}
// Recall session from RTC deep-sleep preserved variable
state = node.setBufferSession(LWsession); // Send them to LoRaWAN stack
// If we have booted at least once we should have a session to restore, so report any failure
// Otherwise no point saying there's been a failure when it was bound to fail with an empty
// LWsession var. At this point, bootCount has already been incremented, hence the > 2
debug((state != RADIOLIB_ERR_NONE) && (bootCount > 2), F("Restoring session buffer failed"), state, false);
// Process the restored session or failing that, create a new one &
// return flag to indicate a fresh join is required
Serial.println(F("Setup LoRaWAN session"));
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, false);
// See comment above, no need to report a failure that is bound to occur on first boot
debug((state != RADIOLIB_ERR_NONE) && (bootCount > 2), F("Restore session failed"), state, false);
// Loop until successful join
while (state != RADIOLIB_ERR_NONE) {
Serial.println(F("Join ('login') to the LoRaWAN Network"));
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, true);
if (state < RADIOLIB_ERR_NONE) {
Serial.print(F("Join failed: "));
Serial.println(state);
// How long to wait before join attenpts. This is an interim solution pending
// implementation of TS001 LoRaWAN Specification section #7 - this doc applies to v1.0.4 & v1.1
// It sleeps for longer & longer durations to give time for any gateway issues to resolve
// or whatever is interfering with the device <-> gateway airwaves.
uint32_t sleepForSeconds = min((bootCountSinceUnsuccessfulJoin++ + 1UL) * 60UL, 3UL * 60UL);
Serial.print(F("Boots since unsuccessful join: "));
Serial.println(bootCountSinceUnsuccessfulJoin);
Serial.print(F("Retrying join in "));
Serial.print(sleepForSeconds);
Serial.println(F(" seconds"));
gotoSleep(sleepForSeconds);
} else { // Join was successful
Serial.println(F("Joined"));
// ##### Save the join counters (nonces) to permanent store
Serial.println(F("Saving nonces to flash"));
uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE]; // Create somewhere to store nonces
uint8_t *persist = node.getBufferNonces(); // Get pointer to nonces
memcpy(buffer, persist, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // Copy in to buffer
store.putBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // Send them to the store
// We'll save the session after the uplink
// Reset the failed join count
bootCountSinceUnsuccessfulJoin = 0;
delay(1000); // Hold off off hitting the airwaves again too soon - an issue in the US
} // if beginOTAA state
} // while join
// ##### Close the store
store.end();
// ----- And now for the main event -----
Serial.println(F("Sending uplink"));
// Read some inputs
uint8_t Digital2 = digitalRead(2);
uint16_t Analog1 = analogRead(A1);
// Build payload byte array
uint8_t uplinkPayload[3];
uplinkPayload[0] = Digital2;
uplinkPayload[1] = highByte(Analog1); // See notes for high/lowByte functions
uplinkPayload[2] = lowByte(Analog1);
// Perform an uplink
state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload));
debug((state != RADIOLIB_ERR_RX_TIMEOUT) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false);
Serial.print(F("FcntUp: "));
Serial.println(node.getFcntUp());
// Now save session to RTC memory
uint8_t *persist = node.getBufferSession();
memcpy(LWsession, persist, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);
// Wait until next uplink - observing legal & TTN FUP constraints
gotoSleep(uplinkIntervalSeconds);
}
// The ESP32 wakes from deep-sleep and starts from the very beginning
// which is a very good place to start, as any singing nun knows.
// It then goes back to sleep, so loop() is never called and which is
// why it is empty.
void loop() {}