feat: settings ops, the WiFi and frequency scans, and reboot for socket protocol 2

Step 1d of the protocol 2 core (docs/SECURITY_PLAN.md).

- getSettings/saveSettings by section: general, network, mqtt, security,
  radio. A save is the settings route's request, so loop() applies it with
  the settings' own fromJSON(). ProtoSettings checks every field first:
  kind, range, string length, IP addresses.
- Secrets are only reported as has*. A security save replies with the
  client's new token and closes the other sockets; other saves send
  settingsChanged to the other clients.
- scanWifi runs an async scan from ESPNetwork, kept apart from the roaming
  scan, and wifiNetworks goes to the clients that asked.
- beginFrequencyScan, endFrequencyScan and reboot run at once on async_tcp,
  as their routes do: a scan's end mustn't wait in the loop queue behind an
  unpair that waits for the scan to end (new op kind 'a').
- e2e: test_settings.py and a --reboot flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-25 09:52:09 +01:00
parent 97a4e98183
commit 2517f40709
22 changed files with 1221 additions and 49 deletions

View file

@ -31,6 +31,21 @@ class ProtoSocket {
void replyEdit(const proto_reply_to_t &to, model_edit_type_t type, const edit_result_t &result);
// The reply to a read op, from the shade model. loop() only.
void replyRead(const proto_reply_to_t &to, uint8_t op, uint8_t groupId);
// Any other reply formatted on loop(): ok, and the fields written between
// the two. One too large for a frame goes as an internal error.
ProtoWriter beginReply(const proto_reply_to_t &to);
void endReply(const proto_reply_to_t &to);
void replyRefused(const proto_reply_to_t &to, proto_error_t code, const char *desc);
// settingsChanged, to every client but the one that saved them.
void settingsChanged(uint32_t savedBy, const char *section);
// Closes every client but keep, as a security save does: their tokens may
// no longer be good.
void closeAllBut(uint32_t keep);
// wifiNetworks goes to the clients that asked for a scan since the last
// one, once it is done: count is WiFi.scanComplete(), and the networks
// are WiFi's scan results.
void wantsWifiNetworks(uint32_t clientId);
void wifiNetworks(int16_t count);
// Events, loop() only.
void shadeState(SomfyShade &shade);
void shade(SomfyShade &shade);