CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
WifiHandlers.cpp
Go to the documentation of this file.
3#include "cdc_hal/IRtc.h"
4#include "cdc_ui/I18n.h"
5#include "cdc_ui/ViewStack.h"
7#include "nvs.h"
8#include "esp_sntp.h"
9#include "esp_timer.h"
10#include "freertos/FreeRTOS.h"
11#include "freertos/task.h"
12#include <cstring>
13#include <cstdio>
14
15namespace cdc::ui {
16
22 memset(this, 0, sizeof(*this));
23 useDhcp = true;
24 strncpy(netmask, "255.255.255.0", sizeof(netmask));
25}
26
31WifiHandlers& WifiHandlers::instance() {
32 static WifiHandlers s_instance;
33 return s_instance;
34}
35
41bool WifiHandlers::isValidIpOctet(int val) {
42 return val >= 0 && val <= 255;
43}
44
50bool WifiHandlers::isValidIpAddress(const char* ip) {
51 if (!ip || !ip[0]) return false;
52 int a, b, c, d;
53 if (sscanf(ip, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) return false;
54 return isValidIpOctet(a) && isValidIpOctet(b) && isValidIpOctet(c) && isValidIpOctet(d);
55}
56
62uint32_t WifiHandlers::parseIpAddress(const char* ip) const {
63 int a, b, c, d;
64 if (sscanf(ip, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) return 0;
65 if (!isValidIpOctet(a) || !isValidIpOctet(b) || !isValidIpOctet(c) || !isValidIpOctet(d)) return 0;
66 return (static_cast<uint32_t>(a) << 24) | (static_cast<uint32_t>(b) << 16) |
67 (static_cast<uint32_t>(c) << 8) | static_cast<uint32_t>(d);
68}
69
74 nvs_handle_t nvs;
75 if (nvs_open("wifi", NVS_READONLY, &nvs) != ESP_OK) {
77 }
79 nvs_get_u32(nvs, "tout", &ms);
80 nvs_close(nvs);
83 return ms;
84}
85
90 if (ms < WIFI_CONNECT_TIMEOUT_MIN_MS || ms > WIFI_CONNECT_TIMEOUT_MAX_MS) {
91 return false;
92 }
93 nvs_handle_t nvs;
94 if (nvs_open("wifi", NVS_READWRITE, &nvs) != ESP_OK) return false;
95 esp_err_t err = nvs_set_u32(nvs, "tout", ms);
96 if (err == ESP_OK) nvs_commit(nvs);
97 nvs_close(nvs);
98 return err == ESP_OK;
99}
100
104void WifiHandlers::saveCredentials(const char* ssid, const char* password) {
105 wizard_.reset();
106 if (ssid) {
107 strncpy(wizard_.ssid, ssid, sizeof(wizard_.ssid) - 1);
108 }
109 if (password) {
110 strncpy(wizard_.password, password, sizeof(wizard_.password) - 1);
111 }
112 wizard_.security = hal::WifiSecurity::WPA2_PSK;
113 wizard_.useDhcp = true;
114 saveConfig();
115}
116
121 nvs_handle_t nvs;
122 if (nvs_open("wifi", NVS_READWRITE, &nvs) == ESP_OK) {
123 nvs_erase_all(nvs);
124 nvs_commit(nvs);
125 nvs_close(nvs);
126 }
127 config_ = {};
128 config_.valid = false;
129 userEnabled_ = false;
130}
131
137 nvs_handle_t nvs;
138 if (nvs_open("wifi", NVS_READONLY, &nvs) != ESP_OK) {
139 config_.valid = false;
140 userEnabled_ = false;
141 return;
142 }
143
144 uint8_t ena = 0;
145 nvs_get_u8(nvs, "ena", &ena);
146 userEnabled_ = (ena != 0);
147
148 size_t len = sizeof(config_.ssid);
149 if (nvs_get_str(nvs, "ssid", config_.ssid, &len) != ESP_OK || len <= 1) {
150 nvs_close(nvs);
151 config_.valid = false;
152 return;
153 }
154
155 len = sizeof(config_.password);
156 nvs_get_str(nvs, "pass", config_.password, &len);
157 nvs_get_u8(nvs, "sec", &config_.security);
158
159 uint8_t dhcp = 1;
160 nvs_get_u8(nvs, "dhcp", &dhcp);
161 config_.useDhcp = (dhcp != 0);
162
163 nvs_get_u32(nvs, "ip", &config_.staticIp);
164 nvs_get_u32(nvs, "gw", &config_.gateway);
165 nvs_get_u32(nvs, "nm", &config_.netmask);
166
167 nvs_close(nvs);
168 config_.valid = true;
169}
170
176 nvs_handle_t nvs;
177 if (nvs_open("wifi", NVS_READWRITE, &nvs) != ESP_OK) return;
178
179 nvs_set_str(nvs, "ssid", wizard_.ssid);
180 nvs_set_str(nvs, "pass", wizard_.password);
181 nvs_set_u8(nvs, "sec", static_cast<uint8_t>(wizard_.security));
182 nvs_set_u8(nvs, "dhcp", wizard_.useDhcp ? 1 : 0);
183
184 // Parse and save static IP config
185 if (!wizard_.useDhcp) {
186 nvs_set_u32(nvs, "ip", parseIpAddress(wizard_.staticIp));
187 nvs_set_u32(nvs, "gw", parseIpAddress(wizard_.gateway));
188 nvs_set_u32(nvs, "nm", parseIpAddress(wizard_.netmask));
189 }
190
191 nvs_commit(nvs);
192 nvs_close(nvs);
193
194 // Reload config
195 loadConfig();
196}
197
203 auto* wifi = hal::getWifiControllerInstance();
204 return wifi && wifi->isConnected();
205}
206
212 if (!config_.valid) {
213 lastError_ = "No config";
214 return false;
215 }
216
217 auto* wifi = hal::getWifiControllerInstance();
218 if (!wifi) {
219 lastError_ = "No WiFi HW";
220 return false;
221 }
222
223 if (!wifi->isEnabled()) {
224 if (!wifi->enable(hal::WifiMode::STA)) {
225 lastError_ = "WiFi init failed";
226 return false;
227 }
228 }
229
230 char toastMsg[96];
231 std::snprintf(toastMsg, sizeof(toastMsg), "%s\n%s",
232 cdc::ui::tr("core.wifi_connecting"), config_.ssid);
233 showToastTask(toastMsg, 0);
234
235 bool connected = wifi->connect(config_.ssid, config_.password, getConnectTimeoutMs());
236
239
240 if (!connected || !wifi->isConnected()) {
241 hal::WifiState state = wifi->getWifiState();
242 switch (state) {
243 case hal::WifiState::DISCONNECTED: lastError_ = "Disconnected"; break;
244 case hal::WifiState::CONNECTION_FAILED: lastError_ = "Auth failed"; break;
245 default: lastError_ = "Timeout"; break;
246 }
247 return false;
248 }
249
250 lastError_ = nullptr;
251 return true;
252}
253
259 auto* wifi = hal::getWifiControllerInstance();
260 if (!wifi) return;
261
262 if (wifi->isConnected()) {
263 wifi->disconnect();
264 // Let lwIP's tcpip thread finish the DHCP release before we tear the
265 // Wi-Fi driver down; otherwise dhcp_release_and_stop sends a UDP
266 // packet over a half-deinitialised driver and dereferences a NULL
267 // pointer in ieee80211_output_do.
268 vTaskDelay(pdMS_TO_TICKS(200));
269 }
270 if (wifi->isEnabled()) {
271 wifi->disable();
272 }
273}
274
286bool WifiHandlers::syncNtp(bool disconnectAfter) {
287 auto* wifi = hal::getWifiControllerInstance();
288 if (!wifi) {
289 lastError_ = "No WiFi HW";
290 return false;
291 }
292
293 // Track if we established the connection ourselves
294 bool weConnected = false;
295
296 // If not connected, try to connect using saved config
297 if (!wifi->isConnected()) {
298 if (!config_.valid) {
299 lastError_ = "No config";
300 return false;
301 }
302
303 // Enable WiFi and connect
304 if (!wifi->isEnabled()) {
305 wifi->enable(hal::WifiMode::STA);
306 }
307
308 bool connected = wifi->connect(config_.ssid, config_.password, getConnectTimeoutMs());
309
310 if (!connected || !wifi->isConnected()) {
311 lastError_ = "Connect failed";
312 return false;
313 }
314
315 weConnected = true;
316 }
317
318 // Initialize SNTP if not done
319 static bool sntpInited = false;
320 if (!sntpInited) {
321 esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
322 esp_sntp_setservername(0, "pool.ntp.org");
323 esp_sntp_setservername(1, "time.google.com");
324 esp_sntp_init();
325 sntpInited = true;
326 } else {
327 esp_sntp_restart();
328 }
329
330 // Wait for sync
331 uint32_t startMs = esp_timer_get_time() / 1000;
332 bool synced = false;
333
334 while ((esp_timer_get_time() / 1000 - startMs) < NTP_SYNC_TIMEOUT_MS) {
335 if (esp_sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
336 synced = true;
337 break;
338 }
339 vTaskDelay(pdMS_TO_TICKS(100));
340 }
341
342 // Only disconnect if we opened the connection AND the caller asked us to
343 if (weConnected && disconnectAfter) {
344 wifi->disconnect();
345 wifi->disable();
346 }
347
348 if (synced) {
349 auto* rtc = hal::getRtcInstance();
350 if (rtc) {
351 rtc->markTimeSet();
352 }
353 lastError_ = nullptr;
354 return true;
355 }
356
357 lastError_ = "NTP timeout";
358 return false;
359}
360
370 auto* wifi = hal::getWifiControllerInstance();
371 if (!wifi) {
372 lastError_ = "No WiFi HW";
373 return false;
374 }
375
376 auto syncTimeIfNeeded = [this]() {
377 auto* rtc = hal::getRtcInstance();
378 if (!rtc || !rtc->isTimeSet()) {
379 syncNtp(/*disconnectAfter=*/false); // best-effort, ignore result
380 }
381 };
382
383 if (wifi->isConnected()) {
384 syncTimeIfNeeded();
385 return true;
386 }
387 if (!config_.valid) {
388 loadConfig();
389 }
390 if (!config_.valid) {
391 lastError_ = "No WLAN configured";
392 return false;
393 }
394 if (!connect()) {
395 return false;
396 }
397 syncTimeIfNeeded();
398 return true;
399}
400
404void WifiHandlers::persistUserEnabled(bool enabled) {
405 nvs_handle_t nvs;
406 if (nvs_open("wifi", NVS_READWRITE, &nvs) != ESP_OK) return;
407 nvs_set_u8(nvs, "ena", enabled ? 1 : 0);
408 nvs_commit(nvs);
409 nvs_close(nvs);
410}
411
415void WifiHandlers::maybeRelease() {
416 if (!userEnabled_ && holdCount_ <= 0) {
417 disconnect();
418 }
419}
420
425 userEnabled_ = enabled;
426 persistUserEnabled(enabled);
427
428 if (enabled) {
429 if (isConnected()) return true;
430 if (!config_.valid) loadConfig();
431 if (!config_.valid) {
432 lastError_ = "No WLAN configured";
433 return false;
434 }
435 return connect();
436 }
437
438 maybeRelease();
439 return true;
440}
441
446 ++holdCount_;
447 if (ensureConnected()) return true;
448 if (holdCount_ > 0) --holdCount_;
449 return false;
450}
451
456 if (holdCount_ > 0) --holdCount_;
457 maybeRelease();
458}
459
464 loadConfig();
465 if (userEnabled_ && config_.valid) {
466 connect();
467 }
468}
469
470} // namespace cdc::ui
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
void render(bool synchronous=false)
Render current view (and modal if present) and flush to display.
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
bool setUserEnabled(bool enabled)
Sets the user/system WiFi intent and applies it immediately.
bool connect()
Connects to Wi-Fi using saved configuration.
static bool isValidIpAddress(const char *ip)
Validates dotted IPv4 address string.
bool syncNtp(bool disconnectAfter=true)
Synchronizes system time via NTP.
void saveCredentials(const char *ssid, const char *password)
Stores credentials directly (WPA2-PSK, DHCP) and persists them.
bool ensureConnected()
Ensures the device is connected to WiFi and optionally syncs time, leaving the connection up for the ...
bool setConnectTimeoutMs(uint32_t ms)
Persists the connect timeout to NVS.
void release()
Releases a previously acquired WiFi hold.
void saveConfig()
Saves current wizard Wi-Fi configuration to NVS.
bool acquire()
Acquires a hold on the WiFi connection for a plugin/host caller.
void restoreOnBoot()
Restores the persisted WiFi intent at boot.
static WifiHandlers & instance()
Returns singleton Wi-Fi handlers instance.
uint32_t getConnectTimeoutMs() const
Reads the persisted connect timeout (NVS key "tout").
void loadConfig()
Loads Wi-Fi configuration from NVS.
void disconnect()
Disconnects and disables Wi-Fi if active.
void clearConfig()
Erases the entire "wifi" NVS namespace and invalidates the cached configuration.
bool isConnected() const
Returns whether Wi-Fi is currently connected.
IWifiController * getWifiControllerInstance()
Returns the singleton Wi-Fi controller service instance.
IRtc * getRtcInstance()
Returns the singleton RTC service instance.
Definition Rtc.cpp:304
Centralized key-code constants for cdc_views.
Definition IModule.h:8
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
static constexpr uint32_t WIFI_CONNECT_TIMEOUT_DEFAULT_MS
Definition WifiHandlers.h:9
void showToastTask(const char *message, uint16_t durationMs=0)
Shows a task/progress toast message.
static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MIN_MS
static constexpr uint32_t NTP_SYNC_TIMEOUT_MS
static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MAX_MS
void reset()
Resets Wi-Fi wizard state to defaults.