cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
net.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 * Copyright (c) 2026 Cryptnox SA
4 */
5
10
11/******************************************************************
12 * 1. Included files
13 ******************************************************************/
14
15#include "net.h"
16#include "civil_time.h"
17
18#include <string.h>
19#include <stdlib.h> /* malloc, free */
20#include <time.h> /* time */
21#include <sys/time.h> /* settimeofday — clearing a back-dated clock */
22#include <inttypes.h> /* PRId64 */
23
24/* CW_Utils.h pulls in Arduino.h (via platform_compat.h); it must come before
25 * any lwip-including IDF header (esp_netif.h, ...) so that IPAddress.h
26 * declares INADDR_NONE before lwip defines it as a macro. */
27#include "CW_Utils.h" /* hardened memory primitives (CODING_RULES §1.4) */
28
29#include "freertos/FreeRTOS.h"
30#include "freertos/event_groups.h"
31#include "esp_wifi.h"
32#include "esp_event.h"
33#include "esp_netif.h"
34#include "esp_netif_sntp.h"
35#include "esp_app_desc.h" /* esp_app_get_description — build timestamp */
36#include "esp_timer.h" /* the background re-join */
37#include "wdt.h" /* wdt_feed() — callers on the task watchdog block here */
38#include "esp_log.h"
39
40static const char *const TAG = "net";
41
42#define WIFI_CONNECTED_BIT BIT0
43#define WIFI_FAIL_BIT BIT1
44/* Retries inside one connect call. Kept low: main.cpp retries the whole call
45 * (WIFI_SAVED_ATTEMPTS), and each of those resets the association properly. */
46#define WIFI_MAX_RETRY 2
47
48/* Way out when the association succeeds but no IP ever arrives (hung DHCP):
49 * the FAIL bit is never set, so only this timeout ends the call. */
50#define WIFI_TIMEOUT_MS 15000
51
52/* Background re-join once a kept association drops and the immediate retries
53 * are spent: 2, 4, 8, 16 s, then every 30 s for as long as it takes. A router
54 * that reboots mid-shift is back in a minute or two, and the terminal has to be
55 * back on it without anybody power-cycling it. */
56#define REJOIN_FIRST_S 2U
57#define REJOIN_MAX_S 30U
58
59/******************************************************************
60 * 2. Module state
61 ******************************************************************/
62
63static EventGroupHandle_t s_wifi_event_group = NULL;
64static int s_retry_num = 0;
65static bool s_wifi_inited = false;
66static bool s_sntp_running = false;
67
68/* Whether the station association is one to hold on to. Set by a successful
69 * join (and by net_wifi_keep_trying), cleared by net_wifi_disconnect — which is
70 * also what net_ap_start calls, so the re-join never runs while the portal is
71 * AP-only. Read from the event and timer tasks, hence volatile. */
72static volatile bool s_keep = false;
73static esp_timer_handle_t s_rejoin_timer = NULL;
74static uint32_t s_rejoin_s = 0U; /* next backoff; 0 = first */
75
76/* Whether the SoftAP is currently the radio's only interface. The scan and
77 * connect calls below need the station back for a moment, and this is how they
78 * know to put the radio back to AP-only afterwards — and how the re-join knows
79 * to stay out of the way. */
80static volatile bool s_ap_up = false;
81
82/******************************************************************
83 * 3. WiFi event handler
84 ******************************************************************/
85
87static void rejoin_cb(void *arg)
88{
89 (void)arg;
90 if (!s_keep || s_ap_up) { return; }
91 /* Straight to the ceiling: a failed attempt reschedules through the handler
92 * instead of spending the immediate retries a second time. */
94 ESP_LOGI(TAG, "WiFi re-join attempt");
95 (void)esp_wifi_connect();
96}
97
99static void rejoin_schedule(void)
100{
101 if (!s_keep || s_ap_up || (s_rejoin_timer == NULL)) { return; }
102 if (s_rejoin_s == 0U) {
104 } else {
106 : (s_rejoin_s * 2U);
107 }
108 (void)esp_timer_stop(s_rejoin_timer); /* ESP_ERR_INVALID_STATE if idle */
109 if (esp_timer_start_once(s_rejoin_timer,
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));
112 }
113}
114
116static void rejoin_cancel(void)
117{
118 if (s_rejoin_timer != NULL) { (void)esp_timer_stop(s_rejoin_timer); }
119 s_rejoin_s = 0U;
120}
121
135static void wifi_event_handler(void *arg, esp_event_base_t event_base,
136 int32_t event_id, void *event_data)
137{
138 (void)arg;
139 (void)event_data;
140
141 if ((event_base == WIFI_EVENT) && (event_id == WIFI_EVENT_STA_START)) {
142 /* Started — association is initiated explicitly by
143 * net_wifi_connect(), not here, so a start with no credentials
144 * (e.g. before provisioning) doesn't churn through retries. */
145 } else if ((event_base == WIFI_EVENT) &&
146 (event_id == WIFI_EVENT_STA_DISCONNECTED)) {
147 if (s_wifi_event_group != NULL) {
148 xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
149 }
151 esp_wifi_connect();
152 s_retry_num++;
153 ESP_LOGW(TAG, "WiFi retry %d/%d", s_retry_num, WIFI_MAX_RETRY);
154 } else {
155 if (s_wifi_event_group != NULL) {
156 xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
157 }
158 rejoin_schedule(); /* no-op unless the association is being kept */
159 }
160 } else if ((event_base == IP_EVENT) && (event_id == IP_EVENT_STA_GOT_IP)) {
161 s_retry_num = 0;
163 if (s_wifi_event_group != NULL) {
164 xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
165 }
166 } else {
167 /* other events ignored */
168 }
169}
170
171/******************************************************************
172 * 4. Public API
173 ******************************************************************/
174
176{
177 if (s_wifi_inited) { return; }
178
179 s_wifi_event_group = xEventGroupCreate();
180 s_retry_num = 0;
181
182 ESP_ERROR_CHECK(esp_netif_init());
183 ESP_ERROR_CHECK(esp_event_loop_create_default());
184 (void)esp_netif_create_default_wifi_sta();
185
186 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
187 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
188
189 /* Persistent handlers (never unregistered) so init/connect can repeat. */
190 ESP_ERROR_CHECK(esp_event_handler_instance_register(
191 WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
192 ESP_ERROR_CHECK(esp_event_handler_instance_register(
193 IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
194
195 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
196 ESP_ERROR_CHECK(esp_wifi_start());
197
198 const esp_timer_create_args_t targs = {
199 .callback = &rejoin_cb,
200 .arg = NULL,
201 .dispatch_method = ESP_TIMER_TASK,
202 .name = "wifi_rejoin",
203 .skip_unhandled_events = true,
204 };
205 ESP_ERROR_CHECK(esp_timer_create(&targs, &s_rejoin_timer));
206
207 s_wifi_inited = true;
208}
209
210/* SoftAP netif, created on first net_ap_start() and then kept: destroying and
211 * recreating it across setup steps buys nothing and esp_netif teardown while
212 * lwIP still holds sockets is a known way to crash. */
213static esp_netif_t *s_ap_netif = NULL;
214
215bool net_ap_start(const char *ssid, const char *pass)
216{
217 if ((ssid == NULL) || (pass == NULL) || (strlen(pass) < 8U)) { return false; }
218 net_wifi_init(); /* idempotent — also guarantees esp_wifi_start() has run */
219
220 if (s_ap_netif == NULL) {
221 s_ap_netif = esp_netif_create_default_wifi_ap();
222 if (s_ap_netif == NULL) { return false; }
223 }
224
225 wifi_config_t cfg;
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),
228 "%s", 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;
233 cfg.ap.channel = 1;
234 /* One. WPA2-PSK gives every station the same key, so it does not isolate
235 * them from each other: a second association is a silent seat on the wire,
236 * watching the operator type the venue's Wi-Fi password. At one, an intruder
237 * who got the passphrase off the screen instead takes the operator's seat —
238 * the operator cannot join, notices immediately, and restarts setup with a
239 * new passphrase. Loud beats quiet. A stale association is cleared the same
240 * way; net_ap_stop() and the AP's own inactivity timeout free the slot. */
241 cfg.ap.max_connection = 1;
242
243 /* AP-only, and the station goes down first. httpd binds every interface, so
244 * in APSTA the config portal answers the venue LAN too — the one place its
245 * perimeter (a passphrase on the panel in front of the operator) means
246 * nothing. The scan and connect calls borrow the station back below, and
247 * net_ap_stop() re-associates. */
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; }
251 /* With one slot, a phone that walked away without deauthenticating holds the
252 * only seat until the AP gives up on it, and the 300 s default is five minutes
253 * of "cannot join" in the middle of setup. Not much shorter than this, though:
254 * the auth step deliberately sends the operator from the phone to the panel, a
255 * locked phone stops polling, and deauthing it there is how a browser ends up
256 * back on cellular. Two minutes is longer than any panel interaction and less
257 * than half the lockout. Diagnosable if it fails: silently keeping 300 s with
258 * one slot is the lockout nobody could explain. */
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");
262 }
263 s_ap_up = true;
264 ESP_LOGI(TAG, "SoftAP '%s' up on 192.168.4.1 (AP-only, station down)", ssid);
265 return true;
266}
267
269{
270 if (!s_wifi_inited) { return; }
271 /* Past the retry ceiling first: wifi_event_handler() re-associates on every
272 * STA_DISCONNECTED below it, so a bare esp_wifi_disconnect() would undo
273 * itself three times over. net_wifi_connect() zeroes the counter again, so
274 * nothing after this inherits the ceiling. */
276 s_keep = false; /* ...and the background re-join with it */
278 (void)esp_wifi_disconnect();
279 /* Take the interface down with the association while a portal is up: the
280 * association is what put the config forms on the venue LAN, and leaving an
281 * idle station enabled leaves half of that in place. */
282 if (s_ap_up) { (void)esp_wifi_set_mode(WIFI_MODE_AP); }
283 ESP_LOGI(TAG, "station association dropped");
284}
285
286void net_ap_stop(void)
287{
288 if (s_ap_netif == NULL) { return; }
289 s_ap_up = false;
290 (void)esp_wifi_set_mode(WIFI_MODE_STA);
291 ESP_LOGI(TAG, "SoftAP down");
292
293 /* Put back the association net_ap_start() displaced. The driver still holds
294 * the credentials — only the link went away — so this is a reconnect, not a
295 * reconfigure. Skipped when the station is already up, which is the wizard
296 * case: it joined the venue network through net_wifi_connect() and the portal
297 * comes down in the same breath. */
298 wifi_ap_record_t joined;
299 if (esp_wifi_sta_get_ap_info(&joined) == ESP_OK) { return; }
300
301 wifi_config_t cfg;
302 if ((esp_wifi_get_config(WIFI_IF_STA, &cfg) == ESP_OK) &&
303 (cfg.sta.ssid[0] != '\0')) {
304 s_retry_num = 0; /* net_wifi_disconnect() left it at the ceiling */
305 s_keep = true; /* the terminal's own network: hold on to it */
306 const esp_err_t err = esp_wifi_connect();
307 if (err == ESP_OK) {
308 ESP_LOGI(TAG, "re-joining '%s'",
309 reinterpret_cast<const char *>(cfg.sta.ssid));
310 } else {
311 /* Loud: a terminal that quietly stayed off its network after the
312 * config page closed looks working and fails at the first RPC call. */
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));
316 }
317 }
318 /* The copy carries the passphrase (CODING_RULES §1.4). */
319 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(&cfg), sizeof(cfg));
320}
321
322uint16_t net_wifi_scan(net_wifi_ap_t *out, uint16_t max)
323{
324 wdt_feed(); /* a blocking all-channel scan */
326 if ((out == NULL) || (max == 0U)) { return 0U; }
327
328 wifi_scan_config_t scan_cfg;
329 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(&scan_cfg), sizeof(scan_cfg)); /* all channels, all SSIDs */
330
331 /* Scanning is a station operation, and net_ap_start() switched the station
332 * off. Borrow it for the scan and the readout, then hand the radio straight
333 * back: AP-only is what keeps the portal off the venue LAN, so the window
334 * where it is not AP-only has to be this one call and no longer. */
335 const bool borrowed = s_ap_up;
336 if (borrowed) { (void)esp_wifi_set_mode(WIFI_MODE_APSTA); }
337
338 uint16_t num = 0U;
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);
342 if (num > 0U) {
343 recs = static_cast<wifi_ap_record_t *>(
344 malloc(num * sizeof(wifi_ap_record_t)));
345 if (recs != NULL) {
346 (void)esp_wifi_scan_get_ap_records(&num, recs);
347 }
348 }
349 }
350
351 if (borrowed) { (void)esp_wifi_set_mode(WIFI_MODE_AP); }
352 if (recs == NULL) { return 0U; }
353
354 uint16_t count = 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; } /* hidden network */
358
359 bool dup = false; /* one entry per SSID */
360 for (uint16_t j = 0U; j < count; j++) {
361 if (strcmp(out[j].ssid, ssid) == 0) { dup = true; break; }
362 }
363 if (dup) { continue; }
364
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);
369 count++;
370 }
371
372 free(recs);
373 return count;
374}
375
376bool net_wifi_connect(const char *ssid, const char *password)
377{
378 wdt_feed(); /* up to WIFI_TIMEOUT_MS below */
380
381 /* The station is off while the SoftAP is up (net_ap_start), and joining
382 * obviously needs it. APSTA rather than AP-only for the duration: the phone
383 * that submitted the form is on the AP and has to be told what happened, and
384 * a mid-join mode flip would drop it before it could be. The caller either
385 * keeps the join and stops the portal, or drops the association again —
386 * see UI_EVENT_WIFI_TRY in main.cpp. */
387 if (s_ap_up) { (void)esp_wifi_set_mode(WIFI_MODE_APSTA); }
388
389 /* Not kept while it is being tried: a join that fails must not turn into a
390 * background loop on a network nobody has proven. */
391 s_keep = false;
393 xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
394 s_retry_num = 0;
395
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);
400 /* Open networks have an empty passphrase; otherwise require WPA2+. */
401 wifi_cfg.sta.threshold.authmode =
402 (password[0] == '\0') ? WIFI_AUTH_OPEN : WIFI_AUTH_WPA2_PSK;
403
404 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
405 /* The WiFi driver keeps its own copy — scrub the stack copy of the
406 * credentials immediately (CODING_RULES §1.4). */
407 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(&wifi_cfg),
408 sizeof(wifi_cfg));
409
410 (void)esp_wifi_disconnect(); /* drop any current AP before (re)connecting */
411 (void)esp_wifi_connect();
412
413 ESP_LOGI(TAG, "Connecting to \"%s\"...", ssid);
414
415 EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
417 pdFALSE, pdFALSE,
418 pdMS_TO_TICKS(WIFI_TIMEOUT_MS));
419
420 bool connected = ((bits & WIFI_CONNECTED_BIT) != 0U);
421 if (connected) {
422 s_keep = true; /* drops from here on re-join in the background */
423 ESP_LOGI(TAG, "WiFi connected");
424 } else {
425 ESP_LOGE(TAG, "WiFi connect failed");
426 }
427 return connected;
428}
429
431{
432 if (!s_wifi_inited || s_ap_up) { return; }
433 s_keep = true;
435}
436
438{
439 return (s_wifi_event_group != NULL) &&
440 ((xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) != 0U);
441}
442
444{
445 return s_keep && !net_wifi_online();
446}
447
448bool net_wifi_rssi(int8_t *rssi_out)
449{
450 if (rssi_out == NULL) { return false; }
451 wifi_ap_record_t ap;
452 if (esp_wifi_sta_get_ap_info(&ap) != ESP_OK) {
453 return false; /* not associated */
454 }
455 *rssi_out = ap.rssi;
456 return true;
457}
458
459/* __DATE__/__TIME__ are the build machine's LOCAL time but are read back here
460 * as if UTC, so the derived floor can sit up to ~14 h ahead of the real build
461 * instant. One day of slack absorbs every real-world TZ offset; it costs the
462 * attacker nothing, since reviving an expired certificate needs weeks of
463 * back-dating, not hours.
464 * ponytail: fixed slack, not a TZ database. */
465#define BUILD_FLOOR_SLACK_S 86400
466
479static int64_t build_time_floor(void)
480{
481 const esp_app_desc_t *desc = esp_app_get_description();
482 int64_t stamp = 0;
483
484 if ((desc == NULL) ||
485 !civil_parse_build_stamp(desc->date, desc->time, &stamp)) {
486 ESP_LOGE(TAG, "build stamp unparseable - clock lower bound DISABLED");
487 return 0;
488 }
489 return stamp - BUILD_FLOOR_SLACK_S;
490}
491
501static void on_time_sync(struct timeval *tv)
502{
503 const int64_t floor_epoch = build_time_floor();
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);
510 }
511}
512
514static bool sntp_start(void)
515{
516 /* Several reliable servers — phone hotspots often slow or drop NTP (UDP
517 * 123) to pool.ntp.org, so fall back to Google / Cloudflare time. */
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"));
520 config.sync_cb = on_time_sync;
521 esp_err_t err = esp_netif_sntp_init(&config);
522 if (err != ESP_OK) {
523 ESP_LOGE(TAG, "SNTP init: %s", esp_err_to_name(err));
524 return false;
525 }
526 return true;
527}
528
530{
531 if (s_sntp_running) { return; } /* already subscribed: lwIP is retrying */
533 if (s_sntp_running) { ESP_LOGI(TAG, "SNTP running in the background"); }
534}
535
536bool net_time_sync(uint32_t timeout_ms)
537{
538 /* A successful sync leaves SNTP subscribed, so a second call would fail on
539 * ESP_ERR_INVALID_STATE. Drop it first: every call then waits for a fresh
540 * packet, which is what makes this usable as a per-network probe rather than
541 * a one-shot at boot. */
542 wdt_feed(); /* up to timeout_ms below */
543 if (s_sntp_running) {
544 esp_netif_sntp_deinit();
545 s_sntp_running = false;
546 }
547
548 if (!sntp_start()) { return false; }
549
550 /* Poll across the budget so a slow first response doesn't fail us. */
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; }
554 bool synced = false;
555 for (int i = 0; (i < attempts) && !synced; i++) {
556 if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(per_wait)) == ESP_OK) {
557 synced = true;
558 } else {
559 ESP_LOGW(TAG, "SNTP: no time yet (%d/%d)", i + 1, attempts);
560 }
561 }
562
563 /* Stay subscribed on failure too: lwIP then retries on its own (15 s, backing
564 * off to 150 s) and sets the clock if the network comes back. A deinit here
565 * would leave no client running at all. */
566 s_sntp_running = true;
567
568 if (!synced) {
569 ESP_LOGE(TAG, "SNTP sync timed out");
570 return false;
571 }
572
573 /* SNTP is unauthenticated (plain UDP/123), so anyone on the path can
574 * dictate the time. Reject a clock earlier than our own build: that is
575 * the direction an attacker needs to resurrect an expired certificate. */
576 const int64_t floor_epoch = build_time_floor();
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);
582 /* Unsubscribe here, unlike the timeout path above: a server feeding
583 * back-dated time is not one to leave resyncing us in the background.
584 * Keep the flag honest so the next call re-inits rather than double-
585 * deiniting. */
586 esp_netif_sntp_deinit();
587 s_sntp_running = false;
588 return false;
589 }
590
591 ESP_LOGI(TAG, "System time synced via SNTP (epoch %" PRId64 ")", now_epoch);
592 /* Leave SNTP running for periodic background resyncs. */
593 return true;
594}
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
Definition eth_rpc.cpp:33
bool net_ap_start(const char *ssid, const char *pass)
Raise a WPA2 SoftAP as the radio's only interface, for phone-based configuration.
Definition net.cpp:215
static uint32_t s_rejoin_s
Definition net.cpp:74
void net_wifi_disconnect(void)
Drop the station association, without the retry loop pulling it back.
Definition net.cpp:268
void net_time_background(void)
Subscribe SNTP without waiting, so the clock is set whenever the network comes back.
Definition net.cpp:529
static esp_timer_handle_t s_rejoin_timer
Definition net.cpp:73
#define WIFI_TIMEOUT_MS
Definition net.cpp:50
static bool s_wifi_inited
Definition net.cpp:65
void net_wifi_keep_trying(void)
Hold on to the network last configured, even though it is down now.
Definition net.cpp:430
bool net_time_sync(uint32_t timeout_ms)
Block until the system clock has been set via SNTP and sanity-checked.
Definition net.cpp:536
void net_ap_stop(void)
Drop the SoftAP, return the radio to station-only, and re-join.
Definition net.cpp:286
static void rejoin_schedule(void)
Schedule the next re-join, doubling the wait up to REJOIN_MAX_S.
Definition net.cpp:99
static EventGroupHandle_t s_wifi_event_group
Definition net.cpp:63
bool net_wifi_rssi(int8_t *rssi_out)
Read the RSSI of the currently associated access point.
Definition net.cpp:448
static bool sntp_start(void)
Subscribe the SNTP client, without waiting for it.
Definition net.cpp:514
#define WIFI_FAIL_BIT
Definition net.cpp:43
bool net_wifi_online(void)
true while the station holds an IP address.
Definition net.cpp:437
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...
Definition net.cpp:376
uint16_t net_wifi_scan(net_wifi_ap_t *out, uint16_t max)
Scan for nearby access points (blocking).
Definition net.cpp:322
#define WIFI_MAX_RETRY
Definition net.cpp:46
static esp_netif_t * s_ap_netif
Definition net.cpp:213
void net_wifi_init(void)
Bring up the WiFi driver in station mode (idempotent).
Definition net.cpp:175
bool net_wifi_reconnecting(void)
true while a kept network is down and the re-join is running.
Definition net.cpp:443
static bool s_sntp_running
Definition net.cpp:66
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.
Definition net.cpp:135
static void rejoin_cb(void *arg)
Background re-join: one association attempt, from the timer task.
Definition net.cpp:87
#define BUILD_FLOOR_SLACK_S
Definition net.cpp:465
#define WIFI_CONNECTED_BIT
Definition net.cpp:42
#define REJOIN_FIRST_S
Definition net.cpp:56
static int64_t build_time_floor(void)
Earliest system time this firmware will accept, as a Unix epoch.
Definition net.cpp:479
static void rejoin_cancel(void)
Stop the background re-join and reset its backoff.
Definition net.cpp:116
static int s_retry_num
Definition net.cpp:64
static void on_time_sync(struct timeval *tv)
Every SNTP update, foreground or background, lands here first.
Definition net.cpp:501
static volatile bool s_ap_up
Definition net.cpp:80
static volatile bool s_keep
Definition net.cpp:72
#define REJOIN_MAX_S
Definition net.cpp:57
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).
Definition net.h:33
char ssid[33]
Definition net.h:34
bool open
Definition net.h:36
int8_t rssi
Definition net.h:35
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.
Definition wdt.h:28