From e71d584fca96d2a952619a1f33f1ab41b0a0095d Mon Sep 17 00:00:00 2001
From: Manuel Bl <10954524+manuelbl@users.noreply.github.com>
Date: Tue, 28 Sep 2021 17:35:05 +0200
Subject: [PATCH] Save and restore for deep sleep

---
 examples/deep_sleep_in_c/CMakeLists.txt      |  10 ++
 examples/deep_sleep_in_c/main/CMakeLists.txt |   4 +
 examples/deep_sleep_in_c/main/main.c         | 117 +++++++++++++++++++
 src/hal/hal_esp32.c                          |  36 ++++--
 src/ttn.c                                    |  44 +++++--
 src/ttn_rtc.c                                |  58 +++++++++
 src/ttn_rtc.h                                |  30 +++++
 7 files changed, 278 insertions(+), 21 deletions(-)
 create mode 100644 examples/deep_sleep_in_c/CMakeLists.txt
 create mode 100644 examples/deep_sleep_in_c/main/CMakeLists.txt
 create mode 100644 examples/deep_sleep_in_c/main/main.c
 create mode 100644 src/ttn_rtc.c
 create mode 100644 src/ttn_rtc.h

diff --git a/examples/deep_sleep_in_c/CMakeLists.txt b/examples/deep_sleep_in_c/CMakeLists.txt
new file mode 100644
index 0000000..7c07596
--- /dev/null
+++ b/examples/deep_sleep_in_c/CMakeLists.txt
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.5)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+
+# Update the below line to match the path to the ttn-esp32 library,
+# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
+list(APPEND EXTRA_COMPONENT_DIRS "../..")
+
+#add_definitions(-DLMIC_ENABLE_event_logging=1)
+
+project(deep_sleep)
diff --git a/examples/deep_sleep_in_c/main/CMakeLists.txt b/examples/deep_sleep_in_c/main/CMakeLists.txt
new file mode 100644
index 0000000..d0ee70d
--- /dev/null
+++ b/examples/deep_sleep_in_c/main/CMakeLists.txt
@@ -0,0 +1,4 @@
+idf_component_register(
+    SRCS "main.c"
+    INCLUDE_DIRS "."
+    REQUIRES ttn-esp32)
diff --git a/examples/deep_sleep_in_c/main/main.c b/examples/deep_sleep_in_c/main/main.c
new file mode 100644
index 0000000..43e9949
--- /dev/null
+++ b/examples/deep_sleep_in_c/main/main.c
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * 
+ * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
+ * 
+ * Copyright (c) 2021 Manuel Bleichenbacher
+ * 
+ * Licensed under MIT License
+ * https://opensource.org/licenses/MIT
+ *
+ * Sample program (in C) sending messages and going to deep sleep in-between.
+ *******************************************************************************/
+
+#include "freertos/FreeRTOS.h"
+#include "esp_event.h"
+#include "driver/gpio.h"
+#include "nvs_flash.h"
+#include "esp_log.h"
+#include "esp_sleep.h"
+
+#include "ttn.h"
+
+// NOTE:
+// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
+// Go to Components / The Things Network, select the appropriate values and save.
+
+// Copy the below hex strings from the TTN console (Applications > Your application > End devices
+// > Your device > Activation information)
+
+// AppEUI (sometimes called JoinEUI)
+const char *appEui = "????????????????";
+// DevEUI
+const char *devEui = "????????????????";
+// AppKey
+const char *appKey = "????????????????????????????????";
+
+// Pins and other resources
+#define TTN_SPI_HOST      HSPI_HOST
+#define TTN_SPI_DMA_CHAN  1
+#define TTN_PIN_SPI_SCLK  5
+#define TTN_PIN_SPI_MOSI  27
+#define TTN_PIN_SPI_MISO  19
+#define TTN_PIN_NSS       18
+#define TTN_PIN_RXTX      TTN_NOT_CONNECTED
+#define TTN_PIN_RST       14
+#define TTN_PIN_DIO0      26
+#define TTN_PIN_DIO1      35
+
+#define TX_INTERVAL 60 // in sec
+static uint8_t msgData[] = "Hello, world";
+
+
+void app_main(void)
+{
+    esp_err_t err;
+    // Initialize the GPIO ISR handler service
+    err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
+    ESP_ERROR_CHECK(err);
+    
+    // Initialize the NVS (non-volatile storage) for saving and restoring the keys
+    err = nvs_flash_init();
+    ESP_ERROR_CHECK(err);
+
+    // Initialize SPI bus
+    spi_bus_config_t spi_bus_config = {
+        .miso_io_num = TTN_PIN_SPI_MISO,
+        .mosi_io_num = TTN_PIN_SPI_MOSI,
+        .sclk_io_num = TTN_PIN_SPI_SCLK,
+        .quadwp_io_num = -1,
+        .quadhd_io_num = -1
+    }; 
+    err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
+    ESP_ERROR_CHECK(err);
+
+    // Initialize TTN
+    ttn_init();
+
+    // Configure the SX127x pins
+    ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
+
+    // The below line can be commented after the first run as the data is saved in NVS
+    ttn_provision(devEui, appEui, appKey);
+
+    // ttn_set_adr_enabled(false);
+    // ttn_set_data_rate(TTN_DR_US915_SF7);
+    // ttn_set_max_tx_pow(14);
+
+    if (ttn_resume_after_deep_sleep()) {
+        printf("Resumed from deep sleep.\n");
+    
+    } else {
+
+        printf("Joining...\n");
+        if (ttn_join())
+        {
+            printf("Joined.\n");
+        }
+        else
+        {
+            printf("Join failed. Goodbye\n");
+            return;
+        }
+    }
+
+    printf("Sending message...\n");
+    ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
+    printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
+
+    // Wait until TTN communication is idle and save state
+    ttn_wait_for_idle();
+    ttn_prepare_for_deep_sleep();
+
+    // Schedule wake up
+    esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
+
+    printf("Going to deep sleep...\n");
+    esp_deep_sleep_start();
+}
diff --git a/src/hal/hal_esp32.c b/src/hal/hal_esp32.c
index be4ae3f..7972ea1 100755
--- a/src/hal/hal_esp32.c
+++ b/src/hal/hal_esp32.c
@@ -22,6 +22,8 @@
 #include "driver/timer.h"
 #include "esp_timer.h"
 #include "esp_log.h"
