Public Access
M1 step 2: Wi-Fi Service, Saved Networks, NTP, Settings page
- lib/wifi (host-tested): SavedNetworks (up to 8, validated, hidden flag, persisted) and WifiController (joins the strongest Saved Network, tries hidden ones in turn, backoff 10/30/60 s, connect timeout, Monitoring override, radio off without Saved Networks) - WifiService carries out the controller's actions, sets the EU country code, and syncs the Clock over SNTP without touching the TZ - Wi-Fi On/Off setting; Settings > Wi-Fi page: status, add from a scan or a hidden network, forget - Status Bar: W + signal bars, W? while searching, MON while monitoring - Serial: wifi add / wifi status (replaces the step 1 heap probe) Verified on the device: joins the test network ~5 s after boot, clock set over NTP, ~129 KB free heap while connected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
#include "wifi_service.h"
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
#include <esp_wifi.h>
|
||||
|
||||
namespace roro {
|
||||
|
||||
|
||||
int WifiService::rssi() const {
|
||||
return controller_.state() == WifiController::State::Connected ? WiFi.RSSI() : 0;
|
||||
}
|
||||
|
||||
void WifiService::startScan() {
|
||||
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;
|
||||
if (WiFi.scanNetworks(true /* async */, false /* no hidden */) == WIFI_SCAN_FAILED) return;
|
||||
scanRunning_ = true;
|
||||
}
|
||||
|
||||
void WifiService::startListScan() {
|
||||
listScanPending_ = true;
|
||||
startScan();
|
||||
}
|
||||
|
||||
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();
|
||||
if (ssid.empty()) continue;
|
||||
results.push_back({ssid, WiFi.RSSI(i)});
|
||||
entries.push_back({ssid, WiFi.RSSI(i), WiFi.encryptionType(i) == WIFI_AUTH_OPEN});
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
if (listScanPending_) {
|
||||
listScan_ = entries;
|
||||
listScanPending_ = false;
|
||||
}
|
||||
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
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "event_bus.h"
|
||||
#include "saved_networks.h"
|
||||
#include "service.h"
|
||||
#include "services/clock_service.h"
|
||||
#include "settings.h"
|
||||
#include "wifi_controller.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
struct ScanEntry {
|
||||
std::string ssid;
|
||||
int rssi;
|
||||
bool open;
|
||||
};
|
||||
|
||||
// Owns the Wi-Fi radio: carries out the WifiController's decisions with the ESP32 Wi-Fi stack,
|
||||
// syncs the clock over NTP once Connected, and runs scans for the Settings network list.
|
||||
class WifiService : public Service {
|
||||
public:
|
||||
WifiService(Settings& settings, SavedNetworks& saved, ClockService& clock)
|
||||
: settings_(settings), saved_(saved), clock_(clock), controller_(saved) {}
|
||||
const char* name() const override { return "wifi"; }
|
||||
uint32_t tickIntervalMs() const override { return 250; }
|
||||
void tick(uint32_t nowMs) override;
|
||||
|
||||
WifiController::State state() const { return controller_.state(); }
|
||||
const std::string& ssid() const { return controller_.ssid(); }
|
||||
int rssi() const;
|
||||
|
||||
// Scan for the "add a network" list; results arrive in listScan() when listScanDone().
|
||||
void startListScan();
|
||||
bool listScanDone() const { return !listScanPending_; }
|
||||
const std::vector<ScanEntry>& listScan() const { return listScan_; }
|
||||
|
||||
// A Saved Network was added: try it now rather than after the retry delay.
|
||||
void savedNetworksChanged() { controller_.retryNow(millis()); }
|
||||
|
||||
void requestMonitor() { apply(controller_.requestMonitor()); }
|
||||
void releaseMonitor() { apply(controller_.releaseMonitor(settings_.getBool(Setting::WifiEnabled))); }
|
||||
|
||||
private:
|
||||
void apply(const WifiController::Step& step);
|
||||
void startScan();
|
||||
void finishScan(uint32_t nowMs);
|
||||
void startNtp();
|
||||
|
||||
Settings& settings_;
|
||||
SavedNetworks& saved_;
|
||||
ClockService& clock_;
|
||||
WifiController controller_;
|
||||
bool scanRunning_ = false;
|
||||
bool listScanPending_ = false;
|
||||
std::vector<ScanEntry> listScan_;
|
||||
bool ntpWaiting_ = false;
|
||||
bool radioInitialised_ = false;
|
||||
};
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user