Commit graph

8 commits

Author SHA1 Message Date
cjkas
8fa087aa89 fix: edit ops review findings
From the review of 7723711:
- The assembler is locked: a client closed for a full queue is closed
  by the task that was sending, loop(), whose disconnect dropped the
  buffer while async_tcp could be copying into it. A whole message is
  taken out of it, so a disconnect can't free it while it is read.
- loop() expires a message stalled for 10 s, freeing its buffer and
  telling its client, instead of holding it until another client came.
  A binary message is refused before it can touch another's buffer.
- Each edit takes only the fields its route reads; one it would ignore
  is refused by name. linkRemote's rolling code goes in under the
  route's name, so applyModelEdit is unchanged again.
- An edit refused for an id that doesn't exist replies not found
  (edit_result_t::missing). A reply that doesn't fit its buffer goes
  as an internal error, so the op still gets one.
- The TYPE table says what becomes of each op (proto_op_t), which the
  socket switches on. The reply address is one proto_reply_to_t. The
  routes and the ops start and queue an edit the same way
  (newModelEdit, submitModelEdit). The full-list texts and the offered
  remote address rule live in ModelEditor.
- e2e: the sort order test skips a list that it would renumber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:10:06 +02:00
cjkas
83c946dd5f feat: remove the GitHub update
The release check, release list, download, cancel and
/recoverFilesystem go, with the GitHub button and the Auto Check for
Updates setting in the web UI (docs/adr/0007-no-github-update.md).
The HTTPS session it needed failed for want of a 35-40 KB block (#76),
it checked no certificate, and the web UI's buttons had been getting
404.  Updates are uploads only (/updateFirmware, /updateApplication).

The old socket still sends fwStatus with the installed versions, which
the web UI and Home Assistant read.  checkForUpdate stays in NVS and the
backup file so their formats don't change.

The release image shrinks by 126.5 KB: 142 KB of the OTA slot is free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:28:23 +02:00
cjkas
609cd455c6 fix: only touch the task watchdog from tasks that are subscribed to it
Only loop() is subscribed.  setConnected() runs on the WiFi event task
and reached SocketEmitter::endEmit(), whose plain esp_task_wdt_reset()
logged "esp_task_wdt_reset(707): task not found" on every boot.  The web
and socket handlers on async_tcp did the same.

/scanaps on async_tcp called esp_task_wdt_add(NULL) after the scan,
subscribing a task that nothing resets, so the board could panic 15 s
after a scan from the UI.

TaskWdt.h resets only a subscribed task and re-adds a task after a long
call only when it was watched before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:45:04 +02:00
cjkas
e1a62fe5be feat: keep Ethernet settings independent of the framework
Prepares the move to Arduino 3.x / ESP-IDF 5.5, which renumbers
eth_phy_type_t (ETH_PHY_GENERIC is now 0) and only has the RMII MAC
API on the original ESP32.

- Store the PHY chip as our own id (eth_phy_id_t, the 2.x values) and
  translate it by name at ETH.begin(), so NVS, backups and the web UI
  keep their numbers on either framework.
- Build Ethernet only where CONFIG_ETH_USE_ESP32_EMAC is set instead of
  everywhere but the C6.  C3 and S3 could never use it.  A stored
  Ethernet connection falls back to Wi-Fi on those chips.
- /networksettings reports hasEthernet; the UI hides the Ethernet
  options without it.
- Fix the UI PHY list: 5 is the KSZ8041, add the KSZ8081 (6), drop the
  DM9051, an SPI chip the MAC can't drive.
- SSDP takes its address from WiFi/ETH.localIP() instead of the
  tcpip_adapter API IDF 5 removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:13:49 +02:00
cjkas
953d4dc6f7 fix: stop the 5 s boot wait, tell the client about a bad restore
NTPSettings::apply() waited up to 5 s in getLocalTime() for a clock that
can't be set before the network is up, then returned before it set TZ.
Every cold boot was 5 s slower and ran without its time zone.  It now
calls configTzTime(), which sets the zone and starts SNTP without
waiting, and save() goes through apply().  /setgeneral checked
ntpServer twice, so a request that changed only the zone was ignored.

A restore ignored the result of ShadeConfigFile::restore() and rebooted
either way, so a bad file failed with no word to the user.  The /restore
reply now validates the file and answers 400, which the restore dialog
shows, and an incomplete upload gets a 400 instead of no reply.  A
failed restore changes nothing, so loop() no longer reboots after one.
/shades.tmp, a full copy of the backup with its rolling codes, is
deleted after every restore and after /updateShadeConfig.

The 6 extra bytes in the 2026-10-18 backup: TRANS_REC_SIZE stayed 74
when noiseDetection was added to the 80-byte transceiver record, so
every backup since declares it short.  It is 80 now.  The size check
stays a warning so those backups still restore, and the sum moves to
expectedFileBytes() with tests.  The repeater record length is checked
as a warning only, since validate() also runs on shades.cfg at boot.

Commands for an unknown shade, tilt or group now log a warning, and a
socket that never pinged says so.  KNOWN_ISSUES.md is updated, including
the real cause of the ~365 ms loop stall (radio_tx busy-waits each frame
on loop()'s core).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:08:16 +02:00
cjkas
9383ebec2e fix: keep stored secrets when an older UI sends them empty
Firmware and UI files update separately, so an old index.js can run
against firmware that no longer returns secrets. It sends "" for the
field it can't fill in, which cleared the WiFi passphrase (reboot into
AP mode) or the MQTT password.

The firmware now decides: "" keeps the stored secret, unless the same
request changes the SSID or MQTT username, then it clears it (open
network). /setNetwork compares the settings after applying them, so a
kept passphrase no longer triggers a reboot. The UI cache-buster moves
to 3.0.14d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:07:31 +02:00
cjkas
1fb4c4cfcd fix: stop sending stored passwords and PIN back to clients
The settings routes returned the WiFi passphrase, MQTT password and the
login password and PIN in plain text, and the debug log printed them and
the API key. Responses now carry hasPassphrase/hasPassword/hasPin flags,
logs mask the values, and an omitted secret keeps the stored one.

The UI loads secret fields empty and treats a blank field as "keep" while
the SSID or username is unchanged. Clearing a secret without changing the
SSID or username is listed in docs/KNOWN_ISSUES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:34:18 +02:00
cjkas
a0ad301779 refactor: split the transports into the modules GLOSSARY.md names
Code moves unchanged; only files and names change.

- entryPoints/WebUiRoutes.cpp: registerWebUiRoutes(), the port 80 routes
  and the UI's files, with the helpers only they use.
- entryPoints/ApiRoutes.cpp: registerApiRoutes(), the port 8081 routes.
- entryPoints/Web.cpp keeps the servers' lifecycle, auth and the handlers
  both route sets call; entryPoints/WebRoutes.h is what the three share.
- entryPoints/MQTT.* -> MqttConnection.*, class MQTTClass -> MqttConnection.
- outputs/SomfyMqtt.cpp -> outputs/MqttPublisher.cpp.
- outputs/Sockets.* -> outputs/SocketEmitter.*.

The subscribe and unsubscribe lists (the latter repeated
groups/+/direction/set) become MqttCommands' MQTT_COMMAND_TOPICS, and a
native test checks the parser takes every topic in it. GLOSSARY.md gains
MqttConnection and Change fan-out.

328 native tests. Flash: esp32devdbg +3,112 bytes (the routes are a second
ArduinoJson file), esp32dev -2,336 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:47:56 +02:00