29#include "freertos/FreeRTOS.h"
30#include "freertos/event_groups.h"
34#include "esp_netif_sntp.h"
35#include "esp_app_desc.h"
40static const char *
const TAG =
"net";
42#define WIFI_CONNECTED_BIT BIT0
43#define WIFI_FAIL_BIT BIT1
46#define WIFI_MAX_RETRY 2
50#define WIFI_TIMEOUT_MS 15000
56#define REJOIN_FIRST_S 2U
57#define REJOIN_MAX_S 30U
94 ESP_LOGI(
TAG,
"WiFi re-join attempt");
95 (void)esp_wifi_connect();
110 static_cast<uint64_t
>(
s_rejoin_s) * 1000000ULL) == ESP_OK) {
111 ESP_LOGW(
TAG,
"WiFi down - re-join in %u s",
static_cast<unsigned>(
s_rejoin_s));
136 int32_t event_id,
void *event_data)
141 if ((event_base == WIFI_EVENT) && (event_id == WIFI_EVENT_STA_START)) {
145 }
else if ((event_base == WIFI_EVENT) &&
146 (event_id == WIFI_EVENT_STA_DISCONNECTED)) {
160 }
else if ((event_base == IP_EVENT) && (event_id == IP_EVENT_STA_GOT_IP)) {
182 ESP_ERROR_CHECK(esp_netif_init());
183 ESP_ERROR_CHECK(esp_event_loop_create_default());
184 (void)esp_netif_create_default_wifi_sta();
186 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
187 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
190 ESP_ERROR_CHECK(esp_event_handler_instance_register(
192 ESP_ERROR_CHECK(esp_event_handler_instance_register(
195 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
196 ESP_ERROR_CHECK(esp_wifi_start());
198 const esp_timer_create_args_t targs = {
201 .dispatch_method = ESP_TIMER_TASK,
202 .name =
"wifi_rejoin",
203 .skip_unhandled_events =
true,
217 if ((ssid == NULL) || (pass == NULL) || (strlen(pass) < 8U)) {
return false; }
221 s_ap_netif = esp_netif_create_default_wifi_ap();
226 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(&cfg),
sizeof(cfg));
227 (void)snprintf(
reinterpret_cast<char *
>(cfg.ap.ssid),
sizeof(cfg.ap.ssid),
229 cfg.ap.ssid_len =
static_cast<uint8_t
>(strlen(
reinterpret_cast<char *
>(cfg.ap.ssid)));
230 (void)snprintf(
reinterpret_cast<char *
>(cfg.ap.password),
231 sizeof(cfg.ap.password),
"%s", pass);
232 cfg.ap.authmode = WIFI_AUTH_WPA2_PSK;
241 cfg.ap.max_connection = 1;
249 if (esp_wifi_set_mode(WIFI_MODE_AP) != ESP_OK) {
return false; }
250 if (esp_wifi_set_config(WIFI_IF_AP, &cfg) != ESP_OK) {
return false; }
259 if (esp_wifi_set_inactive_time(WIFI_IF_AP, 120) != ESP_OK) {
260 ESP_LOGW(
TAG,
"AP idle timeout unchanged - a dropped phone may hold the "
261 "only slot for 5 min");
264 ESP_LOGI(
TAG,
"SoftAP '%s' up on 192.168.4.1 (AP-only, station down)", ssid);
278 (void)esp_wifi_disconnect();
282 if (
s_ap_up) { (void)esp_wifi_set_mode(WIFI_MODE_AP); }
283 ESP_LOGI(
TAG,
"station association dropped");
290 (void)esp_wifi_set_mode(WIFI_MODE_STA);
291 ESP_LOGI(
TAG,
"SoftAP down");
298 wifi_ap_record_t joined;
299 if (esp_wifi_sta_get_ap_info(&joined) == ESP_OK) {
return; }
302 if ((esp_wifi_get_config(WIFI_IF_STA, &cfg) == ESP_OK) &&
303 (cfg.sta.ssid[0] !=
'\0')) {
306 const esp_err_t err = esp_wifi_connect();
308 ESP_LOGI(
TAG,
"re-joining '%s'",
309 reinterpret_cast<const char *
>(cfg.sta.ssid));
313 ESP_LOGE(
TAG,
"could not re-join '%s' (%s) - restart the terminal",
314 reinterpret_cast<const char *
>(cfg.sta.ssid),
315 esp_err_to_name(err));
319 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(&cfg),
sizeof(cfg));
326 if ((out == NULL) || (max == 0U)) {
return 0U; }
328 wifi_scan_config_t scan_cfg;
329 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(&scan_cfg),
sizeof(scan_cfg));
336 if (borrowed) { (void)esp_wifi_set_mode(WIFI_MODE_APSTA); }
339 wifi_ap_record_t *recs = NULL;
340 if (esp_wifi_scan_start(&scan_cfg,
true) == ESP_OK) {
341 (void)esp_wifi_scan_get_ap_num(&num);
343 recs =
static_cast<wifi_ap_record_t *
>(
344 malloc(num *
sizeof(wifi_ap_record_t)));
346 (void)esp_wifi_scan_get_ap_records(&num, recs);
351 if (borrowed) { (void)esp_wifi_set_mode(WIFI_MODE_AP); }
352 if (recs == NULL) {
return 0U; }
355 for (uint16_t i = 0U; (i < num) && (count < max); i++) {
356 const char *ssid =
reinterpret_cast<const char *
>(recs[i].ssid);
357 if (ssid[0] ==
'\0') {
continue; }
360 for (uint16_t j = 0U; j < count; j++) {
361 if (strcmp(out[j].ssid, ssid) == 0) { dup =
true;
break; }
363 if (dup) {
continue; }
365 (void)strncpy(out[count].ssid, ssid,
sizeof(out[count].ssid) - 1U);
366 out[count].
ssid[
sizeof(out[count].
ssid) - 1U] =
'\0';
367 out[count].
rssi = recs[i].rssi;
368 out[count].
open = (recs[i].authmode == WIFI_AUTH_OPEN);
387 if (
s_ap_up) { (void)esp_wifi_set_mode(WIFI_MODE_APSTA); }
396 wifi_config_t wifi_cfg;
397 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(&wifi_cfg),
sizeof(wifi_cfg));
398 (void)strncpy(
reinterpret_cast<char *
>(wifi_cfg.sta.ssid), ssid,
sizeof(wifi_cfg.sta.ssid) - 1U);
399 (void)strncpy(
reinterpret_cast<char *
>(wifi_cfg.sta.password), password,
sizeof(wifi_cfg.sta.password) - 1U);
401 wifi_cfg.sta.threshold.authmode =
402 (password[0] ==
'\0') ? WIFI_AUTH_OPEN : WIFI_AUTH_WPA2_PSK;
404 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
407 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(&wifi_cfg),
410 (void)esp_wifi_disconnect();
411 (void)esp_wifi_connect();
413 ESP_LOGI(
TAG,
"Connecting to \"%s\"...", ssid);
423 ESP_LOGI(
TAG,
"WiFi connected");
425 ESP_LOGE(
TAG,
"WiFi connect failed");
450 if (rssi_out == NULL) {
return false; }
452 if (esp_wifi_sta_get_ap_info(&ap) != ESP_OK) {
465#define BUILD_FLOOR_SLACK_S 86400
481 const esp_app_desc_t *desc = esp_app_get_description();
484 if ((desc == NULL) ||
486 ESP_LOGE(
TAG,
"build stamp unparseable - clock lower bound DISABLED");
504 if ((tv != NULL) && (
static_cast<int64_t
>(tv->tv_sec) < floor_epoch)) {
505 ESP_LOGE(
TAG,
"SNTP time %" PRId64
" precedes firmware build floor - "
506 "clock cleared (spoofed NTP?)",
507 static_cast<int64_t
>(tv->tv_sec));
508 const struct timeval zero = { 0, 0 };
509 (void)settimeofday(&zero, NULL);
518 esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(3,
519 ESP_SNTP_SERVER_LIST(
"time.google.com",
"pool.ntp.org",
"time.cloudflare.com"));
521 esp_err_t err = esp_netif_sntp_init(&config);
523 ESP_LOGE(
TAG,
"SNTP init: %s", esp_err_to_name(err));
544 esp_netif_sntp_deinit();
551 const int attempts = 3;
552 uint32_t per_wait = timeout_ms /
static_cast<uint32_t
>(attempts);
553 if (per_wait < 2000U) { per_wait = 2000U; }
555 for (
int i = 0; (i < attempts) && !synced; i++) {
556 if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(per_wait)) == ESP_OK) {
559 ESP_LOGW(
TAG,
"SNTP: no time yet (%d/%d)", i + 1, attempts);
569 ESP_LOGE(
TAG,
"SNTP sync timed out");
577 const int64_t now_epoch =
static_cast<int64_t
>(time(NULL));
578 if (now_epoch < floor_epoch) {
579 ESP_LOGE(
TAG,
"SNTP time %" PRId64
" precedes firmware build floor %"
580 PRId64
" - refusing (spoofed NTP?)",
581 now_epoch, floor_epoch);
586 esp_netif_sntp_deinit();
591 ESP_LOGI(
TAG,
"System time synced via SNTP (epoch %" PRId64
")", now_epoch);
bool civil_parse_build_stamp(const char *date, const char *time_str, int64_t *out)
Parse the compiler's DATE / TIME pair into an epoch value.
TZ-independent calendar arithmetic and date-string parsing.
static const char *const TAG
bool net_ap_start(const char *ssid, const char *pass)
Raise a WPA2 SoftAP as the radio's only interface, for phone-based configuration.
static uint32_t s_rejoin_s
void net_wifi_disconnect(void)
Drop the station association, without the retry loop pulling it back.
void net_time_background(void)
Subscribe SNTP without waiting, so the clock is set whenever the network comes back.
static esp_timer_handle_t s_rejoin_timer
static bool s_wifi_inited
void net_wifi_keep_trying(void)
Hold on to the network last configured, even though it is down now.
bool net_time_sync(uint32_t timeout_ms)
Block until the system clock has been set via SNTP and sanity-checked.
void net_ap_stop(void)
Drop the SoftAP, return the radio to station-only, and re-join.
static void rejoin_schedule(void)
Schedule the next re-join, doubling the wait up to REJOIN_MAX_S.
static EventGroupHandle_t s_wifi_event_group
bool net_wifi_rssi(int8_t *rssi_out)
Read the RSSI of the currently associated access point.
static bool sntp_start(void)
Subscribe the SNTP client, without waiting for it.
bool net_wifi_online(void)
true while the station holds an IP address.
bool net_wifi_connect(const char *ssid, const char *password)
Connect to a WiFi network and block until an IP is obtained (up to 30 s). May be called repeatedly to...
uint16_t net_wifi_scan(net_wifi_ap_t *out, uint16_t max)
Scan for nearby access points (blocking).
static esp_netif_t * s_ap_netif
void net_wifi_init(void)
Bring up the WiFi driver in station mode (idempotent).
bool net_wifi_reconnecting(void)
true while a kept network is down and the re-join is running.
static bool s_sntp_running
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
WiFi/IP event handler driving the connect retry state machine.
static void rejoin_cb(void *arg)
Background re-join: one association attempt, from the timer task.
#define BUILD_FLOOR_SLACK_S
#define WIFI_CONNECTED_BIT
static int64_t build_time_floor(void)
Earliest system time this firmware will accept, as a Unix epoch.
static void rejoin_cancel(void)
Stop the background re-join and reset its backoff.
static void on_time_sync(struct timeval *tv)
Every SNTP update, foreground or background, lands here first.
static volatile bool s_ap_up
static volatile bool s_keep
Network bring-up: Wi-Fi station (init/scan/connect/RSSI) and SNTP time sync. No application-protocol ...
A scanned access point (subset of fields the UI needs).
Task-watchdog feed that is safe to call from any task.
static void wdt_feed(void)
Feed the task watchdog if, and only if, the calling task is subscribed.