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 +#include #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 + +#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 + +#ifdef __cplusplus +extern "C" +{ +#endif + + void ttn_rtc_save(); + bool ttn_rtc_restore(); + +#ifdef __cplusplus +} +#endif + +#endif