Files
roro9stack/src/services/wifi_service.h
T
twislaandClaude Opus 5.5 4404dd9380 VPN: a WireGuard tunnel (#8)
The device joins a WireGuard network over whatever Wi-Fi it is on: one
peer, IPv4. A client's .conf is imported from the card (/vpn/wg0.conf) and
kept in the device's settings, private key included, never shown; Settings
offers to delete the file. A switch brings the tunnel up until the next
restart, "Start with Wi-Fi" every time; it waits for the clock, which a
handshake needs. VPN shows in the Status Bar.

The protocol is esphome/wireguard 0.4.8. It calls lwIP without lwIP's lock,
which this framework checks: every call into it is made with the lock held.

What goes through the tunnel is everything (AllowedIPs 0.0.0.0/0) or the
one subnet the device's tunnel address is in: lwIP routes by an
interface's subnet or by default, nothing finer. The import says how many
ranges it can't reach.

Checked against a test peer in both directions and against a real server,
with a configuration uploaded from a phone (docs/milestones/N1.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-08 01:49:47 +02:00

100 lines
4.1 KiB
C++

#pragma once
#include <vector>
#include "event_bus.h"
#include "saved_networks.h"
#include "scan_entry.h"
#include "service.h"
#include "services/clock_service.h"
#include "settings.h"
#include "wifi_controller.h"
namespace roro {
// 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;
std::string ip() const; // empty unless Connected
// Scans for a list of nearby networks (all channels, or just one for a quick refresh); results
// arrive in listScan() when listScanDone(), and listScanSeq() counts completed scans.
void startListScan(int channel = 0);
bool listScanDone() const { return !listScanPending_; }
const std::vector<ScanEntry>& listScan() const { return listScan_; }
uint32_t listScanSeq() const { return listScanSeq_; }
// Done with list scans: switch the radio back off if Wi-Fi itself is off.
void endListScans();
// What the device is using and where each part came from (S1, Q113).
struct Connection {
bool connected = false;
bool fixed = false; // the address: Fixed, or from DHCP
bool dnsFromSettings = false; // DNS: from Settings, or from DHCP
std::string address, mask, gateway;
int prefix = 0;
std::string dns[2];
struct Ntp {
std::string server;
bool fromDhcp = false;
bool answered = false; // it has replied since the last (re)start
} ntp[3];
int ntpCount = 0;
};
Connection connection() const;
// A Saved Network's IP setting changed: if it's the one in use, join it again with it (Q114).
void ipSettingChanged(const std::string& ssid);
// The DNS or NTP settings changed: use them now.
void serversChanged() { applyServers(Why::SettingsChanged); }
// While a tunnel has put its own DNS servers in (issue #8), ours are not put back over them.
void holdDns(bool held);
// The noise self-test switches the radio off for a few seconds. Not saved anywhere: a restart
// during the test brings Wi-Fi back, which a changed setting wouldn't.
void debugPause(bool paused) { paused_ = paused; }
// 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(int channel = 0);
void finishScan(uint32_t nowMs);
void startNtp();
void join(const std::string& ssid, const std::string& password);
enum class Why { Joined, SettingsChanged, Check };
void applyServers(Why why); // DNS and NTP, as Settings and the network say
Settings& settings_;
SavedNetworks& saved_;
ClockService& clock_;
WifiController controller_;
bool scanRunning_ = false;
bool listScanPending_ = false;
std::vector<ScanEntry> listScan_;
uint32_t listScanSeq_ = 0;
bool ntpWaiting_ = false;
bool sntpStarted_ = false;
bool radioInitialised_ = false;
bool paused_ = false; // Debug Builds: off for a moment, whatever the setting says
bool fixed_ = false; // the network in use has a Fixed address
bool dnsFromSettings_ = false;
bool dnsHeld_ = false;
std::string ntpNames_[2]; // lwIP keeps the pointers, so the names live here
uint32_t serversCheckedMs_ = 0;
};
} // namespace roro