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, "time.krim.dev");
323 esp_sntp_setservername(1, "pool.ntp.org");
324 esp_sntp_setservername(2, "time.google.com");
325 esp_sntp_init();
326 sntpInited = true;
327 } else {
328 esp_sntp_restart();
329 }
330
331 // Wait for sync
332 uint32_t startMs = esp_timer_get_time() / 1000;
333 bool synced = false;
334
335 while ((esp_timer_get_time() / 1000 - startMs) < NTP_SYNC_TIMEOUT_MS) {
336 if (esp_sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
337 synced = true;
338 break;
339 }
340 vTaskDelay(pdMS_TO_TICKS(100));
341 }
342
343 // Only disconnect if we opened the connection AND the caller asked us to
344 if (weConnected && disconnectAfter) {
345 wifi->disconnect();
346 wifi->disable();
347 }
348
349 if (synced) {
350 auto* rtc = hal::getRtcInstance();
351 if (rtc) {
352 rtc->markTimeSet();
353 }
354 lastError_ = nullptr;
355 return true;
356 }
357
358 lastError_ = "NTP timeout";
359 return false;
360}
361
371 auto* wifi = hal::getWifiControllerInstance();
372 if (!wifi) {
373 lastError_ = "No WiFi HW";
374 return false;
375 }
376
377 auto syncTimeIfNeeded = [this]() {
378 auto* rtc = hal::getRtcInstance();
379 if (!rtc || !rtc->isTimeSet()) {
380 syncNtp(/*disconnectAfter=*/false); // best-effort, ignore result
381 }
382 };
383
384 if (wifi->isConnected()) {
385 syncTimeIfNeeded();
386 return true;
387 }
388 if (!config_.valid) {
389 loadConfig();
390 }
391 if (!config_.valid) {
392 lastError_ = "No WLAN configured";
393 return false;
394 }
395 if (!connect()) {
396 return false;
397 }
398 syncTimeIfNeeded();
399 return true;
400}
401
405void WifiHandlers::persistUserEnabled(bool enabled) {
406 nvs_handle_t nvs;
407 if (nvs_open("wifi", NVS_READWRITE, &nvs) != ESP_OK) return;
408 nvs_set_u8(nvs, "ena", enabled ? 1 : 0);
409 nvs_commit(nvs);
410 nvs_close(nvs);
411}
412
416void WifiHandlers::maybeRelease() {
417 if (!userEnabled_ && holdCount_ <= 0) {
418 disconnect();
419 }
420}
421
426 userEnabled_ = enabled;
427 persistUserEnabled(enabled);
428
429 if (enabled) {
430 if (isConnected()) return true;
431 if (!config_.valid) loadConfig();
432 if (!config_.valid) {
433 lastError_ = "No WLAN configured";
434 return false;
435 }
436 return connect();
437 }
438
439 maybeRelease();
440 return true;
441}
442
447 ++holdCount_;
448 if (ensureConnected()) return true;
449 if (holdCount_ > 0) --holdCount_;
450 return false;
451}
452
457 if (holdCount_ > 0) --holdCount_;
458 maybeRelease();
459}
460
465 loadConfig();
466 if (userEnabled_ && config_.valid) {
467 connect();
468 }
469}
470
471} // 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:53
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:209
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.