Skip to content

Commit 8f9293b

Browse files
committed
Add Wi-Fi connection watchdog
Periodically check if the Wi-Fi connection is down, but should be up: - run a scan - if an SSID is in scan results and is in stored Wi-Fi credentials, and Wi-Fi is not connected, start a watchdog timeout - if the timeout elapses, reboot with a custom reboot reason (in the "expected" classification, so this does not impact stability score) Also add a new custom metric `wifi_scan_result_count_max`, just for curiousity.
1 parent 991f80c commit 8f9293b

6 files changed

Lines changed: 301 additions & 4 deletions

File tree

Kconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@ config MEMFAULT_APP_OVERRIDE_SOFTWARE_VERSION
1010
Set this to override the built-in software version string from the
1111
VERSION file. Primarily used for building in CI.
1212

13+
config WIFI_WATCHDOG_ENABLE
14+
bool "Enable WiFi watchdog"
15+
default y
16+
depends on WIFI && MEMFAULT
17+
help
18+
Enable WiFi watchdog that monitors WiFi connectivity and reboots
19+
if a known network is available but not connected for a configurable
20+
period of time.
21+
22+
config WIFI_WATCHDOG_TIMEOUT_MINUTES
23+
int "WiFi watchdog timeout in minutes"
24+
default 5
25+
depends on WIFI_WATCHDOG_ENABLE
26+
help
27+
Time in minutes to wait before triggering a reboot when WiFi
28+
credentials are configured, a known network is detected, but
29+
WiFi is not connected.
30+
31+
config WIFI_WATCHDOG_CHECK_INTERVAL_SECONDS
32+
int "WiFi watchdog check interval in seconds"
33+
default 30
34+
depends on WIFI_WATCHDOG_ENABLE
35+
help
36+
Interval in seconds between WiFi watchdog checks.
37+
1338
endmenu
1439

1540
config MEMFAULT_BUILTIN_DEVICE_INFO_SOFTWARE_VERSION
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Define the WiFi scan result count max metric
2+
// This tracks the maximum number of WiFi scan results seen during each heartbeat period
3+
MEMFAULT_METRICS_KEY_DEFINE(wifi_scan_result_count_max, kMemfaultMetricType_Unsigned)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Wi-Fi Watchdog is not desirable, but should not impact stability score.
2+
MEMFAULT_EXPECTED_REBOOT_REASON_DEFINE(WifiWatchdog)

prj.conf

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,14 @@ CONFIG_MEMFAULT=y
5353
CONFIG_MEMFAULT_LOGGING_ENABLE=y
5454
CONFIG_MEMFAULT_HTTP_ENABLE=y
5555
CONFIG_MEMFAULT_HTTP_PERIODIC_UPLOAD=y
56+
CONFIG_MEMFAULT_REBOOT_REASON_CUSTOM_ENABLE=y
57+
CONFIG_MEMFAULT_PERIODIC_FOTA_CHECK=y
5658

5759
# Additional network configuration for Memfault features
5860
CONFIG_DNS_RESOLVER=y
59-
CONFIG_POSIX_API=y
61+
# Increase DNS server max count to 2, for some networks that have a primary and
62+
# secondary DNS server
63+
CONFIG_DNS_RESOLVER_MAX_SERVERS=2
6064
CONFIG_NET_SOCKETS=y
6165
CONFIG_TLS_CREDENTIALS=y
6266
CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
@@ -89,8 +93,6 @@ CONFIG_MEMFAULT_LOG_LEVEL_DBG=y
8993
# Don't log printk() statements, they're meant for raw output to the console.
9094
CONFIG_LOG_PRINTK=n
9195

92-
CONFIG_LOG_MODE_IMMEDIATE=y
93-
9496
# Disable these temporarily, for debugging.
9597
CONFIG_MEMFAULT_COREDUMP_COLLECT_DATA_REGIONS=y
9698
CONFIG_MEMFAULT_COREDUMP_COLLECT_KERNEL_REGION=n

