Public Access
- EcdsaVerifier (mbedTLS, embedded public key) and EspOtaSink (writes the inactive app slot, esp_ota_end validates the image, then sets the boot partition) - UpdateService: listens on TCP 3232 (and mDNS roro9stack-<id>) while Wi-Fi is Connected; streams into UpdateParser; replies OK/ERR to the sender; remembers the pending version so a Rollback is reported after the reboot - Probation (host-tested): confirm after the first frame + 30 s + Wi-Fi (if configured); roll back if configured Wi-Fi never connects in 3 min - Main loop: full-screen progress while receiving; restart once installed, waiting up to 60 s for Text Entry to end - Settings > Firmware: version, Probation status, push address and name, and the .ota files in /updates on the SD card to install - StorageService.runJob() runs work on the storage task (SD installs) - wifi status prints IP and running version; RORO_TEST_CRASH test hook Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
153 lines
4.9 KiB
C++
153 lines
4.9 KiB
C++
#include "wifi_service.h"
|
|
|
|
#include <WiFi.h>
|
|
#include <esp_sntp.h>
|
|
#include <esp_wifi.h>
|
|
|
|
namespace roro {
|
|
|
|
|
|
std::string WifiService::ip() const {
|
|
return controller_.state() == WifiController::State::Connected ? WiFi.localIP().toString().c_str() : "";
|
|
}
|
|
|
|
int WifiService::rssi() const {
|
|
return controller_.state() == WifiController::State::Connected ? WiFi.RSSI() : 0;
|
|
}
|
|
|
|
void WifiService::startScan(int channel) {
|
|
if (!radioInitialised_) {
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.setAutoReconnect(false); // the controller decides when to reconnect
|
|
esp_wifi_set_country_code("EU", true);
|
|
radioInitialised_ = true;
|
|
}
|
|
if (scanRunning_) return;
|
|
// Hidden networks are listed too (with an empty name); the controller skips them.
|
|
if (WiFi.scanNetworks(true /* async */, true /* hidden */, false, channel ? 120 : 300, channel) == WIFI_SCAN_FAILED)
|
|
return;
|
|
scanRunning_ = true;
|
|
}
|
|
|
|
void WifiService::startListScan(int channel) {
|
|
if (scanRunning_) {
|
|
listScanPending_ = true; // results of the scan already running will do
|
|
return;
|
|
}
|
|
listScanPending_ = true;
|
|
startScan(channel);
|
|
}
|
|
|
|
void WifiService::endListScans() {
|
|
listScanPending_ = false;
|
|
if (controller_.state() == WifiController::State::Off && radioInitialised_ && !scanRunning_) {
|
|
WiFi.mode(WIFI_OFF);
|
|
radioInitialised_ = false;
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
const char* security(wifi_auth_mode_t auth) {
|
|
switch (auth) {
|
|
case WIFI_AUTH_OPEN: return "open";
|
|
case WIFI_AUTH_WEP: return "WEP";
|
|
case WIFI_AUTH_WPA_PSK: return "WPA";
|
|
case WIFI_AUTH_WPA2_PSK:
|
|
case WIFI_AUTH_WPA_WPA2_PSK: return "WPA2";
|
|
case WIFI_AUTH_WPA2_ENTERPRISE: return "WPA2-E";
|
|
case WIFI_AUTH_WPA3_PSK:
|
|
case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA3";
|
|
default: return "?";
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void WifiService::finishScan(uint32_t nowMs) {
|
|
int n = WiFi.scanComplete();
|
|
if (n == WIFI_SCAN_RUNNING) return;
|
|
scanRunning_ = false;
|
|
|
|
std::vector<ScanResult> results;
|
|
std::vector<ScanEntry> entries;
|
|
for (int i = 0; i < n; i++) {
|
|
std::string ssid = WiFi.SSID(i).c_str();
|
|
wifi_auth_mode_t auth = WiFi.encryptionType(i);
|
|
entries.push_back({ssid, WiFi.BSSIDstr(i).c_str(), WiFi.channel(i), WiFi.RSSI(i), auth == WIFI_AUTH_OPEN,
|
|
security(auth)});
|
|
if (!ssid.empty()) results.push_back({ssid, WiFi.RSSI(i)});
|
|
}
|
|
WiFi.scanDelete();
|
|
if (listScanPending_) {
|
|
listScan_ = entries;
|
|
listScanPending_ = false;
|
|
listScanSeq_++;
|
|
}
|
|
apply(controller_.scanDone(results, nowMs));
|
|
}
|
|
|
|
void WifiService::startNtp() {
|
|
// Not configTime(): it would overwrite the POSIX TZ the Clock applies.
|
|
if (!esp_sntp_enabled()) {
|
|
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
|
esp_sntp_setservername(0, "pool.ntp.org");
|
|
esp_sntp_init();
|
|
} else {
|
|
esp_sntp_restart();
|
|
}
|
|
ntpWaiting_ = true;
|
|
}
|
|
|
|
void WifiService::apply(const WifiController::Step& step) {
|
|
using Action = WifiController::Action;
|
|
switch (step.action) {
|
|
case Action::None: break;
|
|
case Action::StartScan: startScan(); break;
|
|
case Action::Connect: WiFi.begin(step.ssid.c_str(), step.password.empty() ? nullptr : step.password.c_str()); break;
|
|
case Action::Disconnect: WiFi.disconnect(); break;
|
|
case Action::RadioOff:
|
|
WiFi.disconnect(true);
|
|
WiFi.mode(WIFI_OFF);
|
|
radioInitialised_ = scanRunning_ = false;
|
|
break;
|
|
case Action::StartMonitor:
|
|
startScan(); // makes sure the radio is up
|
|
WiFi.disconnect();
|
|
break;
|
|
case Action::StopMonitorAndScan:
|
|
esp_wifi_set_promiscuous(false);
|
|
startScan();
|
|
break;
|
|
case Action::StopMonitorAndOff:
|
|
esp_wifi_set_promiscuous(false);
|
|
WiFi.mode(WIFI_OFF);
|
|
radioInitialised_ = scanRunning_ = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void WifiService::tick(uint32_t nowMs) {
|
|
using State = WifiController::State;
|
|
if (scanRunning_) finishScan(nowMs);
|
|
|
|
wl_status_t status = WiFi.status();
|
|
if (controller_.state() == State::Connecting) {
|
|
if (status == WL_CONNECTED) {
|
|
apply(controller_.connected(nowMs));
|
|
startNtp();
|
|
} else if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) {
|
|
apply(controller_.disconnected(nowMs));
|
|
}
|
|
} else if (controller_.state() == State::Connected && status != WL_CONNECTED) {
|
|
apply(controller_.disconnected(nowMs));
|
|
}
|
|
|
|
apply(controller_.update(nowMs, settings_.getBool(Setting::WifiEnabled)));
|
|
|
|
if (ntpWaiting_ && sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
|
|
ntpWaiting_ = false;
|
|
clock_.set(static_cast<int64_t>(time(nullptr)), TimeSource::Ntp);
|
|
}
|
|
}
|
|
|
|
} // namespace roro
|