+#include <time.h>
+#include <sys/time.h>
 
 #define LMIC_UNUSED_PIN 0xff
 
@@ -46,7 +48,7 @@ static void lmic_background_task(void* pvParameter);
 static void qio_irq_handler(void* arg);
 static void timer_callback(void *arg);
 static int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time);
-
+static int64_t get_current_time();
 static void init_io(void);
 static void init_spi(void);
 static void init_timer(void);
@@ -64,7 +66,6 @@ static gpio_num_t pin_dio0;
 static gpio_num_t pin_dio1;
 static int8_t rssi_cal = 10;
 
-
 static TaskHandle_t lmic_task;
 static uint32_t dio_interrupt_time;
 static uint8_t dio_num;
@@ -73,6 +74,7 @@ static spi_device_handle_t spi_handle;
 static spi_transaction_t spi_transaction;
 static SemaphoreHandle_t mutex;
 static esp_timer_handle_t timer;
+static int64_t time_offset;
 static int64_t next_alarm;
 static volatile bool run_background_task;
 static volatile wait_kind_e current_wait_kind;
@@ -249,11 +251,14 @@ void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
 
 /*
  * LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this
- * implementation each tick is 16µs. It will wrap arounnd every 19 hours.
+ * implementation, each tick is 16µs. It will wrap arounnd every 19 hours.
  * 
  * The ESP32 has a 64 bit timer counting microseconds. It will wrap around
  * every 584,000 years. So we don't need to bother.
  * 
+ * The time includes an offset initialized from `gettimeofday()`
+ * to ensure the time correctly advances during deep sleep.
+ * 
  * Based on this timer, future callbacks can be scheduled. This is used to
  * schedule the next LMIC job.
  */
@@ -276,15 +281,18 @@ int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time)
     }
     else
     {
-        // one's complement instead of two's complement:
-        // off by 1 µs and ignored
-        os_diff = ~os_diff;
+        os_diff = -os_diff;
         esp_time = esp_now - (((int64_t)os_diff) << 4);
     }
 
     return esp_time;
 }
 
