From f433e826a76c4cc82d1b43ae14851b9c99c9b37e Mon Sep 17 00:00:00 2001 From: Manuel Bl <10954524+manuelbl@users.noreply.github.com> Date: Wed, 29 Sep 2021 16:48:59 +0200 Subject: [PATCH] Save and restore from power off --- examples/power_off_in_c/CMakeLists.txt | 10 ++ examples/power_off_in_c/main/CMakeLists.txt | 4 + examples/power_off_in_c/main/main.c | 128 ++++++++++++++++++++ include/ttn.h | 42 +++++++ src/hal/hal_esp32.c | 17 ++- src/hal/hal_esp32.h | 20 +++ src/ttn.c | 29 +++++ src/ttn_nvs.c | 123 +++++++++++++++++++ src/ttn_nvs.h | 30 +++++ 9 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 examples/power_off_in_c/CMakeLists.txt create mode 100644 examples/power_off_in_c/main/CMakeLists.txt create mode 100644 examples/power_off_in_c/main/main.c create mode 100644 src/ttn_nvs.c create mode 100644 src/ttn_nvs.h diff --git a/examples/power_off_in_c/CMakeLists.txt b/examples/power_off_in_c/CMakeLists.txt new file mode 100644 index 0000000..802785b --- /dev/null +++ b/examples/power_off_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(power_off) diff --git a/examples/power_off_in_c/main/CMakeLists.txt b/examples/power_off_in_c/main/CMakeLists.txt new file mode 100644 index 0000000..d0ee70d --- /dev/null +++ b/examples/power_off_in_c/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "main.c" + INCLUDE_DIRS "." + REQUIRES ttn-esp32) diff --git a/examples/power_off_in_c/main/main.c b/examples/power_off_in_c/main/main.c new file mode 100644 index 0000000..e9de5f1 --- /dev/null +++ b/examples/power_off_in_c/main/main.c @@ -0,0 +1,128 @@ +/******************************************************************************* + * + * 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 powering off 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 messageReceived(const uint8_t* message, size_t length, ttn_port_t port) +{ + printf("Message of %d bytes received on port %d:", length, port); + for (int i = 0; i < length; i++) + printf(" %02x", message[i]); + printf("\n"); +} + +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); + + // Register callback for received messages + ttn_on_message(messageReceived); + + // ttn_set_adr_enabled(false); + // ttn_set_data_rate(TTN_DR_US915_SF7); + // ttn_set_max_tx_pow(14); + + if (ttn_resume_after_power_off(60)) { + printf("Resumed from power off.\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_power_off(); + + printf("Power off...\n"); + // Do whatever is needed to power off the device. + // For testing, press reset button to simulate power cycle. + while (true) + vTaskDelay(pdMS_TO_TICKS(1000)); +} diff --git a/include/ttn.h b/include/ttn.h index 9ddc771..a41b491 100644 --- a/include/ttn.h +++ b/include/ttn.h @@ -587,6 +587,29 @@ extern "C" */ bool ttn_resume_after_deep_sleep(void); + /** + * @brief Resumes TTN communication after power off. + * + * The communcation state is restored from data previously saved in NVS (non-volatile storage). + * The RF module and the TTN background task are started. + * + * This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join() + * to continue with the established communication and to avoid a further join procedure. + * + * In order to advance the clock, the estimated duration the device was powered off has to + * be specified. As the exact duration is probably not known, an estimation of the shortest + * duration between power off and on can be used instead. + * + * If the device has access to the real time, set the system time (using `settimeofday()`) + * before calling this function (and before @ref join()) and pass 0 for `off_duration`. + * + * Before this function is called, `nvs_flash_init()` must have been called once. + * + * @param off_duration duration the device was powered off (in minutes) + * @return `true` if the device was able to resume, `false` otherwise. + */ + bool ttn_resume_after_power_off(int off_duration); + /** * @brief Stops all activies and prepares for deep sleep. * @@ -604,6 +627,25 @@ extern "C" */ void ttn_prepare_for_deep_sleep(void); + /** + * @brief Stops all activies and prepares for power off. + * + * This function is called before powering off the device. It saves the current + * communication state in NVS (non-volatile storage) and shuts down the RF module + * and the TTN background task. + * + * It neither clears the provisioned keys nor the configured pins + * but they will be lost if the device is powered off. + * + * Before calling this function, use @ref ttn_busy_duration() to check + * that the TTN device is idle and ready to be powered off. + * + * To restart communication, @ref ttn_resume_after_power_off(int) must be called. + * + * Before this function is called, `nvs_flash_init()` must have been called once. + */ + void ttn_prepare_for_power_off(void); + /** * @brief Waits until the TTN device is idle. * diff --git a/src/hal/hal_esp32.c b/src/hal/hal_esp32.c index bc4d362..f519098 100755 --- a/src/hal/hal_esp32.c +++ b/src/hal/hal_esp32.c @@ -75,6 +75,7 @@ static spi_transaction_t spi_transaction; static SemaphoreHandle_t mutex; static esp_timer_handle_t timer; static int64_t time_offset; +static int32_t initial_time_offset; static int64_t next_alarm; static volatile bool run_background_task; static volatile wait_kind_e current_wait_kind; @@ -306,11 +307,25 @@ void init_timer(void) struct timeval now; gettimeofday(&now, NULL); - time_offset = (int64_t)now.tv_sec * 1000000; + time_offset += (int64_t)now.tv_sec * 1000000; + initial_time_offset = 0; ESP_LOGI(TAG, "Timer initialized"); } +uint32_t hal_esp32_get_time(void) +{ + struct timeval now; + gettimeofday(&now, NULL); + return now.tv_sec + initial_time_offset; +} + +void hal_esp32_set_time(uint32_t time_val) +{ + initial_time_offset = time_val; + time_offset = (int64_t)time_val * 1000000; +} + void set_next_alarm(int64_t time) { next_alarm = time; diff --git a/src/hal/hal_esp32.h b/src/hal/hal_esp32.h index 936ff0a..e80e215 100644 --- a/src/hal/hal_esp32.h +++ b/src/hal/hal_esp32.h @@ -35,6 +35,26 @@ void hal_esp32_set_rssi_cal(int8_t rssi_cal); TickType_t hal_esp32_get_timer_duration(void); +/** + * Gets the time. + * + * The time is relative to boot time of the + * run when the device joined the TTN network. + * + * @return time (in seconds) + */ +uint32_t hal_esp32_get_time(void); + +/** + * Sets the time. + * + * The time is relative to boot time of the + * run when the device joined the TTN network. + * + * @param time_val time (in seconds) + */ +void hal_esp32_set_time(uint32_t time_val); + #ifdef __cplusplus } diff --git a/src/ttn.c b/src/ttn.c index bf1790e..235fe7a 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_nvs.h" #include "ttn_rtc.h" #define TAG "ttn" @@ -236,6 +237,29 @@ bool ttn_resume_after_deep_sleep(void) return true; } +bool ttn_resume_after_power_off(int off_duration) +{ + 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_nvs_restore(off_duration)) + return false; + + has_joined = true; + return true; +} + // Called immediately before sending join request message void config_rf_params(void) { @@ -340,6 +364,11 @@ void ttn_prepare_for_deep_sleep(void) stop(); } +void ttn_prepare_for_power_off(void) +{ + ttn_nvs_save(); + stop(); +} void ttn_wait_for_idle(void) { diff --git a/src/ttn_nvs.c b/src/ttn_nvs.c new file mode 100644 index 0000000..695f0be --- /dev/null +++ b/src/ttn_nvs.c @@ -0,0 +1,123 @@ +/******************************************************************************* + * + * 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 NVS. + *******************************************************************************/ + +#include "esp_log.h" +#include "esp_system.h" +#include "hal/hal_esp32.h" +#include "lmic/lmic.h" +#include "nvs_flash.h" +#include "ttn_rtc.h" +#include + +#define LMIC_OFFSET(field) __builtin_offsetof(struct lmic_t, field) +#define LMIC_DIST(field1, field2) (LMIC_OFFSET(field2) - LMIC_OFFSET(field1)) + +#define TAG "ttn_nvs" +#define NVS_FLASH_PARTITION "ttn" +#define NVS_FLASH_KEY_CHUNK_1 "chunk1" +#define NVS_FLASH_KEY_CHUNK_2 "chunk2" +#define NVS_FLASH_KEY_CHUNK_3 "chunk3" +#define NVS_FLASH_KEY_TIME "time" + +void ttn_nvs_save() +{ + nvs_handle handle = 0; + esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READWRITE, &handle); + if (res != ESP_OK) + goto done; + + // Save LMIC struct except client, osjob, pendTxData and frame + size_t len1 = LMIC_DIST(radio, pendTxData); + res = nvs_set_blob(handle, NVS_FLASH_KEY_CHUNK_1, &LMIC.radio, len1); + if (res != ESP_OK) + goto done; + + size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD; + res = nvs_set_blob(handle, NVS_FLASH_KEY_CHUNK_2, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, len2); + if (res != ESP_OK) + goto done; + + size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME; + res = nvs_set_blob(handle, NVS_FLASH_KEY_CHUNK_3, (u1_t *)&LMIC.frame + MAX_LEN_FRAME, len3); + if (res != ESP_OK) + goto done; + + res = nvs_set_u32(handle, NVS_FLASH_KEY_TIME, hal_esp32_get_time()); + if (res != ESP_OK) + goto done; + + res = nvs_commit(handle); + if (res != ESP_OK) + goto done; + +done: + nvs_close(handle); + + if (res == ESP_ERR_NVS_NOT_INITIALIZED) + { + ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first."); + } + else + { + ESP_ERROR_CHECK(res); + } +} + +bool ttn_nvs_restore(int off_duration) +{ + nvs_handle handle = 0; + esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READWRITE, &handle); + if (res != ESP_OK) + goto done; + + uint32_t time_val; + res = nvs_get_u32(handle, NVS_FLASH_KEY_TIME, &time_val); + if (res != ESP_OK) + goto done; + + size_t len1 = LMIC_DIST(radio, pendTxData); + res = nvs_get_blob(handle, NVS_FLASH_KEY_CHUNK_1, &LMIC.radio, &len1); + if (res != ESP_OK) + goto done; + + size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD; + res = nvs_get_blob(handle, NVS_FLASH_KEY_CHUNK_2, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, &len2); + if (res != ESP_OK) + goto done; + + size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME; + res = nvs_get_blob(handle, NVS_FLASH_KEY_CHUNK_3, (u1_t *)&LMIC.frame + MAX_LEN_FRAME, &len3); + if (res != ESP_OK) + goto done; + + // invalidate data + res = nvs_erase_key(handle, NVS_FLASH_KEY_TIME); + if (res != ESP_OK) + goto done; + + res = nvs_commit(handle); + if (res != ESP_OK) + goto done; + + if (off_duration != 0) + hal_esp32_set_time(time_val + off_duration * 60); + +done: + nvs_close(handle); + + if (res == ESP_ERR_NVS_NOT_INITIALIZED) + { + ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first."); + } + + return res == ESP_OK; +} diff --git a/src/ttn_nvs.h b/src/ttn_nvs.h new file mode 100644 index 0000000..996037b --- /dev/null +++ b/src/ttn_nvs.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 NVS. + *******************************************************************************/ + +#ifndef TTN_NVS_H +#define TTN_NVS_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + void ttn_nvs_save(); + bool ttn_nvs_restore(int off_duration); + +#ifdef __cplusplus +} +#endif + +#endif