src/main.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ int main(void) {
5353

5454
memfault_device_info_dump();
5555
memfault_zephyr_port_install_root_certs();
56+
5657
// Initiate auto connection on the wifi interface
57-
struct net_if* iface = net_if_get_default();
58+
struct net_if* iface = net_if_get_wifi_sta(); // net_if_get_default();
5859
int rc = net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0);
5960

6061
if (rc) {

src/wifi_watchdog.c

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
//! @file
2+
//!
3+
//! @brief WiFi watchdog module that monitors WiFi connectivity and reboots
4+
//! if a known network is available but not connected.
5+
6+
#include <zephyr/kernel.h>
7+
#include <zephyr/logging/log.h>
8+
#include <zephyr/net/net_if.h>
9+
#include <zephyr/net/net_mgmt.h>
10+
#include <zephyr/net/wifi_credentials.h>
11+
#include <zephyr/net/wifi_mgmt.h>
12+
13+
#include "memfault/components.h"
14+
15+
LOG_MODULE_REGISTER(wifi_watchdog, LOG_LEVEL_DBG);
16+
17+
#if defined(CONFIG_WIFI_WATCHDOG_ENABLE)
18+
19+
// Track the maximum number of WiFi scan results per heartbeat
20+
static uint32_t s_scan_result_count_max = 0;
21+
static uint32_t s_last_scan_result_count = 0;
22+
23+
// WiFi state tracking
24+
static bool s_wifi_connected = false;
25+
static bool s_wifi_scanning = false;
26+
static uint64_t s_last_wifi_ok_timestamp_ms = 0;
27+
28+
// Scan result buffer - store SSIDs of APs seen during the most recent scan
29+
#define MAX_SCAN_RESULTS 32
30+
static char s_scanned_ssids[MAX_SCAN_RESULTS][WIFI_SSID_MAX_LEN + 1];
31+
static uint8_t s_scanned_ssid_lens[MAX_SCAN_RESULTS];
32+
static uint8_t s_scan_result_count = 0;
33+
34+
// Network management event handler
35+
static struct net_mgmt_event_callback s_wifi_mgmt_cb;
36+
static struct k_work s_scan_work;
37+
38+
// Forward declarations
39+
static void prv_wifi_scan_work_handler(struct k_work* work);
40+
41+
// Callback context for checking if any stored credential matches scan results
42+
typedef struct {
43+
bool found;
44+
} sCredScanMatchCtx;
45+
46+
static void prv_check_cred_ssid_cb(void* cb_arg, const char* ssid,
47+
size_t ssid_len) {
48+
sCredScanMatchCtx* ctx = (sCredScanMatchCtx*)cb_arg;
49+
if (ctx->found) {
50+
return; // Already found a match
51+
}
52+
for (int i = 0; i < s_scan_result_count; i++) {
53+
if (s_scanned_ssid_lens[i] == ssid_len &&
54+
memcmp(s_scanned_ssids[i], ssid, ssid_len) == 0) {
55+
LOG_DBG("Scan result matches stored credential SSID: %.*s", (int)ssid_len,
56+
ssid);
57+
ctx->found = true;
58+
return;
59+
}
60+
}
61+
}
62+
63+
// Check if scan results contain any network matching stored credentials
64+
static bool prv_scan_contains_known_network(void) {
65+
if (s_scan_result_count == 0) {
66+
return false;
67+
}
68+
sCredScanMatchCtx ctx = {.found = false};
69+
wifi_credentials_for_each_ssid(prv_check_cred_ssid_cb, &ctx);
70+
return ctx.found;
71+
}
72+
73+
// Handle a single scan result event (cb->info points to wifi_scan_result)
74+
static void prv_handle_scan_result(struct net_mgmt_event_callback* cb) {
75+
const struct wifi_scan_result* entry =
76+
(const struct wifi_scan_result*)cb->info;
77+
78+
if (s_scan_result_count < MAX_SCAN_RESULTS) {
79+
size_t ssid_len = MIN(entry->ssid_length, WIFI_SSID_MAX_LEN);
80+
memcpy(s_scanned_ssids[s_scan_result_count], entry->ssid, ssid_len);
81+
s_scanned_ssids[s_scan_result_count][ssid_len] = '\0';
82+
s_scanned_ssid_lens[s_scan_result_count] = ssid_len;
83+
s_scan_result_count++;
84+
}
85+
}
86+
87+
// Handle scan-done event: finalize count and update metric
88+
static void prv_handle_scan_done(void) {
89+
s_wifi_scanning = false;
90+
s_last_scan_result_count = s_scan_result_count;
91+
92+
if (s_scan_result_count > s_scan_result_count_max) {
93+
s_scan_result_count_max = s_scan_result_count;
94+
}
95+
96+
LOG_DBG("WiFi scan completed, found %d networks (max this heartbeat: %d)",
97+
s_scan_result_count, s_scan_result_count_max);
98+
}
99+
100+
// WiFi network management event handler
101+
static void prv_wifi_event_handler(struct net_mgmt_event_callback* cb,
102+
uint64_t mgmt_event, struct net_if* iface) {
103+
switch (mgmt_event) {
104+
case NET_EVENT_WIFI_CONNECT_RESULT: {
105+
const struct wifi_status* status = (const struct wifi_status*)cb->info;
106+
if (status->status == 0) {
107+
LOG_INF("WiFi connected");
108+
s_wifi_connected = true;
109+
s_last_wifi_ok_timestamp_ms = k_uptime_get();
110+
} else {
111+
LOG_WRN("WiFi connection failed: %d", status->status);
112+
s_wifi_connected = false;
113+
}
114+
break;
115+
}
116+
117+
case NET_EVENT_WIFI_DISCONNECT_RESULT: {
118+
LOG_INF("WiFi disconnected");
119+
s_wifi_connected = false;
120+
break;
121+
}
122+
123+
case NET_EVENT_WIFI_SCAN_RESULT:
124+
prv_handle_scan_result(cb);
125+
break;
126+
127+
case NET_EVENT_WIFI_SCAN_DONE:
128+
LOG_DBG("WiFi scan done");
129+
prv_handle_scan_done();
130+
break;
131+
132+
default:
133+
break;
134+
}
135+
}
136+
137+
// Work handler to trigger WiFi scan
138+
static void prv_wifi_scan_work_handler(struct k_work* work) {
139+
struct net_if* iface = net_if_get_wifi_sta();
140+
if (!iface) {
141+
LOG_ERR("WiFi interface not found");
142+
return;
143+
}
144+
145+
if (s_wifi_scanning) {
146+
LOG_DBG("WiFi scan already in progress");
147+
return;
148+
}
149+
150+
LOG_DBG("Starting WiFi scan");
151+
s_wifi_scanning = true;
152+
s_scan_result_count = 0;
153+
const struct wifi_scan_params params = {0};
154+
int ret =
155+
net_mgmt(NET_REQUEST_WIFI_SCAN, iface, (void*)&params, sizeof(params));
156+
if (ret) {
157+
LOG_ERR("Failed to start WiFi scan: %d", ret);
158+
s_wifi_scanning = false;
159+
}
160+
}
161+
162+
// WiFi watchdog check thread
163+
static void prv_wifi_watchdog_thread(void* arg1, void* arg2, void* arg3) {
164+
ARG_UNUSED(arg1);
165+
ARG_UNUSED(arg2);
166+
ARG_UNUSED(arg3);
167+
168+
// Wait for system to stabilize
169+
k_sleep(K_SECONDS(10));
170+
171+
// Initialize timestamp
172+
s_last_wifi_ok_timestamp_ms = k_uptime_get();
173+
174+
while (1) {
175+
// Always scan to populate the metric, regardless of connection state
176+
k_work_submit(&s_scan_work);
177+
178+
// Wait for scan to complete before evaluating results
179+
k_sleep(K_SECONDS(5));
180+
181+
// Reset the connected timestamp while WiFi is up
182+
if (s_wifi_connected) {
183+
s_last_wifi_ok_timestamp_ms = k_uptime_get();
184+
} else {
185+
// Check if we should trigger the watchdog
186+
uint64_t now = k_uptime_get();
187+
uint64_t time_since_ok = now - s_last_wifi_ok_timestamp_ms;
188+
uint32_t timeout_ms = CONFIG_WIFI_WATCHDOG_TIMEOUT_MINUTES * 60 * 1000;
189+
190+
LOG_DBG("WiFi not connected for %lld ms (timeout: %u ms)", time_since_ok,
191+
timeout_ms);
192+
193+
if (time_since_ok > timeout_ms) {
194+
// Reboot only if a stored credential matches a visible AP;
195+
// wifi_credentials_for_each_ssid handles the "creds configured" check.
196+
bool known_network_present = prv_scan_contains_known_network();
197+
198+
LOG_WRN("WiFi watchdog timeout: known_network_present=%d",
199+
known_network_present);
200+
201+
if (known_network_present) {
202+
LOG_ERR("WiFi watchdog triggered - rebooting!");
203+
204+
// Trigger Memfault reboot with custom reason
205+
MEMFAULT_REBOOT_MARK_RESET_IMMINENT(kMfltRebootReason_WifiWatchdog);
206+
207+
// Reboot the system
208+
memfault_platform_reboot();
209+
}
210+
}
211+
}
212+
213+
// Sleep for check interval
214+
k_sleep(K_SECONDS(CONFIG_WIFI_WATCHDOG_CHECK_INTERVAL_SECONDS));
215+
}
216+
}
217+
218+
// Thread stack and definition
219+
#define WIFI_WATCHDOG_STACK_SIZE 2048
220+
#define WIFI_WATCHDOG_PRIORITY 5
221+
222+
K_THREAD_DEFINE(wifi_watchdog_thread, WIFI_WATCHDOG_STACK_SIZE,
223+
prv_wifi_watchdog_thread, NULL, NULL, NULL,
224+
WIFI_WATCHDOG_PRIORITY, 0, 0);
225+
226+
// Initialize WiFi watchdog
227+
static int wifi_watchdog_init(void) {
228+
LOG_INF("WiFi watchdog initialized (timeout: %d minutes)",
229+
CONFIG_WIFI_WATCHDOG_TIMEOUT_MINUTES);
230+
231+
// Initialize work item for WiFi scanning
232+
k_work_init(&s_scan_work, prv_wifi_scan_work_handler);
233+
234+
// Register WiFi event callbacks
235+
net_mgmt_init_event_callback(
236+
&s_wifi_mgmt_cb, prv_wifi_event_handler,
237+
NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT |
238+
NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE);
239+
net_mgmt_add_event_callback(&s_wifi_mgmt_cb);
240+
241+
return 0;
242+
}
243+
244+
// Memfault metrics collection callback
245+
void memfault_metrics_heartbeat_collect_data(void) {
246+
// Set the maximum scan result count for this heartbeat period
247+
int rc = memfault_metrics_heartbeat_set_unsigned(
248+
MEMFAULT_METRICS_KEY(wifi_scan_result_count_max),
249+
s_scan_result_count_max);
250+
251+
if (rc != 0) {
252+
LOG_ERR("Failed to set wifi_scan_result_count_max metric: %d", rc);
253+
} else {
254+
LOG_DBG("Set wifi_scan_result_count_max metric to %u",
255+
s_scan_result_count_max);
256+
}
257+
258+
// Reset the max counter for the next heartbeat period
259+
s_scan_result_count_max = 0;
260+
}
261+
262+
SYS_INIT(wifi_watchdog_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
263+
264+
#endif /* CONFIG_WIFI_WATCHDOG_ENABLE */

0 commit comments

Comments
 (0)