+int64_t get_current_time()
+{
+    return esp_timer_get_time() + time_offset;
+}
+
 void init_timer(void)
 {
     esp_timer_create_args_t timer_config = {
@@ -296,6 +304,10 @@ void init_timer(void)
     esp_err_t err = esp_timer_create(&timer_config, &timer);
     ESP_ERROR_CHECK(err);
 
+    struct timeval now;
+    gettimeofday(&now, NULL);
+    time_offset = (int64_t)now.tv_sec * 1000000;
+
     ESP_LOGI(TAG, "Timer initialized");
 }
 
@@ -308,7 +320,7 @@ void arm_timer(int64_t esp_now)
 {
     if (next_alarm == 0)
         return;
-    int64_t timeout = next_alarm - esp_timer_get_time();
+    int64_t timeout = next_alarm - get_current_time();
     if (timeout < 0)
         timeout = 10;
     esp_timer_start_once(timer, timeout);
@@ -379,7 +391,7 @@ TickType_t hal_esp32_get_timer_duration(void)
         return 1; // busy, not waiting
 
     if (alarm_time != 0)
-        return pdMS_TO_TICKS((alarm_time - esp_timer_get_time() + 999) / 1000);
+        return pdMS_TO_TICKS((alarm_time - get_current_time() + 999) / 1000);
 
 
     return 0; // waiting indefinitely
@@ -391,7 +403,7 @@ u4_t IRAM_ATTR hal_ticks(void)
 {
     // LMIC tick unit: 16µs
     // esp_timer unit: 1µs
-    return (u4_t)(esp_timer_get_time() >> 4);
+    return (u4_t)(get_current_time() >> 4);
 }
 
 // Wait until the specified time.
@@ -399,7 +411,7 @@ u4_t IRAM_ATTR hal_ticks(void)
 // All other events are ignored and will be served later.
 u4_t hal_waitUntil(u4_t time)
 {
-    int64_t esp_now = esp_timer_get_time();
+    int64_t esp_now = get_current_time();
     int64_t esp_time = os_time_to_esp_time(esp_now, time);
     set_next_alarm(esp_time);
     arm_timer(esp_now);
@@ -423,7 +435,7 @@ void hal_esp32_wake_up(void)
 // in the queue. If the job is not due yet, LMIC will go to sleep.
 u1_t hal_checkTimer(uint32_t time)
 {
-    int64_t esp_now = esp_timer_get_time();
+    int64_t esp_now = get_current_time();
     int64_t esp_time = os_time_to_esp_time(esp_now, time);
     int64_t diff = esp_time - esp_now;
     if (diff < 100)
@@ -440,7 +452,7 @@ void hal_sleep(void)
     if (wait(WAIT_KIND_CHECK_IO))
         return;
 
-    arm_timer(esp_timer_get_time());
+    arm_timer(get_current_time());
     wait(WAIT_KIND_WAIT_FOR_ANY_EVENT);
 }
 
diff --git a/src/ttn.c b/src/ttn.c
index 2435871..d6a7e4d 100644
--- a/src/ttn.c
+++ b/src/ttn.c
@@ -18,6 +18,7 @@
 #include "lmic/lmic.h"
 #include "ttn_logging.h"
 #include "ttn_provisioning.h"
+#include "ttn_rtc.h"
 
 #define TAG "ttn"
 
@@ -61,7 +62,7 @@ static bool is_started;
 static bool has_joined;
 static QueueHandle_t lmic_event_queue;
 static ttn_message_cb message_callback;
-static ttn_waiting_reason_t waiting_reason = TTN_WAITING_NONE;
+static ttn_waiting_reason_t waiting_reason;
 static ttn_rf_settings_t last_rf_settings[4];
 static ttn_rx_tx_window_t current_rx_tx_window;
 static int subband = 2;
@@ -212,6 +213,29 @@ bool ttn_join(void)
     return join_core();
 }
 
+bool ttn_resume_after_deep_sleep(void)
+{
+    if (!ttn_provisioning_have_keys())
+    {
+        if (!ttn_provisioning_restore_keys(false))
+            return false;
+    }
+
+    if (!ttn_provisioning_have_keys())
+    {
+        ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
+        return false;
+    }
+
+    start();
+
+    if (!ttn_rtc_restore())
+        return false;
+
+    has_joined = true;
+    return true;
+}
+
 // Called immediately before sending join request message
 void config_rf_params(void)
 {
@@ -310,6 +334,12 @@ bool ttn_is_provisioned(void)
     return ttn_provisioning_have_keys();
 }
 
+void ttn_prepare_for_deep_sleep(void)
+{
+    ttn_rtc_save();
+}
+
+
 void ttn_wait_for_idle(void)
 {
     while (true)
@@ -353,30 +383,26 @@ void ttn_set_adr_enabled(bool enabled)
 
 void ttn_set_data_rate(ttn_data_rate_t data_rate)
 {
+    join_data_rate = data_rate;
+
     if (has_joined)
     {
         hal_esp32_enter_critical_section();
         LMIC_setDrTxpow(data_rate, LMIC.adrTxPow);
         hal_esp32_leave_critical_section();
     }
-    else
-    {
-        join_data_rate = data_rate;
-    }
 }
 
 void ttn_set_max_tx_pow(int tx_pow)
 {
+    max_tx_power = tx_pow;
+
     if (has_joined)
     {
         hal_esp32_enter_critical_section();
         LMIC_setDrTxpow(LMIC.datarate, tx_pow);
         hal_esp32_leave_critical_section();
     }
-    else
-    {
-        max_tx_power = tx_pow;
-    }
 }
 
 ttn_rf_settings_t ttn_get_rf_settings(ttn_rx_tx_window_t window)
diff --git a/src/ttn_rtc.c b/src/ttn_rtc.c
new file mode 100644
index 0000000..78949ca
--- /dev/null
+++ b/src/ttn_rtc.c
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ *
+ * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
+ *
+ * Copyright (c) 2018-2021 Manuel Bleichenbacher
+ *
+ * Licensed under MIT License
+ * https://opensource.org/licenses/MIT
+ *
+ * Functions for storing and retrieving TTN communication state from RTC memory.
+ *******************************************************************************/
+
+#include "ttn_rtc.h"
+#include "esp_system.h"
+#include "lmic/lmic.h"
+#include <string.h>
+
+#define LMIC_OFFSET(field) __builtin_offsetof(struct lmic_t, field)
+#define LMIC_DIST(field1, field2) (LMIC_OFFSET(field2) - LMIC_OFFSET(field1))
+#define TTN_RTC_MEM_SIZE (sizeof(struct lmic_t) - LMIC_OFFSET(radio) - MAX_LEN_PAYLOAD - MAX_LEN_FRAME)
+
+#define TTN_RTC_FLAG_VALUE 0xf30b84ce
+
+RTC_DATA_ATTR uint8_t ttn_rtc_mem_buf[TTN_RTC_MEM_SIZE];
+RTC_DATA_ATTR uint32_t ttn_rtc_flag;
+
+void ttn_rtc_save()
+{
+    // Copy LMIC struct except client, osjob, pendTxData and frame
+    size_t len1 = LMIC_DIST(radio, pendTxData);
+    memcpy(ttn_rtc_mem_buf, &LMIC.radio, len1);
+    size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
+    memcpy(ttn_rtc_mem_buf + len1, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, len2);
+    size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
+    memcpy(ttn_rtc_mem_buf + len1 + len2, (u1_t *)&LMIC.frame + MAX_LEN_FRAME, len3);
+
+    ttn_rtc_flag = TTN_RTC_FLAG_VALUE;
+}
+
+bool ttn_rtc_restore()
+{
+    if (ttn_rtc_flag != TTN_RTC_FLAG_VALUE)
+        return false;
+
+    // Restore data
+    size_t len1 = LMIC_DIST(radio, pendTxData);
+    memcpy(&LMIC.radio, ttn_rtc_mem_buf, len1);
+    memset(LMIC.pendTxData, 0, MAX_LEN_PAYLOAD);
+    size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
+    memcpy((u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, ttn_rtc_mem_buf + len1, len2);
+    memset(LMIC.frame, 0, MAX_LEN_FRAME);
+    size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
+    memcpy((u1_t *)&LMIC.frame + MAX_LEN_FRAME, ttn_rtc_mem_buf + len1 + len2, len3);
+
+    ttn_rtc_flag = 0xffffffff; // invalidate RTC data
+
+    return true;
+}
diff --git a/src/ttn_rtc.h b/src/ttn_rtc.h
new file mode 100644
index 0000000..1609bd7
--- /dev/null
+++ b/src/ttn_rtc.h
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ *
+ * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
+ *
+ * Copyright (c) 2018-2021 Manuel Bleichenbacher
+ *
+ * Licensed under MIT License
+ * https://opensource.org/licenses/MIT
+ *
+ * Functions for storing and retrieving TTN communication state from RTC memory.
+ *******************************************************************************/
+
+#ifndef TTN_RTC_H
+#define TTN_RTC_H
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+    void ttn_rtc_save();
+    bool ttn_rtc_restore();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif