From 26d251122736c6731f94c46fed2552d3c230d96a Mon Sep 17 00:00:00 2001 From: Noah Pendleton <2538614+noahp@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:56:34 -0400 Subject: [PATCH] Re-sync with upstream (#1) --- CMakeLists.txt | 26 ++ README.md | 50 ++- main/CMakeLists.txt | 15 +- main/Kconfig.projbuild | 24 +- main/app_memfault_transport.h | 26 ++ main/app_memfault_transport_http.c | 13 + main/app_memfault_transport_mqtt.c | 171 ++++++++ main/cmd_app.c | 28 +- main/cmd_decl.h | 11 + main/cmd_wifi.c | 182 ++++----- main/cmd_wifi_legacy.c | 6 + main/console_example_main.c | 34 +- main/led.c | 25 +- .../memfault_metrics_heartbeat_config.def | 11 +- main/settings.c | 370 ++++++++++++++++++ main/settings.h | 41 ++ partitions_example_mqtt.csv | 10 + sdkconfig.defaults | 4 +- sdkconfig.mqtt | 2 + third-party/memfault-firmware-sdk | 2 +- 20 files changed, 910 insertions(+), 141 deletions(-) create mode 100644 main/app_memfault_transport.h create mode 100644 main/app_memfault_transport_http.c create mode 100644 main/app_memfault_transport_mqtt.c create mode 100644 main/settings.c create mode 100644 main/settings.h create mode 100644 partitions_example_mqtt.csv create mode 100644 sdkconfig.mqtt diff --git a/CMakeLists.txt b/CMakeLists.txt index e09af0e..dec3cac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,11 @@ if(DEFINED IDF_VERSION_MAJOR) endif() endif() +# Look for the Memfault SDK in a subdirectory first get_filename_component(memfault_firmare_sdk_dir third-party/memfault-firmware-sdk ABSOLUTE) +if(NOT EXISTS ${memfault_firmare_sdk_dir}) +get_filename_component(memfault_firmare_sdk_dir ../../../../ ABSOLUTE) +endif() include(${memfault_firmare_sdk_dir}/ports/esp_idf/memfault.cmake) # NOTE: This include also applies global compiler options, make sure @@ -24,6 +28,28 @@ include(${memfault_firmare_sdk_dir}/ports/esp_idf/memfault.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(${PROJECT_NAME}) +# Check for invalid partition table configurations +if ( + CONFIG_APP_MEMFAULT_TRANSPORT_HTTP AND + NOT CONFIG_PARTITION_TABLE_CUSTOM_FILENAME STREQUAL "partitions_example.csv" +) + message(WARNING "Data transport is HTTP but using partition table ${CONFIG_PARTITION_TABLE_CUSTOM_FILENAME}") + set(INVALID_PARTITION_TABLE true) +elseif( + CONFIG_APP_MEMFAULT_TRANSPORT_MQTT AND + NOT CONFIG_PARTITION_TABLE_CUSTOM_FILENAME STREQUAL "partitions_example_mqtt.csv" +) + message(WARNING "Data transport is MQTT but using partition table ${CONFIG_PARTITION_TABLE_CUSTOM_FILENAME}") + set(INVALID_PARTITION_TABLE true) +endif() + +if (INVALID_PARTITION_TABLE) + message(FATAL_ERROR + "Invalid partition table configuration, check CONFIG_APP_MEMFAULT_TRANSPORT configs.\ + If this error occurs repeatedly run `idf.py fullclean && rm sdkconfig`" + ) +endif() + # Add the Memfault Build ID so each build can have a unique version. set(IDF_PROJECT_EXECUTABLE ${PROJECT_NAME}.elf) add_custom_command(TARGET ${IDF_PROJECT_EXECUTABLE} diff --git a/README.md b/README.md index 28ef13b..766cd84 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,38 @@ -# ESP32 Standalone Memfault Demo Application +# esp32 Demo Application -This Demo App is based on the example in the Memfault Firmware SDK: +This Demo App is based on the console example from ESP-IDF, which can be found +here relative to the ESP-IDF SDK root folder: -https://github.com/memfault/memfault-firmware-sdk/tree/0.39.0/examples/esp32 +- `examples/system/console/advanced/` -It also showcases including the Memfault SDK as a submodule for an ESP-IDF -project. +## Configuring for MQTT -The submodule was added with this command: +This application includes an option to send Memfault data over MQTT. This option requires a few extra pieces to set up. +You can either follow the steps outlined here or use your own MQTT setup. -```bash -❯ git submodule add https://github.com/memfault/memfault-firmware-sdk.git \ - third-party/memfault-firmware-sdk -``` +### Broker Setup -When cloning this repo, either use the `--recursive` flag or update submodules -after cloning: +1. Install a local installtion of Cedalo by following the [installation guide](https://docs.cedalo.com/management-center/installation/) +2. Login to Cedalo at +3. Create a new client login for the device + - Ensure device client has the "client" role to allow publishing data +4. Create a new client login for the Python service + - Ensure Python service client has "client" role to allow subscribing to data -```bash -❯ git clone --recursive https://github.com/memfault/esp32-standalone-example -# or -❯ git clone https://github.com/memfault/esp32-standalone-example -❯ cd esp32-standalone-example -❯ git submodule update --init --recursive -``` +### Service Setup -## Building +1. Modify the script found in Docs->Best Practices->MQTT with Memfault with the the following: + 1. The service client login information previously created + 2. Connection info for your local broker + 3. Map of Memfault projects to project keys +2. Start the service by running `python mqtt.py` -Can be built using `idf.py build` as usual. +### Device Setup + +1. Make the following modifications to `main/app_memfault_transport_mqtt.c`: + 1. Update `MEMFAULT_PROJECT` macro with your project's name + 2. Update `s_mqtt_config` with your setup's IP address, and MQTT client username and password +2. Clean your existing build with `idf.py fullclean && rm sdkconfig` +3. Set your target: `idf.py set-target ` +4. Build your image: `idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.mqtt" build` +5. Flash to your device using `idf.py flash` diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ec06463..2dce33c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,9 +4,14 @@ list(APPEND cmd_system.c console_example_main.c led.c - memfault/memfault_platform_device_info.c ) +if (CONFIG_APP_MEMFAULT_TRANSPORT_HTTP) + list(APPEND COMPONENT_SRCS app_memfault_transport_http.c) +elseif(CONFIG_APP_MEMFAULT_TRANSPORT_MQTT) + list(APPEND COMPONENT_SRCS app_memfault_transport_mqtt.c) +endif() + # the 'cmd_wifi.c' implementation is different for ESP-IDF v5+ if("${IDF_VERSION_MAJOR}" VERSION_GREATER_EQUAL 5) list(APPEND @@ -20,6 +25,14 @@ else() ) endif() +# include settings.c only on idf >= 4 +if("${IDF_VERSION_MAJOR}" VERSION_GREATER_EQUAL 4) + list(APPEND + COMPONENT_SRCS + settings.c + ) +endif() + set(COMPONENT_ADD_INCLUDEDIRS . memfault diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 1ee9494..4fd686b 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -8,13 +8,6 @@ config STORE_HISTORY command history. If this option is enabled, initalizes a FAT filesystem and uses it to store command history. -config MEMFAULT_ESP32_MAIN_FIRMWARE_VERSION - string "Main firmware version" - default "1.0.0-dev" - help - The version of the main firmware. Used when the device reports in as - well as for OTA checks. - config MEMFAULT_APP_OTA bool "Enable automatic periodic check+update for OTA" default y @@ -28,6 +21,23 @@ config MEMFAULT_APP_WIFI_AUTOJOIN help Automatically join if credentials are configured. +choice APP_MEMFAULT_TRANSPORT + prompt "Protocol to send chunks over" + default APP_MEMFAULT_TRANSPORT_HTTP + + config APP_MEMFAULT_TRANSPORT_HTTP + bool "HTTP" + config APP_MEMFAULT_TRANSPORT_MQTT + bool "MQTT" + select MQTT_PROTOCOL_5 +endchoice + +if APP_MEMFAULT_TRANSPORT_MQTT +config MEMFAULT_DEVICE_INFO_SOFTWARE_TYPE + string "Override default software version for the application" + default "esp32-main-mqtt" +endif + # These LED settings are taken from ESP-IDF: # examples/get-started/blink/main/blink_example_main.c diff --git a/main/app_memfault_transport.h b/main/app_memfault_transport.h new file mode 100644 index 0000000..8e25e45 --- /dev/null +++ b/main/app_memfault_transport.h @@ -0,0 +1,26 @@ +//! @file +//! +//! Copyright (c) Memfault, Inc. +//! See License.txt for details + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +//! Initializes any components needed for configured transport +//! +//! Transport is selected via CONFIG_APP_MEMFAULT_TRANSPORT choices +void app_memfault_transport_init(void); + +//! Sends all available Memfault chunks over configured transport +//! +//! Transport is selected via CONFIG_APP_MEMFAULT_TRANSPORT choices +//! +//! @return 0 on success or non-zero on error +int app_memfault_transport_send_chunks(void); + +#ifdef __cplusplus +} +#endif diff --git a/main/app_memfault_transport_http.c b/main/app_memfault_transport_http.c new file mode 100644 index 0000000..d7643e3 --- /dev/null +++ b/main/app_memfault_transport_http.c @@ -0,0 +1,13 @@ +//! @file +//! +//! Copyright (c) Memfault, Inc. +//! See License.txt for details + +#include "app_memfault_transport.h" +#include "memfault/esp_port/http_client.h" + +void app_memfault_transport_init(void) {} + +int app_memfault_transport_send_chunks(void) { + return memfault_esp_port_http_client_post_data(); +} diff --git a/main/app_memfault_transport_mqtt.c b/main/app_memfault_transport_mqtt.c new file mode 100644 index 0000000..0bfe0b5 --- /dev/null +++ b/main/app_memfault_transport_mqtt.c @@ -0,0 +1,171 @@ +//! @file +//! +//! Copyright (c) Memfault, Inc. +//! See License.txt for details + +#include +#include + +#include "app_memfault_transport.h" +#include "esp_log.h" +#include "memfault/components.h" +#include "mqtt_client.h" + +// TODO: Fill in with device's Memfault project +#define MEMFAULT_PROJECT "my_project" + +static const char *TAG = "app_memfault_transport_mqtt"; + +// TODO: Fill in with broker connection configuration +static esp_mqtt_client_config_t s_mqtt_config = { + .broker.address.uri = "mqtt://192.168.50.88", + .credentials.username = "test", + .credentials.authentication.password = "test1234", + .session.protocol_ver = MQTT_PROTOCOL_V_5, +}; +static SemaphoreHandle_t s_mqtt_connected = NULL; + +static esp_mqtt_client_handle_t s_mqtt_client = NULL; +static esp_mqtt5_publish_property_config_t s_publish_property = { + .topic_alias = 1, +}; +static char s_topic_string[128] = {0}; + +static uint8_t s_chunk_data[1024] = {0}; + +static void mqtt_event_handler(MEMFAULT_UNUSED void *handler_args, + MEMFAULT_UNUSED esp_event_base_t base, + MEMFAULT_UNUSED int32_t event_id, void *event_data) { + esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; + + switch (event->event_id) { + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "Connected to MQTT broker"); + xSemaphoreGive(s_mqtt_connected); + break; + default: + ESP_LOGE(TAG, "Unknown MQTT event received[%d]", event->event_id); + break; + } +} + +static void prv_close_client(void) { + int rv = esp_mqtt_client_disconnect(s_mqtt_client); + if (rv) { + ESP_LOGW(TAG, "Failed to disconnect[%d]", rv); + } + + rv = esp_mqtt_client_destroy(s_mqtt_client); + if (rv) { + ESP_LOGW(TAG, "Failed to destroy client[%d]", rv); + } + memfault_metrics_heartbeat_timer_stop(MEMFAULT_METRICS_KEY(mqtt_conn_uptime)); + + s_mqtt_client = NULL; +} + +static int prv_create_client(void) { + if (s_mqtt_client) { + return 0; + } + + s_mqtt_client = esp_mqtt_client_init(&s_mqtt_config); + if (s_mqtt_client == NULL) { + ESP_LOGE(TAG, "MQTT client failed to initialize"); + return -1; + } + + int rv = + esp_mqtt_client_register_event(s_mqtt_client, MQTT_EVENT_CONNECTED, mqtt_event_handler, NULL); + if (rv) { + ESP_LOGE(TAG, "MQTT event handler registration failed[%d]", rv); + } + + rv = esp_mqtt_client_start(s_mqtt_client); + if (rv) { + ESP_LOGE(TAG, "MQTT client start failed[%d]", rv); + return -1; + } + + // Wait for successful connection + rv = xSemaphoreTake(s_mqtt_connected, (1000 * 10) / portTICK_PERIOD_MS); + if (rv != pdTRUE) { + ESP_LOGE(TAG, "MQTT client failed to connect[%d]", rv); + memfault_metrics_heartbeat_timer_start(MEMFAULT_METRICS_KEY(mqtt_conn_downtime)); + prv_close_client(); + return -1; + } + + // Update connection metrics when connected + memfault_metrics_heartbeat_timer_stop(MEMFAULT_METRICS_KEY(mqtt_conn_downtime)); + memfault_metrics_heartbeat_timer_start(MEMFAULT_METRICS_KEY(mqtt_conn_uptime)); + + // Set topic alias before publishing + rv = esp_mqtt5_client_set_publish_property(s_mqtt_client, &s_publish_property); + if (rv != 0) { + ESP_LOGW(TAG, "MQTT client could not set publish property [%d]", rv); + } + return 0; +} + +static const char *prv_get_device_serial(void) { + sMemfaultDeviceInfo info = {0}; + memfault_platform_get_device_info(&info); + return info.device_serial; +} + +void prv_build_topic_string(void) { + // String already built + if (strlen(s_topic_string) > 0) { + return; + } + + const char *device_serial = prv_get_device_serial(); + snprintf(s_topic_string, MEMFAULT_ARRAY_SIZE(s_topic_string), + "memfault/" MEMFAULT_PROJECT "/%s/chunks", device_serial); +} + +void app_memfault_transport_init(void) { +#if MEMFAULT_FREERTOS_PORT_USE_STATIC_ALLOCATION != 0 + static StaticSemaphore_t s_mqtt_connected; + s_mqtt_connected = xSemaphoreCreateBinaryStatic(&s_mqtt_connected); +#else + s_mqtt_connected = xSemaphoreCreateBinary(); +#endif +} + +int app_memfault_transport_send_chunks(void) { + int rv = prv_create_client(); + + if (rv) { + return rv; + } + + prv_build_topic_string(); + + ESP_LOGD(TAG, "Checking for packetizer data"); + while (memfault_packetizer_data_available()) { + size_t chunk_size = MEMFAULT_ARRAY_SIZE(s_chunk_data); + bool chunk_filled = memfault_packetizer_get_chunk(s_chunk_data, &chunk_size); + + if (!chunk_filled) { + ESP_LOGW(TAG, "No chunk data produced"); + break; + } + + rv = esp_mqtt_client_publish(s_mqtt_client, s_topic_string, (char *)s_chunk_data, chunk_size, 1, + 0); + if (rv < 0) { + ESP_LOGE(TAG, "MQTT failed to publish[%d]", rv); + memfault_packetizer_abort(); + break; + } + + memfault_metrics_heartbeat_add(MEMFAULT_METRICS_KEY(mqtt_publish_bytes), chunk_size); + memfault_metrics_heartbeat_add(MEMFAULT_METRICS_KEY(mqtt_publish_count), 1); + ESP_LOGD(TAG, "chunk[%d], len[%zu] published to %s", rv, chunk_size, s_topic_string); + } + + prv_close_client(); + return rv; +} diff --git a/main/cmd_app.c b/main/cmd_app.c index e688e59..74bbedf 100644 --- a/main/cmd_app.c +++ b/main/cmd_app.c @@ -14,6 +14,7 @@ #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" +#include "memfault/components.h" #include "sdkconfig.h" //! cause the task watchdog to fire by deadlocking the example task @@ -26,16 +27,31 @@ static int test_task_watchdog(int argc, char** argv) { return 0; } -static void register_test_task_watchdog(void) { - const esp_console_cmd_t cmd = { +static int prv_coredump_size(int argc, char** argv) { + (void)argc, (void)argv; + + sMfltCoredumpStorageInfo storage_info = {0}; + memfault_platform_coredump_storage_get_info(&storage_info); + const size_t size_needed = memfault_coredump_storage_compute_size_required(); + printf("coredump storage capacity: %zuB\n", storage_info.size); + printf("coredump size required: %zuB\n", size_needed); + return 0; +} + +void register_app(void) { + const esp_console_cmd_t test_watchdog_cmd = { .command = "test_task_watchdog", .help = "Demonstrate the task watchdog tripping on a deadlock", .hint = NULL, .func = &test_task_watchdog, }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} + ESP_ERROR_CHECK(esp_console_cmd_register(&test_watchdog_cmd)); -void register_app(void) { - register_test_task_watchdog(); + const esp_console_cmd_t coredump_size_cmd = { + .command = "coredump_size", + .help = "Print the coredump storage capacity and the capacity required", + .hint = NULL, + .func = &prv_coredump_size, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&coredump_size_cmd)); } diff --git a/main/cmd_decl.h b/main/cmd_decl.h index 22bb08c..ecf6a09 100644 --- a/main/cmd_decl.h +++ b/main/cmd_decl.h @@ -21,6 +21,17 @@ void register_wifi(void); bool wifi_join(const char* ssid, const char* pass); void wifi_load_creds(char** ssid, char** password); +#define MEMFAULT_PROJECT_KEY_LEN 32 +// if gcc < 11, disable -Wattributes, "access" attribute is not supported +#if __GNUC__ < 11 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wattributes" +#endif +__attribute__((access(write_only, 1, 2))) int wifi_get_project_key(char* project_key, + size_t project_key_len); +#if __GNUC__ < 11 + #pragma GCC diagnostic pop +#endif // Register app-specific console commands void register_app(void); diff --git a/main/cmd_wifi.c b/main/cmd_wifi.c index d92bada..3ef075e 100644 --- a/main/cmd_wifi.c +++ b/main/cmd_wifi.c @@ -16,8 +16,7 @@ #include "esp_wifi.h" #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" -#include "nvs.h" -#include "nvs_flash.h" +#include "settings.h" // enable for more verbose debug logs #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG @@ -121,8 +120,8 @@ bool wifi_join(const char *ssid, const char *pass) { .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD, }, }; - strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); - strncpy((char *)wifi_config.sta.password, pass, sizeof(wifi_config.sta.password)); + strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1); + strncpy((char *)wifi_config.sta.password, pass, sizeof(wifi_config.sta.password) - 1); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); @@ -183,55 +182,19 @@ static int connect(int argc, char **argv) { //! Since the creds are set via cli, we can safely assume there's no null bytes //! in the password, despite being theoretically permissible by the spec static void prv_save_wifi_creds(const char* ssid, const char* password) { - // Initialize NVS - esp_err_t err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { - // NVS partition was truncated and needs to be erased - // Retry nvs_flash_init - ESP_ERROR_CHECK(nvs_flash_erase()); - err = nvs_flash_init(); + // length arg is ignored for string-type settings + esp_err_t err = settings_set(kSettingsWifiSsid, ssid, 0); + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) writing ssid to NVS!", esp_err_to_name(err)); } - ESP_ERROR_CHECK(err); - - // Open - nvs_handle_t nvs_handle; - err = nvs_open("storage", NVS_READWRITE, &nvs_handle); + err = settings_set(kSettingsWifiPassword, password, 0); if (err != ESP_OK) { - ESP_LOGE(__func__, "Error (%s) opening NVS handle!", esp_err_to_name(err)); - } else { - ESP_LOGD(__func__, "Opened NVS handle"); - - // Write - err = nvs_set_str(nvs_handle, "wifi_ssid", ssid); - if (err != ESP_OK) { - ESP_LOGE(__func__, "Error (%s) writing ssid to NVS!", esp_err_to_name(err)); - } else { - ESP_LOGD(__func__, "Wrote ssid to NVS"); - } - err = nvs_set_str(nvs_handle, "wifi_password", password); - if (err == ESP_OK) { - if (err != ESP_OK) { - ESP_LOGE(__func__, "Error (%s) writing password to NVS!", esp_err_to_name(err)); - } else { - ESP_LOGD(__func__, "Wrote password to NVS"); - } - } - - // Commit written value. - // After setting any values, nvs_commit() must be called to ensure changes are written - // to flash storage. Implementations may write to storage at other times, - // but this is not guaranteed. - err = nvs_commit(nvs_handle); - if (err != ESP_OK) { - ESP_LOGE(__func__, "Error (%s) committing NVS!", esp_err_to_name(err)); - } else { - ESP_LOGD(__func__, "Successfully saved new wifi creds"); - strncpy(s_wifi_ssid, ssid, sizeof(s_wifi_ssid)); - strncpy(s_wifi_pass, password, sizeof(s_wifi_pass)); - } - - // Close - nvs_close(nvs_handle); + ESP_LOGE(__func__, "Error (%s) writing password to NVS!", esp_err_to_name(err)); + } + if (err == ESP_OK) { + ESP_LOGD(__func__, "Successfully saved new wifi creds"); + strncpy(s_wifi_ssid, ssid, sizeof(s_wifi_ssid) - 1); + strncpy(s_wifi_pass, password, sizeof(s_wifi_pass) - 1); } } @@ -248,50 +211,23 @@ void wifi_load_creds(char** ssid, char** password) { *ssid = NULL; *password = NULL; - // Initialize NVS - esp_err_t err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { - // NVS partition was truncated and needs to be erased - // Retry nvs_flash_init - ESP_ERROR_CHECK(nvs_flash_erase()); - err = nvs_flash_init(); + ESP_LOGD(__func__, "Reading wifi creds ... "); + size_t len = sizeof(s_wifi_ssid); + esp_err_t err = settings_get(kSettingsWifiSsid, s_wifi_ssid, &len); + if (err != ESP_OK) { + ESP_LOGE(__func__, "failed reading ssid"); + return; } - ESP_ERROR_CHECK(err); - // Open - nvs_handle_t nvs_handle; - err = nvs_open("storage", NVS_READWRITE, &nvs_handle); + len = sizeof(s_wifi_pass); + err = settings_get(kSettingsWifiPassword, s_wifi_pass, &len); if (err != ESP_OK) { - ESP_LOGE(__func__, "Error (%s) opening NVS handle!", esp_err_to_name(err)); - } else { - ESP_LOGD(__func__, "Opened NVS handle"); - - // Read - ESP_LOGD(__func__, "Reading wifi creds ... "); - size_t len = sizeof(s_wifi_ssid); - err = nvs_get_str(nvs_handle, "wifi_ssid", s_wifi_ssid, &len); - if (err == ESP_OK) { - ESP_LOGD(__func__, "ssid size: %d", len); - } else { - ESP_LOGE(__func__, "failed reading ssid"); - goto close; - } - - len = sizeof(s_wifi_pass); - err = nvs_get_str(nvs_handle, "wifi_password", s_wifi_pass, &len); - if (err == ESP_OK) { - ESP_LOGD(__func__, "pw size: %d", len); - } else { - ESP_LOGE(__func__, "failed reading pw"); - goto close; - } - - *ssid = s_wifi_ssid; - *password = s_wifi_pass; - - close: - nvs_close(nvs_handle); + ESP_LOGE(__func__, "failed reading pw"); + return; } + + *ssid = s_wifi_ssid; + *password = s_wifi_pass; } // argtable3 requires this data structure to be valid through the entire command @@ -338,6 +274,61 @@ static int wifi_creds_set(int argc, char** argv) { return 0; } +static struct { + struct arg_str* projectkey; + struct arg_end* end; +} memfault_args; + +__attribute__((access(write_only, 1, 2))) int wifi_get_project_key(char* project_key, + size_t project_key_len) { + if (project_key_len != MEMFAULT_PROJECT_KEY_LEN + 1) { + ESP_LOGE(__func__, "Destination buffer must be sized exactly to %zu bytes", + MEMFAULT_PROJECT_KEY_LEN + 1); + return 1; + } + size_t len = project_key_len; + return settings_get(kSettingsProjectKey, project_key, &len); +} + +__attribute__((access(read_only, 1, 2))) static esp_err_t prv_set_project_key( + const char* project_key, size_t project_key_len) { + // should never happen + assert((project_key_len == MEMFAULT_PROJECT_KEY_LEN) && + (strlen(project_key) == MEMFAULT_PROJECT_KEY_LEN)); + + return settings_set(kSettingsProjectKey, project_key, project_key_len); +} + +static int project_key_set(int argc, char** argv) { + if (argc == 1) { + char project_key[MEMFAULT_PROJECT_KEY_LEN + 1]; + esp_err_t err = wifi_get_project_key(project_key, sizeof(project_key)); + if (err != ESP_OK) { + ESP_LOGE(__func__, "failed to load wifi creds"); + return 1; + } + + printf("%.*s\n", MEMFAULT_PROJECT_KEY_LEN, project_key); + return 0; + } + + int nerrors = arg_parse(argc, argv, (void**)&memfault_args); + if (nerrors != 0) { + arg_print_errors(stderr, memfault_args.end, argv[0]); + return 1; + } + + // set the project key to nvs + const char* projectkey = memfault_args.projectkey->sval[0]; + if (strlen(projectkey) != MEMFAULT_PROJECT_KEY_LEN) { + ESP_LOGE(__func__, "Project key must be %d characters", MEMFAULT_PROJECT_KEY_LEN); + return ESP_ERR_INVALID_ARG; + } + esp_err_t err = prv_set_project_key(projectkey, strlen(projectkey)); + + return err; +} + void register_wifi(void) { join_args.ssid = arg_str1(NULL, NULL, "", "SSID of AP"); join_args.password = arg_str0(NULL, NULL, "", "PSK of AP"); @@ -360,6 +351,15 @@ void register_wifi(void) { .argtable = &wifi_creds_args}; ESP_ERROR_CHECK(esp_console_cmd_register(&config_cmd)); + memfault_args.projectkey = arg_str1(NULL, NULL, "", "Memfault Project Key"); + memfault_args.end = arg_end(1); + const esp_console_cmd_t project_key_cmd = {.command = "project_key", + .help = "Save Memfault Project Key to NVS", + .hint = NULL, + .func = &project_key_set, + .argtable = &memfault_args}; + ESP_ERROR_CHECK(esp_console_cmd_register(&project_key_cmd)); + const esp_console_cmd_t disconnect_cmd = {.command = "wifi_disconnect", .help = "Disconnect from WiFi AP", .hint = NULL, diff --git a/main/cmd_wifi_legacy.c b/main/cmd_wifi_legacy.c index 52176bc..5b26c0a 100644 --- a/main/cmd_wifi_legacy.c +++ b/main/cmd_wifi_legacy.c @@ -40,6 +40,12 @@ const int CONNECTED_BIT = BIT0; typedef nvs_handle nvs_handle_t; #endif +int wifi_get_project_key(char* project_key, size_t project_key_len) { + // Configurable project key not supported, project key must be compiled in via + // CONFIG_MEMFAULT_PROJECT_KEY + return 1; +} + static esp_err_t event_handler(void* ctx, system_event_t* event) { switch (event->event_id) { case SYSTEM_EVENT_STA_GOT_IP: diff --git a/main/console_example_main.c b/main/console_example_main.c index 1e7e0b6..ca5b91b 100644 --- a/main/console_example_main.c +++ b/main/console_example_main.c @@ -11,6 +11,7 @@ #include #include +#include "app_memfault_transport.h" #include "argtable3/argtable3.h" #include "cmd_decl.h" #include "driver/uart.h" @@ -32,6 +33,7 @@ #include "memfault/esp_port/version.h" #include "nvs.h" #include "nvs_flash.h" +#include "settings.h" static const char *TAG = "example"; @@ -47,7 +49,13 @@ static const char *TAG = "example"; static void initialize_filesystem() { static wl_handle_t wl_handle; const esp_vfs_fat_mount_config_t mount_config = {.max_files = 4, .format_if_mount_failed = true}; - esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage", &mount_config, &wl_handle); + esp_err_t err = + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + esp_vfs_fat_spiflash_mount_rw_wl + #else + esp_vfs_fat_spiflash_mount + #endif + (MOUNT_PATH, "storage", &mount_config, &wl_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); return; @@ -74,10 +82,17 @@ static void initialize_console() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); +#else /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); +#endif /* Install UART driver for interrupt-driven reads and writes */ ESP_ERROR_CHECK(uart_driver_install(CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0)); @@ -211,6 +226,8 @@ static void prv_poster_task(void *args) { const TickType_t ota_check_interval = pdMS_TO_TICKS(60 * 60 * 1000); TickType_t ota_last_check_time = xTaskGetTickCount() - ota_check_interval; + app_memfault_transport_init(); + MEMFAULT_LOG_INFO("Data poster task up and running every %" PRIu32 "s.", interval_sec); while (true) { @@ -222,7 +239,7 @@ static void prv_poster_task(void *args) { // if connected, post any memfault data if (memfault_esp_port_wifi_connected()) { MEMFAULT_LOG_DEBUG("Checking for memfault data to send"); - int err = memfault_esp_port_http_client_post_data(); + int err = app_memfault_transport_send_chunks(); // if the check-in succeeded, set green, otherwise clear. // gives a quick eyeball check that the app is alive and well led_set_color((err == 0) ? kLedColor_Green : kLedColor_Red); @@ -320,8 +337,6 @@ void app_main() { #if !CONFIG_MEMFAULT_AUTOMATIC_INIT memfault_boot(); #endif - extern void memfault_platform_device_info_boot(void); - memfault_platform_device_info_boot(); memfault_device_info_dump(); g_unaligned_buffer = &s_my_buf[1]; @@ -349,6 +364,15 @@ void app_main() { register_system(); register_wifi(); register_app(); + settings_register_shell_commands(); + + // Attempt to load project key from nvs + static char project_key[MEMFAULT_PROJECT_KEY_LEN + 1] = {0}; + int err = wifi_get_project_key(project_key, sizeof(project_key)); + if (err == 0) { + project_key[sizeof(project_key) - 1] = '\0'; + g_mflt_http_client_config.api_key = project_key; + } #if MEMFAULT_COMPACT_LOG_ENABLE MEMFAULT_COMPACT_LOG_SAVE(kMemfaultPlatformLogLevel_Info, "This is a compact log example"); @@ -403,7 +427,7 @@ void app_main() { } else if (err == ESP_ERR_INVALID_ARG) { // command was empty } else if (err == ESP_OK && ret != ESP_OK) { - printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(err)); + printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret)); } else if (err != ESP_OK) { printf("Internal error: %s\n", esp_err_to_name(err)); } diff --git a/main/led.c b/main/led.c index 4e7b206..2581b98 100644 --- a/main/led.c +++ b/main/led.c @@ -13,11 +13,14 @@ #include #include +#include "settings.h" + #if CONFIG_BLINK_LED_RMT #include "led_strip.h" #endif #include "driver/gpio.h" +#include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/timers.h" #include "memfault/components.h" @@ -28,6 +31,8 @@ // Blue: System is performing an OTA update static int s_led_color = kLedColor_Red; +static int32_t s_led_brightness = 5; + void led_set_color(enum LED_COLORS color) { s_led_color = color; } @@ -69,7 +74,7 @@ static void prv_set_pixel(struct rgb_led_s rgb, bool set) { } } - #else // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + #else // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) static led_strip_t *pStrip_a; @@ -99,24 +104,22 @@ static void prv_heartbeat_led_callback(MEMFAULT_UNUSED TimerHandle_t handle) { /* If the addressable LED is enabled */ struct rgb_led_s rgb_led; - #define BRIGHTNESS 5 - switch (s_led_color) { default: case kLedColor_Red: - rgb_led.r = BRIGHTNESS; + rgb_led.r = s_led_brightness; rgb_led.g = 0; rgb_led.b = 0; break; case kLedColor_Green: rgb_led.r = 0; - rgb_led.g = BRIGHTNESS; + rgb_led.g = s_led_brightness; rgb_led.b = 0; break; case kLedColor_Blue: rgb_led.r = 0; rgb_led.g = 0; - rgb_led.b = BRIGHTNESS; + rgb_led.b = s_led_brightness; break; } @@ -154,9 +157,17 @@ static void led_config(void) { void led_init(void) { led_config(); + size_t len = sizeof(s_led_brightness); + (void)settings_get(kSettingsLedBrightness, &s_led_brightness, &len); + ESP_LOGI(__func__, "LED brightness: %" PRIi32, s_led_brightness); + // create a timer that blinks the LED, indicating the app is alive const char *const pcTimerName = "HeartbeatLED"; - const TickType_t xTimerPeriodInTicks = pdMS_TO_TICKS(500); + int32_t led_interval_ms = 500; + len = sizeof(led_interval_ms); + (void)settings_get(kSettingsLedBlinkIntervalMs, &led_interval_ms, &len); + ESP_LOGI(__func__, "LED blink interval: %" PRIi32, led_interval_ms); + const TickType_t xTimerPeriodInTicks = pdMS_TO_TICKS(led_interval_ms); TimerHandle_t timer; diff --git a/main/memfault/memfault_metrics_heartbeat_config.def b/main/memfault/memfault_metrics_heartbeat_config.def index 3769610..bc0a105 100644 --- a/main/memfault/memfault_metrics_heartbeat_config.def +++ b/main/memfault/memfault_metrics_heartbeat_config.def @@ -1,3 +1,12 @@ -// File for holding custom error traces: +#include "sdkconfig.h" + +// File for holding custom metrics: // https://mflt.io/embedded-metrics MEMFAULT_METRICS_KEY_DEFINE(PosterTaskNumSchedules, kMemfaultMetricType_Unsigned) + +#if defined(CONFIG_APP_MEMFAULT_TRANSPORT_MQTT) +MEMFAULT_METRICS_KEY_DEFINE(mqtt_publish_bytes, kMemfaultMetricType_Unsigned) +MEMFAULT_METRICS_KEY_DEFINE(mqtt_publish_count, kMemfaultMetricType_Unsigned) +MEMFAULT_METRICS_KEY_DEFINE(mqtt_conn_downtime, kMemfaultMetricType_Timer) +MEMFAULT_METRICS_KEY_DEFINE(mqtt_conn_uptime, kMemfaultMetricType_Timer) +#endif diff --git a/main/settings.c b/main/settings.c new file mode 100644 index 0000000..3774e5a --- /dev/null +++ b/main/settings.c @@ -0,0 +1,370 @@ +//! @file +//! +//! Copyright (c) Memfault, Inc. +//! See License.txt for details +//! +//! Implement app settings helpers + +#include "settings.h" + +#include +#include +#include + +#include "argtable3/argtable3.h" +#include "cmd_decl.h" +#include "esp_console.h" +#include "memfault/components.h" +#include "nvs.h" +#include "nvs_flash.h" + +// enable for more verbose debug logs +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#include "esp_log.h" + +//! settings types: string or int +enum settings_type { + kSettingsTypeString, + kSettingsTypeI32, +}; + +//! settings key to {type, key_string, default value} map +static const struct { + enum settings_type type; + const char *key_str; + const union { + const char *str; + const int32_t i32; + } default_value; +} settings_table[] = { + [kSettingsWifiSsid] = + { + kSettingsTypeString, + "wifi_ssid", + { + .str = "", + }, + }, + [kSettingsWifiPassword] = + { + kSettingsTypeString, + "wifi_password", + { + .str = "", + }, + }, + [kSettingsProjectKey] = + { + kSettingsTypeString, + "project_key", + { + .str = CONFIG_MEMFAULT_PROJECT_KEY, + }, + }, + [kSettingsLedBrightness] = + { + kSettingsTypeI32, + "led_brightness", + { + .i32 = 5, + }, + }, + [kSettingsLedBlinkIntervalMs] = + { + kSettingsTypeI32, + "led_blink_ms", + { + .i32 = 500, + }, + }, +}; + +static bool prv_settings_key_is_valid(enum settings_key key) { + return key < (sizeof(settings_table) / sizeof(settings_table[0])); +} + +static esp_err_t prv_open_nvs(nvs_handle_t *nvs_handle) { + // Initialize NVS + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // NVS partition was truncated and needs to be erased + // Retry nvs_flash_init + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + // Open + err = nvs_open("storage", NVS_READWRITE, nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) opening NVS handle!", esp_err_to_name(err)); + return err; + } + + return ESP_OK; +} + +#if __GNUC__ >= 11 +__attribute__((access(write_only, 2))) +#endif +int settings_get(enum settings_key key, void *value, + size_t *len) { + if (!prv_settings_key_is_valid(key)) { + ESP_LOGE(__func__, "Invalid key: %d", key); + return ESP_ERR_INVALID_ARG; + } + + // Initialize NVS + nvs_handle_t nvs_handle; + esp_err_t err = prv_open_nvs(&nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) opening NVS handle!", esp_err_to_name(err)); + return err; + } + + ESP_LOGD(__func__, "Opened NVS handle"); + + // Read + switch (settings_table[key].type) { + case kSettingsTypeString: + err = nvs_get_str(nvs_handle, settings_table[key].key_str, (char *)value, len); + break; + case kSettingsTypeI32: + err = nvs_get_i32(nvs_handle, settings_table[key].key_str, (int32_t *)value); + break; + default: + ESP_LOGE(__func__, "Invalid type: %d", settings_table[key].type); + err = ESP_ERR_INVALID_ARG; + goto close; + break; + } + if (err == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGD(__func__, "%d not in store, loading default", key); + err = ESP_OK; + switch (settings_table[key].type) { + case kSettingsTypeString: + strncpy(value, settings_table[key].default_value.str, *len - 1); + *len = strlen(settings_table[key].default_value.str); + break; + case kSettingsTypeI32: + *(int32_t *)value = settings_table[key].default_value.i32; + break; + default: + ESP_LOGE(__func__, "Invalid type: %d", settings_table[key].type); + err = ESP_ERR_INVALID_ARG; + goto close; + break; + } + + } else if (err != ESP_OK) { + ESP_LOGE(__func__, "failed reading %d", key); + } + +// Close +close: + nvs_close(nvs_handle); + + return err; +} + +#if __GNUC__ >= 11 +__attribute__((access(read_only, 2, 3))) +#endif +int settings_set(enum settings_key key, const void *value, size_t len) { + if (!prv_settings_key_is_valid(key)) { + ESP_LOGE(__func__, "Invalid key: %d", key); + return ESP_ERR_INVALID_ARG; + } + + // Initialize NVS + nvs_handle_t nvs_handle; + esp_err_t err = prv_open_nvs(&nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) opening NVS handle!", esp_err_to_name(err)); + return err; + } + + ESP_LOGD(__func__, "Opened NVS handle"); + + // Write + switch (settings_table[key].type) { + case kSettingsTypeString: + err = nvs_set_str(nvs_handle, settings_table[key].key_str, value); + break; + case kSettingsTypeI32: + err = nvs_set_i32(nvs_handle, settings_table[key].key_str, *(int32_t *)value); + break; + default: + ESP_LOGE(__func__, "Invalid type: %d", settings_table[key].type); + err = ESP_ERR_INVALID_ARG; + goto close; + break; + } + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) writing key %d to NVS!", esp_err_to_name(err), key); + } else { + ESP_LOGD(__func__, "Wrote key %d to NVS", key); + } + + // Commit written value. + // After setting any values, nvs_commit() must be called to ensure changes are written + // to flash storage. Implementations may write to storage at other times, + // but this is not guaranteed. + err = nvs_commit(nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) committing NVS!", esp_err_to_name(err)); + } else { + ESP_LOGD(__func__, "Successfully wrote key %d to NVS", key); + } + +// Close +close: + nvs_close(nvs_handle); + + return err; +} + +static struct { + struct arg_str *key; + struct arg_end *end; +} s_get_args; + +static struct { + struct arg_str *key; + struct arg_str *value; + struct arg_end *end; +} s_set_args; + +static int prv_string_to_key(const char *str, enum settings_key *key) { + for (size_t i = 0; i < sizeof(settings_table) / sizeof(settings_table[0]); i++) { + if (strcmp(str, settings_table[i].key_str) == 0) { + *key = i; + return 0; + } + } + + return 1; +} + +static int prv_get_and_print_setting(enum settings_key key) { + // settings longer than 100 not supported + size_t len = 100; + + char *value = malloc(len); + if (value == NULL) { + ESP_LOGE(__func__, "Error allocating %d bytes", len); + return 1; + } + + int err = settings_get(key, value, &len); + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) getting key %d", esp_err_to_name(err), key); + return 1; + } + + switch (settings_table[key].type) { + case kSettingsTypeString: + printf("%s: %s\n", settings_table[key].key_str, value); + break; + case kSettingsTypeI32: + printf("%s: %" PRIi32 "\n", settings_table[key].key_str, *(int32_t *)value); + break; + default: + break; + } + + free(value); + + return 0; +} + +static int prv_get_cmd(int argc, char **argv) { + if (argc == 1) { + // print every setting in the table + for (size_t i = 0; i < sizeof(settings_table) / sizeof(settings_table[0]); i++) { + (void)prv_get_and_print_setting(i); + } + return 0; + } + + int nerrors = arg_parse(argc, argv, (void **)&s_get_args); + if (nerrors != 0) { + arg_print_errors(stderr, s_get_args.end, argv[0]); + return 1; + } + + enum settings_key key; + if (prv_string_to_key(s_get_args.key->sval[0], &key)) { + ESP_LOGE(__func__, "Invalid key: %s", s_get_args.key->sval[0]); + return 1; + } + + return prv_get_and_print_setting(key); +} + +static int prv_set_cmd(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&s_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, s_set_args.end, argv[0]); + return 1; + } + + enum settings_key key; + if (prv_string_to_key(s_set_args.key->sval[0], &key)) { + ESP_LOGE(__func__, "Invalid key: %s", s_set_args.key->sval[0]); + return 1; + } + + int err = ESP_OK; + switch (settings_table[key].type) { + case kSettingsTypeString: + err = settings_set(key, s_set_args.value->sval[0], strlen(s_set_args.value->sval[0])); + break; + case kSettingsTypeI32: + // convert string to int + { + int32_t i32; + if (sscanf(s_set_args.value->sval[0], "%" SCNi32, &i32) != 1) { + ESP_LOGE(__func__, "Invalid value: %s", s_set_args.value->sval[0]); + return 1; + } + err = settings_set(key, &i32, sizeof(i32)); + } + break; + default: + return 1; + break; + } + + if (err != ESP_OK) { + ESP_LOGE(__func__, "Error (%s) setting key %d", esp_err_to_name(err), key); + return 1; + } + + return 0; +} + +void settings_register_shell_commands(void) { + s_get_args.key = arg_str1(NULL, NULL, "", "Key name to get"); + s_get_args.end = arg_end(1); + static const esp_console_cmd_t s_get_cmd = { + .command = "settings_get", + .help = "Get a setting", + .hint = NULL, + .func = prv_get_cmd, + .argtable = &s_get_args, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&s_get_cmd)); + + s_set_args.key = arg_str1(NULL, NULL, "", "Key name to set"); + s_set_args.value = arg_str1(NULL, NULL, "", "Value to set (string or i32)"); + s_set_args.end = arg_end(2); + static const esp_console_cmd_t s_set_cmd = { + .command = "settings_set", + .help = "Set a setting", + .hint = NULL, + .func = prv_set_cmd, + .argtable = &s_set_args, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&s_set_cmd)); +} diff --git a/main/settings.h b/main/settings.h new file mode 100644 index 0000000..e5fff2c --- /dev/null +++ b/main/settings.h @@ -0,0 +1,41 @@ +//! @file +//! +//! Copyright (c) Memfault, Inc. +//! See License.txt for details +//! +//! App settings helper functions. + +#pragma once + +#include + +#include "memfault/esp_port/version.h" + +enum settings_key { + kSettingsWifiSsid, + kSettingsWifiPassword, + kSettingsProjectKey, + kSettingsLedBrightness, + kSettingsLedBlinkIntervalMs, +}; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) + #if __GNUC__ >= 11 +__attribute__((access(write_only, 2))) + #endif + int settings_get(enum settings_key key, void *value, size_t *len); + #if __GNUC__ >= 11 +__attribute__((access(read_only, 2, 3))) + #endif + int settings_set(enum settings_key key, const void *value, size_t len); +void settings_register_shell_commands(void); +#else // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) +// stub definitions that always fail +static inline int settings_get(enum settings_key key, void *value, size_t *len) { + return -1; +} +static inline int settings_set(enum settings_key key, const void *value, size_t len) { + return -1; +} +static inline void settings_register_shell_commands(void) {} +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) diff --git a/partitions_example_mqtt.csv b/partitions_example_mqtt.csv new file mode 100644 index 0000000..399dbe4 --- /dev/null +++ b/partitions_example_mqtt.csv @@ -0,0 +1,10 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, 0xd000, 0x2000 +phy_init, data, phy, 0xf000, 0x1000 +factory, app, factory, 0x10000, 1048K, +storage, data, fat, , 256K, +coredump, data, coredump,, 256K, +ota_0, app, ota_0, , 1048K, +ota_1, app, ota_1, , 1048K, diff --git a/sdkconfig.defaults b/sdkconfig.defaults index ad6ef42..f585289 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -11,7 +11,6 @@ CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 -CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" CONFIG_APP_OFFSET=0x10000 # Enable FreeRTOS stats formatting functions, needed for 'tasks' command @@ -26,3 +25,6 @@ CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y + +# SoftAP is unused in this example, disabling it saves about 40kB flash +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n diff --git a/sdkconfig.mqtt b/sdkconfig.mqtt new file mode 100644 index 0000000..938c2ff --- /dev/null +++ b/sdkconfig.mqtt @@ -0,0 +1,2 @@ +CONFIG_APP_MEMFAULT_TRANSPORT_MQTT=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example_mqtt.csv" diff --git a/third-party/memfault-firmware-sdk b/third-party/memfault-firmware-sdk index bf7e954..66d6966 160000 --- a/third-party/memfault-firmware-sdk +++ b/third-party/memfault-firmware-sdk @@ -1 +1 @@ -Subproject commit bf7e954dfa246c7d2ef495ace4e41e8f69fbe8a7 +Subproject commit 66d696654a23ea414d3983f2aef8c568b607abef