mirror of
				https://github.com/manuelbl/ttn-esp32.git
				synced 2025-10-31 10:40:35 +01:00 
			
		
		
		
	Save and restore from power off
This commit is contained in:
		
							
								
								
									
										10
									
								
								examples/power_off_in_c/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								examples/power_off_in_c/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
							
								
								
									
										4
									
								
								examples/power_off_in_c/main/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/power_off_in_c/main/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| idf_component_register( | ||||
|     SRCS "main.c" | ||||
|     INCLUDE_DIRS "." | ||||
|     REQUIRES ttn-esp32) | ||||
							
								
								
									
										128
									
								
								examples/power_off_in_c/main/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								examples/power_off_in_c/main/main.c
									
									
									
									
									
										Normal file
									
								
							| @ -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)); | ||||
| } | ||||
| @ -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. | ||||
|      *  | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										29
									
								
								src/ttn.c
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								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) | ||||
| { | ||||
|  | ||||
							
								
								
									
										123
									
								
								src/ttn_nvs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/ttn_nvs.c
									
									
									
									
									
										Normal file
									
								
							| @ -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 <string.h> | ||||
|  | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/ttn_nvs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/ttn_nvs.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 <stdbool.h> | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" | ||||
| { | ||||
| #endif | ||||
|  | ||||
|     void ttn_nvs_save(); | ||||
|     bool ttn_nvs_restore(int off_duration); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user