beegee-tokyo / SX126x-Arduino

Arduino library to use Semtech SX126x LoRa chips and modules to communicate
MIT License
236 stars 64 forks source link

Question/Feature Request: Procedure to save/restore LoRaWAN network authentication across ESP32 deep-sleep #85

Closed avillacis closed 1 year ago

avillacis commented 1 year ago

Hello. We are using your library successfully for several of our IoT projects that need communication in areas without WiFi coverage. However we have the following issue. At least one of our projects needs to run off a combination of solar panels and batteries in order to transmit 24/7 without mains power. So, obviously we make use of deep-sleep. However, every time the device (an ESP32-S2) comes out of deep sleep, it needs to negotiate the join to the LoRaWAN network all over again, which takes time it could be using instead sending its sample backlog.

Is there a way to extract some summary or current state of the LoRaWAN authentication state (assuming the device joined the network successfully), so it can be saved to flash or RTC memory, and be restored on wakeup? This would greatly accelerate data transmission.

beegee-tokyo commented 1 year ago

I am not sure how this could be implemented, the internal parts of the LoRaMAC stack are taken from the Semtech code and I am not 100% familiar with it.

An option I see would be to use ABP instead of OTAA. With ABP you don't need to start the Join request/accept cycle every time. You just set the DevAddr, AppsKey and NWSKey and call join(). It will immediately return, as ABP doesn't require the join sequence.

Of course you have to setup the device in the LoRaWAN server as ABP device as well and use the same DevAddr, AppsKey and NWSKey.

illperipherals commented 1 year ago

avillacis, you are on the correct path here! Unfortunately, most current OTAA devices are not implemented correctly to spec... (rundown here https://lora-developers.semtech.com/documentation/tech-papers-and-guides/lorawan-device-activation/device-activation/ ). Ideally, OTAA devices would only have to do a join proper once per lifetime... Sadly, this doesn't seem to be the case pretty much anywhere, and full joins are used on each power cycle. (More info here https://lora-developers.semtech.com/documentation/tech-papers-and-guides/the-book/joining-and-rejoining/ )

avillacis commented 1 year ago

So, if I understand correctly, for the OTAA case, the application should join the network successfully once, then retrieve the negotiated AppsKey and NWSKey somehow (is there an API for that?), and then, when waking up, retrieve and use the AppsKey and NWSKey in the ABP network join codepath, and it should work, right? Or am I forgetting something?

There should be an example program demonstrating the sequence, if the above strategy is possible and sound.

beegee-tokyo commented 1 year ago

My proposal was to use ABP only, not start with OTAA and after wake-up use ABP.

I never tried it that way, but it could be working.

avillacis commented 1 year ago

SUCCESS!

I could successfully recover enough state from the OTAA negotiation using existing APIs, to persist the session across a full device reset and keep sending packets using the exact same keys. The procedure done is as follows:

  1. On first boot, negotiate OTAA using known devID, appEUI, appKey values as normal.
  2. Once the OTAA network join is successful, the following values should be recovered and persisted in non-volatile storage: MIB_DEV_ADDR, MIB_NWK_SKEY, MIB_APP_SKEY. All of these are retrieved through the LoRaMacMibGetRequestConfirm() function.
  3. On every application packet send and receive, the uplink and downlink frame counters should be recovered and persisted. These are done using MIB_UPLINK_COUNTER and MIB_DOWNLINK_COUNTER with the same function as before.
  4. On subsequent reboots, the device should check if network and application session keys are present, as well as the device address assigned by the network. If they are present, they should be assigned with lmh_setNwkSKey, lmh_setAppSKey, lmh_setDevAddr and then lmh_init should be called in ABP mode, with OTAA flag set to FALSE.
  5. On join success in ABP mode, the previously saved frame counters should be restored using the LoRaMacMibSetRequestConfirm() function and the MIB_UPLINK_COUNTER and MIB_DOWNLINK_COUNTER specifiers. This keeps the network from rejecting the packets with a frame counter reset error.
  6. On appKey change, or if somehow the device concludes it has lost synchronization with the network, go to step 1.
beegee-tokyo commented 1 year ago

Glad you found a solution. Thank you for sharing it here. I mark this with a best practice label for others.

Vednus commented 1 year ago

@avillacis this is just what I've been searching for. Would you mind sharing your example?

kent-williams commented 1 year ago

@avillacis thanks for sharing this!

Just to be clear, you do not have to ever tell the LNS that you are conducting ABP effectively, correct? You're just using that mode device side because you have the session keys stored?

avillacis commented 1 year ago

@avillacis this is just what I've been searching for. Would you mind sharing your example?

Sorry for answering so late, but I was (and still am) very busy with my day job. The procedure I shared previously is a distillation of an algorithm I implemented in a framework I am responsible for. The procedure is not (yet) demonstrated in a standalone example, but I implemented it in this commit and was refined in the following commits for the yubox-LoRaWAN addon library for the YUBOX-Now framework (NOTE: assumes ESP32 or ESP32-S2 environment).

@avillacis thanks for sharing this!

Just to be clear, you do not have to ever tell the LNS that you are conducting ABP effectively, correct? You're just using that mode device side because you have the session keys stored?

That is right. The Chirpstack instance has the device registered as requiring OTAA, but it does not know nor care that the device was rebooted between transmissions, as long as packets keep being sent with the proper negotiated keys. As mentioned when opening this issue, the purpose was to skip renegotiation via OTAA after waking from ESP32 deep sleep, which is almost equivalent to a reboot.