diff --git a/Kconfig b/Kconfig index 093dadb..76ec662 100644 --- a/Kconfig +++ b/Kconfig @@ -10,6 +10,78 @@ config MEMFAULT_APP_OVERRIDE_SOFTWARE_VERSION Set this to override the built-in software version string from the VERSION file. Primarily used for building in CI. +config WIFI_WATCHDOG_ENABLE + bool "Enable WiFi watchdog" + default y + depends on WIFI && MEMFAULT + help + Enable WiFi watchdog that monitors WiFi connectivity and reboots + if a known network is available but not connected for a configurable + period of time. + +config WIFI_WATCHDOG_TIMEOUT_MINUTES + int "WiFi watchdog timeout in minutes" + default 5 + depends on WIFI_WATCHDOG_ENABLE + help + Time in minutes to wait before triggering a reboot when WiFi + credentials are configured, a known network is detected, but + WiFi is not connected. + +config WIFI_WATCHDOG_CHECK_INTERVAL_SECONDS + int "WiFi watchdog check interval in seconds" + default 30 + depends on WIFI_WATCHDOG_ENABLE + help + Interval in seconds between WiFi watchdog checks. + +config SNTP_TIME_SYNC_ENABLE + bool "Enable SNTP time synchronization" + default y + depends on SNTP && WIFI + help + Synchronizes the kernel real-time clock via an NTP server each time + a WiFi connection is established, and then periodically at the + interval set by SNTP_TIME_SYNC_RESYNC_INTERVAL_SECONDS. + Uses the Zephyr SNTP client (sntp_init/query/close) directly. + +config SNTP_TIME_SYNC_SERVER + string "NTP server hostname" + default "pool.ntp.org" + depends on SNTP_TIME_SYNC_ENABLE + help + Hostname of the NTP server to query for time synchronization. + +config SNTP_TIME_SYNC_RESYNC_INTERVAL_SECONDS + int "Periodic SNTP re-sync interval (seconds)" + default 21600 # 6 hours + depends on SNTP_TIME_SYNC_ENABLE + help + How often to re-sync the clock while WiFi is connected. + The initial sync is triggered immediately on WiFi connect. + +choice HEARTBEAT_LED_TYPE + prompt "Select heartbeat LED variant" + default HEARTBEAT_LED_STRIP if $(dt_alias_enabled,led-strip) + default HEARTBEAT_LED_GPIO if $(dt_alias_enabled,heartbeat-led) + +config HEARTBEAT_LED_STRIP + bool "WS2812 RGB LED strip" + depends on LED_STRIP + help + Enable heartbeat LED using a WS2812 RGB LED strip. + GREEN when WiFi is connected, RED otherwise. + Blinks at a 1-second interval. + +config HEARTBEAT_LED_GPIO + bool "GPIO LED" + depends on GPIO + help + Enable heartbeat LED using a simple GPIO-driven LED. + Blinks at a 1-second interval unconditionally. + +endchoice + endmenu config MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION diff --git a/boards/esp32c3_devkitm.overlay b/boards/esp32c3_devkitm.overlay index 4fb2ab8..f0ca8e6 100644 --- a/boards/esp32c3_devkitm.overlay +++ b/boards/esp32c3_devkitm.overlay @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + &wifi { status = "okay"; }; @@ -11,3 +13,45 @@ &coretemp { status = "okay"; }; + +/ { + aliases { + led-strip = &led_strip; + }; +}; + +/* WS2812 RGB LED on GPIO8 via SPI2 */ +&spi2 { + /* Workaround to support WS2812 driver */ + line-idle-low; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; + spi-max-frequency = <6400000>; + + /* WS2812 */ + chain-length = <1>; + spi-cpha; + spi-one-frame = <0xf0>; + spi-zero-frame = <0xc0>; + color-mapping = ; + }; +}; + +&pinctrl { + spim2_default: spim2_default { + group2 { + /* WS2812 data on GPIO8 */ + pinmux = ; + }; + }; +}; + +/* Enable the ESP32-C3 RTC timer counter so it can be used for time persistence + * across soft resets via the counter API and __noinit RAM anchor. */ +&rtc_timer { + status = "okay"; +}; \ No newline at end of file diff --git a/boards/esp32c6_devkitc_esp32c6_hpcore.conf b/boards/esp32c6_devkitc_esp32c6_hpcore.conf new file mode 100644 index 0000000..268fc1f --- /dev/null +++ b/boards/esp32c6_devkitc_esp32c6_hpcore.conf @@ -0,0 +1,3 @@ +# Enable DMA for heartbeat LED +CONFIG_DMA=y +CONFIG_LED_STRIP=y diff --git a/boards/esp32c6_devkitc_esp32c6_hpcore.overlay b/boards/esp32c6_devkitc_esp32c6_hpcore.overlay index b417db0..c8279b9 100644 --- a/boards/esp32c6_devkitc_esp32c6_hpcore.overlay +++ b/boards/esp32c6_devkitc_esp32c6_hpcore.overlay @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + &wifi { status = "okay"; }; @@ -11,3 +13,42 @@ &coretemp { status = "okay"; }; + +/ { + aliases { + led-strip = &led_strip; + }; +}; + +/* WS2812 RGB LED on GPIO8 via I2S */ +&i2s_default { + group1 { + pinmux = ; + }; +}; + +i2s_led: &i2s { + status = "okay"; + + dmas = <&dma 3>; + dma-names = "tx"; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-i2s"; + + reg = <0>; + chain-length = <1>; + color-mapping = ; + reset-delay = <500>; + }; +}; + +&dma { + status = "okay"; +}; + +/* Enable the ESP32-C6 RTC timer counter so it can be used for time persistence + * across soft resets via the counter API and __noinit RAM anchor. */ +&rtc_timer { + status = "okay"; +}; \ No newline at end of file diff --git a/boards/esp32s3_devkitc_esp32s3_procpu.conf b/boards/esp32s3_devkitc_esp32s3_procpu.conf index 56fd230..2e1a5e8 100644 --- a/boards/esp32s3_devkitc_esp32s3_procpu.conf +++ b/boards/esp32s3_devkitc_esp32s3_procpu.conf @@ -1,3 +1,7 @@ # Enable external SPI RAM CONFIG_SPI=y CONFIG_ESP_SPIRAM=y + +# Enable DMA for heartbeat LED +CONFIG_DMA=y +CONFIG_LED_STRIP=y diff --git a/boards/esp32s3_devkitc_esp32s3_procpu.overlay b/boards/esp32s3_devkitc_esp32s3_procpu.overlay index 44eee6f..3322257 100644 --- a/boards/esp32s3_devkitc_esp32s3_procpu.overlay +++ b/boards/esp32s3_devkitc_esp32s3_procpu.overlay @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + &wifi { status = "okay"; }; @@ -16,3 +18,42 @@ &psram0 { size = ; }; + +/ { + aliases { + led-strip = &led_strip; + }; +}; + +/* WS2812 RGB LED on GPIO38 via I2S0 */ +&i2s0_default { + group1 { + pinmux = ; + }; +}; + +i2s_led: &i2s0 { + status = "okay"; + + dmas = <&dma 3>; + dma-names = "tx"; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-i2s"; + + reg = <0>; + chain-length = <1>; + color-mapping = ; + reset-delay = <500>; + }; +}; + +&dma { + status = "okay"; +}; + +/* Enable the ESP32-S3 RTC timer counter so it can be used for time persistence + * across soft resets via the counter API and __noinit RAM anchor. */ +&rtc_timer { + status = "okay"; +}; \ No newline at end of file diff --git a/boards/esp32s3_devkitm_esp32s3_procpu.conf b/boards/esp32s3_devkitm_esp32s3_procpu.conf new file mode 100644 index 0000000..86c2a91 --- /dev/null +++ b/boards/esp32s3_devkitm_esp32s3_procpu.conf @@ -0,0 +1,3 @@ +# Enable DMA + LED_STRIP for heartbeat LED +CONFIG_DMA=y +CONFIG_LED_STRIP=y diff --git a/boards/esp32s3_devkitm_esp32s3_procpu.overlay b/boards/esp32s3_devkitm_esp32s3_procpu.overlay index b417db0..396d4ca 100644 --- a/boards/esp32s3_devkitm_esp32s3_procpu.overlay +++ b/boards/esp32s3_devkitm_esp32s3_procpu.overlay @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + &wifi { status = "okay"; }; @@ -11,3 +13,45 @@ &coretemp { status = "okay"; }; + +/* Enable the ESP32-S3 RTC timer counter so it can be used for time persistence + * across soft resets via the counter API and __noinit RAM anchor. */ +&rtc_timer { + status = "okay"; +}; + +/ { + aliases { + led-strip = &led_strip; + }; +}; + +/* WS2812 RGB LED on GPIO48 via SPI3 */ +&spi3 { + /* Workaround to support WS2812 driver */ + line-idle-low; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; + spi-max-frequency = <6400000>; + + /* WS2812 */ + chain-length = <1>; + spi-cpha; + spi-one-frame = <0xf0>; + spi-zero-frame = <0xc0>; + color-mapping = ; + }; +}; + +&pinctrl { + spim3_default: spim3_default { + group2 { + /* WS2812 data on GPIO48 */ + pinmux = ; + }; + }; +}; diff --git a/boards/esp_wrover_kit_procpu.conf b/boards/esp_wrover_kit_procpu.conf index e69de29..9d2d341 100644 --- a/boards/esp_wrover_kit_procpu.conf +++ b/boards/esp_wrover_kit_procpu.conf @@ -0,0 +1 @@ +CONFIG_HEARTBEAT_LED_GPIO=y diff --git a/boards/esp_wrover_kit_procpu.overlay b/boards/esp_wrover_kit_procpu.overlay index d342be1..b1c2bc6 100644 --- a/boards/esp_wrover_kit_procpu.overlay +++ b/boards/esp_wrover_kit_procpu.overlay @@ -1,3 +1,15 @@ &wifi { status = "okay"; }; + +/* Enable the ESP32 RTC timer counter so it can be used for time persistence + * across soft resets via the counter API and __noinit RAM anchor. */ +&rtc_timer { + status = "okay"; +}; + +/ { + aliases { + heartbeat-led = &red_led; + }; +}; diff --git a/config/memfault_metrics_heartbeat_config.def b/config/memfault_metrics_heartbeat_config.def new file mode 100644 index 0000000..2c545b4 --- /dev/null +++ b/config/memfault_metrics_heartbeat_config.def @@ -0,0 +1,11 @@ +// Define the WiFi scan result count max metric +// This tracks the maximum number of WiFi scan results seen during each heartbeat period +MEMFAULT_METRICS_KEY_DEFINE(wifi_scan_result_count_max, kMemfaultMetricType_Unsigned) + +// Number of successful SNTP time synchronizations in this heartbeat period +MEMFAULT_METRICS_KEY_DEFINE(sntp_sync_count, kMemfaultMetricType_Unsigned) + +// Clock error corrected by the most recent SNTP sync (seconds, signed). +// Only recorded when the system clock was already valid (> 2023) before the +// sync, so the initial 1970-epoch → real-time jump is excluded. +MEMFAULT_METRICS_KEY_DEFINE(sntp_sync_delta_seconds, kMemfaultMetricType_Signed) diff --git a/config/memfault_reboot_reason_user_config.def b/config/memfault_reboot_reason_user_config.def new file mode 100644 index 0000000..4abcb50 --- /dev/null +++ b/config/memfault_reboot_reason_user_config.def @@ -0,0 +1,2 @@ +// Wi-Fi Watchdog is not desirable, but should not impact stability score. +MEMFAULT_EXPECTED_REBOOT_REASON_DEFINE(WifiWatchdog) diff --git a/prj.conf b/prj.conf index 11dca2f..6e01e2e 100644 --- a/prj.conf +++ b/prj.conf @@ -40,20 +40,35 @@ CONFIG_NET_L2_WIFI_SHELL=y CONFIG_NET_L2_ETHERNET=y CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4=y +CONFIG_SETTINGS=y +CONFIG_SETTINGS_NVS=y +CONFIG_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_WIFI_CREDENTIALS=y +CONFIG_WIFI_CREDENTIALS_CONNECT_STORED=y + # Memfault configuration CONFIG_MEMFAULT=y CONFIG_MEMFAULT_LOGGING_ENABLE=y CONFIG_MEMFAULT_HTTP_ENABLE=y CONFIG_MEMFAULT_HTTP_PERIODIC_UPLOAD=y +CONFIG_MEMFAULT_REBOOT_REASON_CUSTOM_ENABLE=y +CONFIG_MEMFAULT_PERIODIC_FOTA_CHECK=y # Additional network configuration for Memfault features CONFIG_DNS_RESOLVER=y -CONFIG_POSIX_API=y +# Increase DNS server max count to 2, for some networks that have a primary and +# secondary DNS server +CONFIG_DNS_RESOLVER_MAX_SERVERS=2 CONFIG_NET_SOCKETS=y CONFIG_TLS_CREDENTIALS=y CONFIG_NET_SOCKETS_SOCKOPT_TLS=y CONFIG_NET_STATISTICS=y CONFIG_NET_STATISTICS_USER_API=y +# Bump this up a bit from the default of 4; this will enable SNTP, DHCP, + +# Memfault checkins to run concurrently. +CONFIG_NET_MAX_CONN=6 # MbedTLS config CONFIG_MBEDTLS_SHA1=y @@ -64,7 +79,12 @@ CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=4096 CONFIG_MBEDTLS_MEMORY_DEBUG=y CONFIG_MBEDTLS_SHELL=y CONFIG_MBEDTLS_AES_ROM_TABLES=y -CONFIG_MBEDTLS_PSA_CRYPTO_C=y + +# Disable PSA Crypto. We are missing some key flag or feature in PSA Crypto that +# prevents establishing a TLS connection with Memfault. Using MbedTLS built in +# ("legacy") crypto instead, which works fine. +# CONFIG_MBEDTLS_PSA_CRYPTO_C=y +CONFIG_MBEDTLS_USE_PSA_CRYPTO=n # Credential shell, useful for debug CONFIG_TLS_CREDENTIALS_SHELL=y @@ -77,8 +97,6 @@ CONFIG_MEMFAULT_LOG_LEVEL_DBG=y # Don't log printk() statements, they're meant for raw output to the console. CONFIG_LOG_PRINTK=n -CONFIG_LOG_MODE_IMMEDIATE=y - # Disable these temporarily, for debugging. CONFIG_MEMFAULT_COREDUMP_COLLECT_DATA_REGIONS=y CONFIG_MEMFAULT_COREDUMP_COLLECT_KERNEL_REGION=n @@ -108,5 +126,25 @@ CONFIG_STREAM_FLASH=y CONFIG_SENSOR=y CONFIG_ADC=y +# SNTP / real-time clock synchronization +# Uses the Zephyr SNTP client directly (sntp_init/query/close) to query +# pool.ntp.org and set the kernel real-time clock via sys_clock_settime. +# UDP is required by the SNTP subsystem. +CONFIG_SNTP=y +CONFIG_NET_UDP=y + +# RTC: use the ESP32 RTC timer counter directly (keeps running through soft +# resets). We store a (unix_epoch, counter_ticks) anchor in RTC slow RAM using +# RTC_NOINIT_ATTR from esp_attr.h (section .rtc_noinit). RTC slow RAM is a +# separate hardware region, immune to FOTA updates shifting the .data layout. +# On the next boot we read the counter, compute elapsed seconds, and restore the +# kernel clock before the first SNTP sync fires. +# CONFIG_COUNTER enables the Zephyr counter API; the ESP32 RTC counter driver is +# auto-enabled by DT_HAS_ESPRESSIF_ESP32_RTC_TIMER_ENABLED (set in overlays). +CONFIG_COUNTER=y + +# Show real wall-clock timestamps in log output once SNTP sets the clock +CONFIG_LOG_TIMESTAMP_USE_REALTIME=y + # Enable compiler warnings as errors CONFIG_COMPILER_WARNINGS_AS_ERRORS=y diff --git a/src/heartbeat_led.c b/src/heartbeat_led.c new file mode 100644 index 0000000..a45ac5a --- /dev/null +++ b/src/heartbeat_led.c @@ -0,0 +1,130 @@ +//! @file +//! +//! @brief Heartbeat LED module. +//! +//! Blinks an LED at a 1-second interval to indicate the device is running. +//! +//! On boards with a WS2812 RGB LED (CONFIG_HEARTBEAT_LED_STRIP): +//! - GREEN when WiFi is connected +//! - RED when WiFi is not connected +//! +//! On boards with a simple GPIO LED (CONFIG_HEARTBEAT_LED_GPIO): +//! - Blinks unconditionally at a 1-second interval + +#include +#include + +LOG_MODULE_REGISTER(heartbeat_led, LOG_LEVEL_DBG); + +// Blink half-period: 500ms on + 500ms off = 1 second total +#define HEARTBEAT_BLINK_HALF_PERIOD_MS 500 + +#define HEARTBEAT_LED_STACK_SIZE 1024 +#define HEARTBEAT_LED_PRIORITY 7 + +#if defined(CONFIG_HEARTBEAT_LED_STRIP) + +#include +#include +#include + +static const struct device* s_led_strip = DEVICE_DT_GET(DT_ALIAS(led_strip)); + +static bool s_wifi_connected = false; +static struct net_mgmt_event_callback s_wifi_led_mgmt_cb; + +// WiFi event handler to track connection state +static void prv_wifi_event_handler(struct net_mgmt_event_callback* cb, + uint64_t mgmt_event, struct net_if* iface) { + ARG_UNUSED(iface); + if (mgmt_event == NET_EVENT_WIFI_CONNECT_RESULT) { + const struct wifi_status* status = (const struct wifi_status*)cb->info; + s_wifi_connected = (status->status == 0); + LOG_DBG("Heartbeat LED: WiFi %s", + s_wifi_connected ? "connected" : "failed"); + } else if (mgmt_event == NET_EVENT_WIFI_DISCONNECT_RESULT) { + s_wifi_connected = false; + LOG_DBG("Heartbeat LED: WiFi disconnected"); + } +} + +static void prv_heartbeat_led_thread(void* arg1, void* arg2, void* arg3) { + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + net_mgmt_init_event_callback( + &s_wifi_led_mgmt_cb, prv_wifi_event_handler, + NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT); + net_mgmt_add_event_callback(&s_wifi_led_mgmt_cb); + + if (!device_is_ready(s_led_strip)) { + LOG_ERR("LED strip device not ready"); + return; + } + + bool led_on = false; + while (1) { + led_on = !led_on; + + struct led_rgb pixel; + if (led_on) { + if (s_wifi_connected) { + // Green: WiFi connected + pixel = (struct led_rgb){.r = 0, .g = 32, .b = 0}; + } else { + // Red: WiFi not connected + pixel = (struct led_rgb){.r = 32, .g = 0, .b = 0}; + } + } else { + // Off + pixel = (struct led_rgb){.r = 0, .g = 0, .b = 0}; + } + + int rc = led_strip_update_rgb(s_led_strip, &pixel, 1); + if (rc != 0) { + LOG_ERR("LED strip update failed: %d", rc); + } + + k_sleep(K_MSEC(HEARTBEAT_BLINK_HALF_PERIOD_MS)); + } +} + +#elif defined(CONFIG_HEARTBEAT_LED_GPIO) + +#include + +static const struct gpio_dt_spec s_led = + GPIO_DT_SPEC_GET(DT_ALIAS(heartbeat_led), gpios); + +static void prv_heartbeat_led_thread(void* arg1, void* arg2, void* arg3) { + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + if (!gpio_is_ready_dt(&s_led)) { + LOG_ERR("Heartbeat LED GPIO not ready"); + return; + } + + int rc = gpio_pin_configure_dt(&s_led, GPIO_OUTPUT_INACTIVE); + if (rc != 0) { + LOG_ERR("Failed to configure heartbeat LED GPIO: %d", rc); + return; + } + + while (1) { + gpio_pin_toggle_dt(&s_led); + k_sleep(K_MSEC(HEARTBEAT_BLINK_HALF_PERIOD_MS)); + } +} + +#endif /* CONFIG_HEARTBEAT_LED_STRIP / CONFIG_HEARTBEAT_LED_GPIO */ + +#if defined(CONFIG_HEARTBEAT_LED_STRIP) || defined(CONFIG_HEARTBEAT_LED_GPIO) + +K_THREAD_DEFINE(heartbeat_led_thread, HEARTBEAT_LED_STACK_SIZE, + prv_heartbeat_led_thread, NULL, NULL, NULL, + HEARTBEAT_LED_PRIORITY, 0, 0); + +#endif /* CONFIG_HEARTBEAT_LED_STRIP || CONFIG_HEARTBEAT_LED_GPIO */ diff --git a/src/heartbeat_metrics.c b/src/heartbeat_metrics.c new file mode 100644 index 0000000..143035c --- /dev/null +++ b/src/heartbeat_metrics.c @@ -0,0 +1,35 @@ +//! @file +//! +//! @brief Central Memfault heartbeat metrics collection point. +//! +//! The Memfault SDK calls memfault_metrics_heartbeat_collect_data() once per +//! heartbeat period. This single implementation delegates to every module +//! that exports a *_metrics_collect() helper, guarded by the module's own +//! CONFIG option so the linker eliminates unused code automatically. + +#include + +#include "memfault/components.h" + +LOG_MODULE_REGISTER(heartbeat_metrics, LOG_LEVEL_DBG); + +// Forward declarations – each module provides a collector when enabled. +// The declarations are guarded so this file carries no build dependency on +// disabled modules. +#if defined(CONFIG_WIFI_WATCHDOG_ENABLE) +extern void wifi_watchdog_metrics_collect(void); +#endif + +#if defined(CONFIG_SNTP_TIME_SYNC_ENABLE) +extern void sntp_time_sync_metrics_collect(void); +#endif + +void memfault_metrics_heartbeat_collect_data(void) { +#if defined(CONFIG_WIFI_WATCHDOG_ENABLE) + wifi_watchdog_metrics_collect(); +#endif + +#if defined(CONFIG_SNTP_TIME_SYNC_ENABLE) + sntp_time_sync_metrics_collect(); +#endif +} diff --git a/src/main.c b/src/main.c index 613b80c..147fc0a 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,12 @@ //! @file +#include #include #include #include +#include +#include +#include #include "app_version.h" #include "memfault/components.h" @@ -15,14 +19,30 @@ LOG_MODULE_REGISTER(mflt_app, LOG_LEVEL_DBG); // Memfault SDK release extern const char* memfault_zephyr_get_device_id(void); +static const char* prv_get_software_version(void) { + static char s_software_version[64]; + + // CI builds will often set git sha = "". in that case, omit that portion of + // the software version string + if (ZEPHYR_MEMFAULT_EXAMPLE_GIT_SHA1[0] == '\0') { + return CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION; + } + + // Otherwise, append it to demonstrate how to include git sha in the software + // version string + snprintf(s_software_version, sizeof(s_software_version), "%s+%s", + CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION, + ZEPHYR_MEMFAULT_EXAMPLE_GIT_SHA1); + return s_software_version; +} + void memfault_platform_get_device_info(sMemfaultDeviceInfo* info) { // override the software version only to demonstrate inserting the GIT SHA *info = (sMemfaultDeviceInfo){ - .device_serial = memfault_zephyr_get_device_id(), - .software_type = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_TYPE, - .software_version = - CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION "+" ZEPHYR_MEMFAULT_EXAMPLE_GIT_SHA1, - .hardware_version = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_HARDWARE_VERSION, + .device_serial = memfault_zephyr_get_device_id(), + .software_type = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_TYPE, + .software_version = prv_get_software_version(), + .hardware_version = CONFIG_MEMFAULT_BUILTIN_DEVICE_INFO_HARDWARE_VERSION, }; } @@ -34,5 +54,15 @@ int main(void) { memfault_device_info_dump(); memfault_zephyr_port_install_root_certs(); + // Initiate auto connection on the wifi interface + struct net_if* iface = net_if_get_wifi_sta(); + int rc = net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0); + + if (rc) { + printk( + "an error occurred when trying to auto-connect to a network. err: %d", + rc); + } + return 0; } diff --git a/src/sntp_time_sync.c b/src/sntp_time_sync.c new file mode 100644 index 0000000..e378565 --- /dev/null +++ b/src/sntp_time_sync.c @@ -0,0 +1,328 @@ +//! @file +//! +//! @brief SNTP / real-time clock synchronization module. +//! +//! On each WiFi connect event a dedicated thread wakes up, waits 2 s for +//! DHCP/DNS to settle, then queries CONFIG_SNTP_TIME_SYNC_SERVER using the +//! Zephyr SNTP client API (sntp_init / sntp_query / sntp_close). On success +//! the kernel real-time clock is set via sys_clock_settime(SYS_CLOCK_REALTIME) +//! so that CONFIG_LOG_TIMESTAMP_USE_REALTIME shows accurate UTC times in logs. +//! +//! The thread also re-syncs automatically every +//! CONFIG_SNTP_TIME_SYNC_RESYNC_INTERVAL_SECONDS while WiFi stays connected. +//! +//! Time persistence across soft resets (watchdog, crash, SW reset): +//! Per the ESP32 hardware docs (espressif,esp32-rtc-timer): "Any reset/sleep +//! mode, except for the power-up reset, will not stop or reset the RTC Timer. +//! There is also no need to enable the RTC Timer node, it starts running from +//! power-up." +//! +//! We exploit this as follows: +//! - On every SNTP sync, a (unix_sec, counter_ticks) anchor is written into a +//! struct placed in RTC slow RAM via RTC_NOINIT_ATTR (esp_attr.h section +//! ".rtc_noinit"). RTC slow RAM is a separate hardware region that is NOT +//! cleared on soft resets, and is not part of the .data/.bss segment, so it +//! is immune to FOTA updates changing the main RAM layout. +//! - On boot, if the magic sentinel in the anchor is intact (soft reset +//! path), +//! we read the current counter ticks, compute elapsed seconds, and restore +//! the kernel clock before the first SNTP sync fires. +//! - On a power-on reset, RTC slow RAM IS cleared → magic is 0 → we skip +//! restoration and wait for SNTP. The counter itself is already running +//! from power-up so the first SNTP sync will immediately produce a valid +//! anchor. +//! +//! Required Kconfig: +//! CONFIG_SNTP=y +//! CONFIG_NET_UDP=y (SNTP runs over UDP) +//! CONFIG_DNS_RESOLVER=y (for zsock_getaddrinfo) +//! CONFIG_COUNTER=y (counter API for RTC timer) +//! CONFIG_SNTP_TIME_SYNC_ENABLE=y (this module, default y) + +#include // RTC_NOINIT_ATTR – places data in .rtc_noinit (RTC slow RAM) +#include // struct tm, struct timespec, gmtime_r +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "memfault/components.h" + +LOG_MODULE_REGISTER(sntp_time_sync, LOG_LEVEL_DBG); + +#if defined(CONFIG_SNTP_TIME_SYNC_ENABLE) + +// Heartbeat counter: successful SNTP syncs in the current period +static uint32_t s_sync_count = 0; + +// Clock delta from the most recent sync where the pre-sync clock was valid. +// Signed: positive = system was behind NTP, negative = system was ahead. +static int32_t s_last_sync_delta_s = 0; +static bool s_sync_delta_valid = false; + +// ---------------------------------------------------------------------------- +// RTC anchor: persists time across soft resets via RTC slow RAM. +// RTC_NOINIT_ATTR (from esp_attr.h) places the struct in the .rtc_noinit +// linker section, which maps to the ESP32 RTC slow memory region. This region +// is NOT cleared on soft resets (watchdog, crash, SW reset), and is entirely +// separate from the main .data/.bss segment, so it is unaffected by FOTA +// updates that change the normal RAM layout. A magic sentinel detects fresh +// power-on resets where RTC slow RAM IS cleared by hardware. +// ---------------------------------------------------------------------------- + +#define RTC_ANCHOR_MAGIC UINT64_C(0xB00710E700C10C1F) + +struct rtc_anchor { + uint64_t magic; + int64_t unix_sec; // Unix seconds recorded at last SNTP sync + uint64_t ticks; // Counter ticks at that moment +}; + +// RTC_NOINIT_ATTR: placed in RTC slow RAM (.rtc_noinit section). +// Survives soft resets; cleared only on power-on reset. +static RTC_NOINIT_ATTR struct rtc_anchor s_rtc_anchor; + +#if DT_NODE_HAS_STATUS(DT_NODELABEL(rtc_timer), okay) +static const struct device* const s_rtc_counter_dev = + DEVICE_DT_GET(DT_NODELABEL(rtc_timer)); +#endif + +// WiFi connection state – written by net_mgmt callback, read by the thread +static volatile bool s_wifi_connected = false; + +// Semaphore given each time WiFi connects; wakes the sync thread. +// Max count 1 so rapid connect/disconnect events collapse to one wakeup. +static K_SEM_DEFINE(s_wifi_connected_sem, 0, 1); + +// ---------------------------------------------------------------------------- +// WiFi event handler +// ---------------------------------------------------------------------------- + +static struct net_mgmt_event_callback s_wifi_mgmt_cb; + +static void prv_wifi_event_handler(struct net_mgmt_event_callback* cb, + uint64_t mgmt_event, struct net_if* iface) { + switch (mgmt_event) { + case NET_EVENT_WIFI_CONNECT_RESULT: { + const struct wifi_status* status = (const struct wifi_status*)cb->info; + if (status->status == 0) { + s_wifi_connected = true; + k_sem_give(&s_wifi_connected_sem); + } + break; + } + case NET_EVENT_WIFI_DISCONNECT_RESULT: + s_wifi_connected = false; + break; + default: + break; + } +} + +// ---------------------------------------------------------------------------- +// SNTP query helper +// ---------------------------------------------------------------------------- + +static int prv_sntp_sync(void) { + // Resolve the NTP server hostname to an IPv4 address + struct zsock_addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_DGRAM, + }; + struct zsock_addrinfo* res = NULL; + + int rc = zsock_getaddrinfo(CONFIG_SNTP_TIME_SYNC_SERVER, "123", &hints, &res); + if (rc != 0 || res == NULL) { + LOG_ERR("DNS lookup for '%s' failed: %d", CONFIG_SNTP_TIME_SYNC_SERVER, rc); + return -EHOSTUNREACH; + } + + struct sntp_ctx ctx; + rc = sntp_init(&ctx, res->ai_addr, res->ai_addrlen); + zsock_freeaddrinfo(res); + if (rc < 0) { + LOG_ERR("sntp_init failed: %d", rc); + return rc; + } + + struct sntp_time sntp_time; + rc = sntp_query(&ctx, 4 * MSEC_PER_SEC, &sntp_time); + sntp_close(&ctx); + + if (rc < 0) { + LOG_WRN("SNTP query failed: %d", rc); + return rc; + } + + // If the system clock is already valid (> Jan 1 2023), record the delta + // between the current kernel time and the SNTP-provided time. We exclude + // the initial sync from epoch so the metric only reflects genuine drift. +#define UNIX_JAN_2023 1672531200LL + struct timespec ts_before; + if (sys_clock_gettime(SYS_CLOCK_REALTIME, &ts_before) == 0 && + ts_before.tv_sec > UNIX_JAN_2023) { + s_last_sync_delta_s = + (int32_t)((int64_t)sntp_time.seconds - (int64_t)ts_before.tv_sec); + s_sync_delta_valid = true; + LOG_DBG("SNTP clock delta: %ds", s_last_sync_delta_s); + } + + // Set the kernel real-time clock + struct timespec ts = { + .tv_sec = (time_t)sntp_time.seconds, + .tv_nsec = 0, + }; + int set_rc = sys_clock_settime(SYS_CLOCK_REALTIME, &ts); + if (set_rc != 0) { + LOG_WRN("sys_clock_settime failed: %d", set_rc); + } + +#if DT_NODE_HAS_STATUS(DT_NODELABEL(rtc_timer), okay) + // Save a (unix_sec, ticks) anchor so time survives a soft reset. + // counter_get_value_64 gives full 64-bit precision, avoiding 32-bit + // wraparound. + if (device_is_ready(s_rtc_counter_dev)) { + uint64_t ticks; + if (counter_get_value_64(s_rtc_counter_dev, &ticks) == 0) { + s_rtc_anchor.unix_sec = (int64_t)sntp_time.seconds; + s_rtc_anchor.ticks = ticks; + s_rtc_anchor.magic = RTC_ANCHOR_MAGIC; + LOG_DBG("RTC anchor saved (unix=%lld ticks=%llu)", s_rtc_anchor.unix_sec, + s_rtc_anchor.ticks); + } + } else { + LOG_WRN("RTC counter device not ready"); + } +#endif + + // Log the synced time as human-readable UTC + struct tm utc; + gmtime_r(&ts.tv_sec, &utc); + LOG_INF("SNTP sync OK - UTC: %04d-%02d-%02d %02d:%02d:%02d", + utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday, utc.tm_hour, + utc.tm_min, utc.tm_sec); + + s_sync_count++; + return 0; +} + +// ---------------------------------------------------------------------------- +// Sync thread +// ---------------------------------------------------------------------------- + +static void prv_sntp_sync_thread(void* a, void* b, void* c) { + ARG_UNUSED(a); + ARG_UNUSED(b); + ARG_UNUSED(c); + + while (1) { + // Block until a WiFi-connect event fires or the re-sync interval elapses + k_sem_take(&s_wifi_connected_sem, + K_SECONDS(CONFIG_SNTP_TIME_SYNC_RESYNC_INTERVAL_SECONDS)); + + if (!s_wifi_connected) { + LOG_DBG("WiFi not connected, skipping SNTP sync"); + continue; + } + + // Brief delay to let DHCP and DNS server assignment settle + k_sleep(K_SECONDS(2)); + + prv_sntp_sync(); + } +} + +#define SNTP_THREAD_STACK_SIZE 2048 +#define SNTP_THREAD_PRIORITY 6 + +K_THREAD_DEFINE(sntp_sync_thread, SNTP_THREAD_STACK_SIZE, prv_sntp_sync_thread, + NULL, NULL, NULL, SNTP_THREAD_PRIORITY, 0, 0); + +// ---------------------------------------------------------------------------- +// Metrics collection helper – called from heartbeat_metrics.c +// ---------------------------------------------------------------------------- + +void sntp_time_sync_metrics_collect(void) { + int rc = memfault_metrics_heartbeat_add(MEMFAULT_METRICS_KEY(sntp_sync_count), + s_sync_count); + if (rc != 0) { + LOG_ERR("Failed to add sntp_sync_count metric: %d", rc); + } else { + LOG_DBG("sntp_sync_count += %u", s_sync_count); + } + s_sync_count = 0; + + if (s_sync_delta_valid) { + rc = memfault_metrics_heartbeat_set_signed( + MEMFAULT_METRICS_KEY(sntp_sync_delta_seconds), s_last_sync_delta_s); + if (rc != 0) { + LOG_ERR("Failed to set sntp_sync_delta_seconds metric: %d", rc); + } else { + LOG_DBG("sntp_sync_delta_seconds = %d", s_last_sync_delta_s); + } + s_sync_delta_valid = false; + } +} + +// ---------------------------------------------------------------------------- +// Initialisation +// ---------------------------------------------------------------------------- + +static int sntp_time_sync_init(void) { + LOG_INF("SNTP time sync initialized (server: %s, resync interval: %ds)", + CONFIG_SNTP_TIME_SYNC_SERVER, + CONFIG_SNTP_TIME_SYNC_RESYNC_INTERVAL_SECONDS); + +#if DT_NODE_HAS_STATUS(DT_NODELABEL(rtc_timer), okay) + // Restore time from the RTC slow RAM anchor if the magic is intact (meaning + // this is a soft reset, not a power-on reset). Read the current counter + // value, compute elapsed seconds using counter_get_frequency, and set the + // kernel real-time clock so log timestamps are correct before SNTP first + // fires. + if (device_is_ready(s_rtc_counter_dev) && + s_rtc_anchor.magic == RTC_ANCHOR_MAGIC) { + uint64_t ticks_now; + if (counter_get_value_64(s_rtc_counter_dev, &ticks_now) == 0) { + uint32_t freq = counter_get_frequency(s_rtc_counter_dev); + int64_t elapsed_sec = + (int64_t)((ticks_now - s_rtc_anchor.ticks) / (uint64_t)freq); + int64_t current_unix = s_rtc_anchor.unix_sec + elapsed_sec; + // Sanity: reject anything before Jan 1 2020 + if (current_unix > 1577836800LL) { + struct timespec ts = {.tv_sec = (time_t)current_unix, .tv_nsec = 0}; + int rc = sys_clock_settime(SYS_CLOCK_REALTIME, &ts); + if (rc == 0) { + struct tm utc; + gmtime_r(&ts.tv_sec, &utc); + LOG_INF( + "Restored time from RTC: %04d-%02d-%02d %02d:%02d:%02d UTC " + "(elapsed %llds)", + utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday, utc.tm_hour, + utc.tm_min, utc.tm_sec, (long long)elapsed_sec); + } else { + LOG_WRN("sys_clock_settime from RTC anchor failed: %d", rc); + } + } else { + LOG_DBG("RTC anchor time implausible - waiting for SNTP"); + s_rtc_anchor.magic = 0; // invalidate so we don't use it again + } + } + } +#endif + + net_mgmt_init_event_callback( + &s_wifi_mgmt_cb, prv_wifi_event_handler, + NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT); + net_mgmt_add_event_callback(&s_wifi_mgmt_cb); + + return 0; +} + +SYS_INIT(sntp_time_sync_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +#endif /* CONFIG_SNTP_TIME_SYNC_ENABLE */ diff --git a/src/wifi_watchdog.c b/src/wifi_watchdog.c new file mode 100644 index 0000000..39d452d --- /dev/null +++ b/src/wifi_watchdog.c @@ -0,0 +1,271 @@ +//! @file +//! +//! @brief WiFi watchdog module that monitors WiFi connectivity and reboots +//! if a known network is available but not connected. + +#include +#include +#include +#include +#include +#include + +#include "memfault/components.h" + +LOG_MODULE_REGISTER(wifi_watchdog, LOG_LEVEL_DBG); + +#if defined(CONFIG_WIFI_WATCHDOG_ENABLE) + +// Track the maximum number of WiFi scan results per heartbeat +static uint32_t s_scan_result_count_max = 0; +static uint32_t s_last_scan_result_count = 0; + +// WiFi state tracking +static bool s_wifi_connected = false; +static bool s_wifi_scanning = false; +static uint64_t s_last_wifi_ok_timestamp_ms = 0; + +// Scan result buffer - store SSIDs of APs seen during the most recent scan +#define MAX_SCAN_RESULTS 32 +static char s_scanned_ssids[MAX_SCAN_RESULTS][WIFI_SSID_MAX_LEN + 1]; +static uint8_t s_scanned_ssid_lens[MAX_SCAN_RESULTS]; +static uint8_t s_scan_result_count = 0; + +// Network management event handler +static struct net_mgmt_event_callback s_wifi_mgmt_cb; +static struct k_work s_scan_work; + +// Forward declarations +static void prv_wifi_scan_work_handler(struct k_work* work); + +// Callback context for checking if any stored credential matches scan results +typedef struct { + bool found; +} sCredScanMatchCtx; + +static void prv_check_cred_ssid_cb(void* cb_arg, const char* ssid, + size_t ssid_len) { + sCredScanMatchCtx* ctx = (sCredScanMatchCtx*)cb_arg; + if (ctx->found) { + return; // Already found a match + } + for (int i = 0; i < s_scan_result_count; i++) { + if (s_scanned_ssid_lens[i] == ssid_len && + memcmp(s_scanned_ssids[i], ssid, ssid_len) == 0) { + LOG_DBG("Scan result matches stored credential SSID: %.*s", (int)ssid_len, + ssid); + ctx->found = true; + return; + } + } +} + +// Check if scan results contain any network matching stored credentials +static bool prv_scan_contains_known_network(void) { + if (s_scan_result_count == 0) { + return false; + } + sCredScanMatchCtx ctx = {.found = false}; + wifi_credentials_for_each_ssid(prv_check_cred_ssid_cb, &ctx); + return ctx.found; +} + +// Handle a single scan result event (cb->info points to wifi_scan_result) +static void prv_handle_scan_result(struct net_mgmt_event_callback* cb) { + const struct wifi_scan_result* entry = + (const struct wifi_scan_result*)cb->info; + + if (s_scan_result_count < MAX_SCAN_RESULTS) { + size_t ssid_len = MIN(entry->ssid_length, WIFI_SSID_MAX_LEN); + memcpy(s_scanned_ssids[s_scan_result_count], entry->ssid, ssid_len); + s_scanned_ssids[s_scan_result_count][ssid_len] = '\0'; + s_scanned_ssid_lens[s_scan_result_count] = ssid_len; + s_scan_result_count++; + } +} + +// Handle scan-done event: finalize count and update metric +static void prv_handle_scan_done(void) { + s_wifi_scanning = false; + s_last_scan_result_count = s_scan_result_count; + + if (s_scan_result_count > s_scan_result_count_max) { + s_scan_result_count_max = s_scan_result_count; + } + + LOG_DBG("WiFi scan completed, found %d networks (max this heartbeat: %d)", + s_scan_result_count, s_scan_result_count_max); +} + +// WiFi network management event handler +static void prv_wifi_event_handler(struct net_mgmt_event_callback* cb, + uint64_t mgmt_event, struct net_if* iface) { + switch (mgmt_event) { + case NET_EVENT_WIFI_CONNECT_RESULT: { + const struct wifi_status* status = (const struct wifi_status*)cb->info; + if (status->status == 0) { + LOG_INF("WiFi connected"); + s_wifi_connected = true; + s_last_wifi_ok_timestamp_ms = k_uptime_get(); + } else { + LOG_WRN("WiFi connection failed: %d", status->status); + s_wifi_connected = false; + } + break; + } + + case NET_EVENT_WIFI_DISCONNECT_RESULT: { + LOG_INF("WiFi disconnected"); + s_wifi_connected = false; + break; + } + + case NET_EVENT_WIFI_SCAN_RESULT: + prv_handle_scan_result(cb); + break; + + case NET_EVENT_WIFI_SCAN_DONE: + LOG_DBG("WiFi scan done"); + prv_handle_scan_done(); + break; + + default: + break; + } +} + +// Work handler to trigger WiFi scan +static void prv_wifi_scan_work_handler(struct k_work* work) { + struct net_if* iface = net_if_get_wifi_sta(); + if (!iface) { + LOG_ERR("WiFi interface not found"); + return; + } + + if (s_wifi_scanning) { + LOG_DBG("WiFi scan already in progress"); + return; + } + + LOG_DBG("Starting WiFi scan"); + s_wifi_scanning = true; + s_scan_result_count = 0; + const struct wifi_scan_params params = {0}; + int ret = + net_mgmt(NET_REQUEST_WIFI_SCAN, iface, (void*)¶ms, sizeof(params)); + if (ret) { + LOG_ERR("Failed to start WiFi scan: %d", ret); + s_wifi_scanning = false; + } +} + +// WiFi watchdog check thread +static void prv_wifi_watchdog_thread(void* arg1, void* arg2, void* arg3) { + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + // Wait for system to stabilize + k_sleep(K_SECONDS(10)); + + // Initialize timestamp + s_last_wifi_ok_timestamp_ms = k_uptime_get(); + + while (1) { + // Always scan to populate the metric, regardless of connection state + k_work_submit(&s_scan_work); + + // Wait for scan to complete before evaluating results + k_sleep(K_SECONDS(5)); + + // Reset the connected timestamp while WiFi is up + if (s_wifi_connected) { + s_last_wifi_ok_timestamp_ms = k_uptime_get(); + } else { + // Check if we should trigger the watchdog + uint64_t now = k_uptime_get(); + uint64_t time_since_ok = now - s_last_wifi_ok_timestamp_ms; + uint32_t timeout_ms = CONFIG_WIFI_WATCHDOG_TIMEOUT_MINUTES * 60 * 1000; + + LOG_DBG("WiFi not connected for %lld ms (timeout: %u ms)", time_since_ok, + timeout_ms); + + // Try retriggering connection + struct net_if* iface = net_if_get_wifi_sta(); + int rc = net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0); + if (rc) { + LOG_ERR("Error retriggering WiFi connection: %d", rc); + } + + if (time_since_ok > timeout_ms) { + // Reboot only if a stored credential matches a visible AP; + // wifi_credentials_for_each_ssid handles the "creds configured" check. + bool known_network_present = prv_scan_contains_known_network(); + + LOG_WRN("WiFi watchdog timeout: known_network_present=%d", + known_network_present); + + if (known_network_present) { + LOG_ERR("WiFi watchdog triggered - rebooting!"); + + // Trigger Memfault reboot with custom reason + MEMFAULT_REBOOT_MARK_RESET_IMMINENT(kMfltRebootReason_WifiWatchdog); + + // Reboot the system + memfault_platform_reboot(); + } + } + } + + // Sleep for check interval + k_sleep(K_SECONDS(CONFIG_WIFI_WATCHDOG_CHECK_INTERVAL_SECONDS)); + } +} + +// Thread stack and definition +#define WIFI_WATCHDOG_STACK_SIZE 2048 +#define WIFI_WATCHDOG_PRIORITY 5 + +K_THREAD_DEFINE(wifi_watchdog_thread, WIFI_WATCHDOG_STACK_SIZE, + prv_wifi_watchdog_thread, NULL, NULL, NULL, + WIFI_WATCHDOG_PRIORITY, 0, 0); + +// Initialize WiFi watchdog +static int wifi_watchdog_init(void) { + LOG_INF("WiFi watchdog initialized (timeout: %d minutes)", + CONFIG_WIFI_WATCHDOG_TIMEOUT_MINUTES); + + // Initialize work item for WiFi scanning + k_work_init(&s_scan_work, prv_wifi_scan_work_handler); + + // Register WiFi event callbacks + net_mgmt_init_event_callback( + &s_wifi_mgmt_cb, prv_wifi_event_handler, + NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT | + NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE); + net_mgmt_add_event_callback(&s_wifi_mgmt_cb); + + return 0; +} + +// Memfault metrics collection callback +void wifi_watchdog_metrics_collect(void) { + // Set the maximum scan result count for this heartbeat period + int rc = memfault_metrics_heartbeat_set_unsigned( + MEMFAULT_METRICS_KEY(wifi_scan_result_count_max), + s_scan_result_count_max); + + if (rc != 0) { + LOG_ERR("Failed to set wifi_scan_result_count_max metric: %d", rc); + } else { + LOG_DBG("Set wifi_scan_result_count_max metric to %u", + s_scan_result_count_max); + } + + // Reset the max counter for the next heartbeat period + s_scan_result_count_max = 0; +} + +SYS_INIT(wifi_watchdog_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +#endif /* CONFIG_WIFI_WATCHDOG_ENABLE */ diff --git a/west.yml b/west.yml index 55cf205..23800a4 100644 --- a/west.yml +++ b/west.yml @@ -13,4 +13,4 @@ manifest: - name: memfault-firmware-sdk url: https://github.com/memfault/memfault-firmware-sdk path: modules/lib/memfault-firmware-sdk - revision: 1.35.0 + revision: 1.35.0+remove-posix-api-dep # 1.35.0