Commit graph

16 commits

Author SHA1 Message Date
cjkas
cbeb56d336 fix: review fixes for the ESP32-P4 build
- The pre-v3 build is for chips before v2.0, not v3.0: the esp32p4_es
  libraries stop at v1.99.  The CI label and comments say so, and that no
  build runs on v2.x or v3.00.
- A save refuses a clock mode the chip can't use: ProtoSettings takes
  CLKMode 0-ETH_CLOCK_ID_MAX, 0 only on the P4.  Before, 1-3 saved and
  then stopped Ethernet at the next start.  A range of one value reads
  "must be 0".
- Tests for the pin limit (54 taken, 55 refused) and the clock limits.
- The web UI's P4 pins leave out strapping pin 36 too.
- The preset says ESP32-P4-WIFI6-POE-ETH, the board's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:10:36 +01:00
cjkas
dfb003d2f7 build: add the ESP32-P4 (#29)
For boards such as Waveshare's ESP32-P4-WIFI6-POE-ETH: WiFi through the
board's ESP32-C6 on SDIO (esp-hosted), Ethernet on the P4's RMII MAC with
an IP101.  Not tested on a device: no P4 board was at hand.

- platformio.ini: env esp32p4 (esp32-p4_r3, chip v3.01+) and esp32p4es
  (esp32-p4, the esp32p4_es libraries, chips before v2.0).  An image runs
  on one of the two only, so each has its own release files, chip models
  "p4" and "p4es".  Both keep the OTA slot: at level 2 they leave about
  128 KB of it.
- Ethernet clock mode: stored as eth_clock_id_t in the ESP32's numbering
  and translated by name, like the PHY id.  The P4's eth_clock_mode_t is
  the IDF's emac_rmii_clock_mode_t, whose 0 is a deprecated default; only
  clock in (GPIO 50) translates there.
- P4 Ethernet defaults: the Waveshare wiring (IP101 as TLK110, address 1,
  MDC 31, MDIO 52, reset 51), board type 8.  The ESP32's put MDIO on an
  SDIO pin.
- Settings take pins up to GPIO 54, the P4's last, instead of 48.
- Radio defaults: SPI 32/33/27, CSN 46 (the framework's Waveshare
  variant), TX 2, RX 3.
- Web UI: a Waveshare ESP32-P4-ETH preset, the P4's clock mode, pin lists
  up to GPIO 54 without the pins the C6 link, Ethernet, USB and UART0 use.
- CI builds and releases both, with their ELFs; bootloader at 0x2000.
- ADR 0003: amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:41:39 +01:00
cjkas
0437cd2a88 fix: review fixes for the C5 build
- ADR 0003: the C5 gets its own consequence instead of sitting in the
  ADR 0006 amendment. It names the single-slot table, the chip revision
  minimum (v1.0, CONFIG_ESP32C5_REV_MIN_FULL=100) and the longer WiFi
  scans with 5 GHz.
- platformio.ini: the [esp32] comment names the C5 and C6 as released
  debug builds; the C5 comment no longer repeats the C6's.
- Transceiver: the C5 comment says why TX and RX are 4 and 5.
- CI: the addr_fs comment names both partition tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:49:42 +01:00
cjkas
38b5c31b79 Merge branch 'feat/esp32c3-build' into feat/esp32c5-build
# Conflicts:
#	docs/adr/0003-pioarduino-idf55.md
2026-10-28 22:33:41 +01:00
cjkas
527215d21a fix: review fixes for the C3 build
- The C3's default radio pins were GPIO 12-17, which hold its SPI flash.
  They are now the framework's SPI pins (SCK 4, MISO 5, MOSI 6, CSN 7)
  with TX 10 and RX 3. A board with pins saved keeps them.
- ADR 0003: the amendment links ADR 0006, says the C3 needs chip
  revision v0.3 or later (CONFIG_ESP32C3_REV_MIN_3), and the Ethernet
  bullet names the C3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:32:37 +01:00
cjkas
298045ec99 build: add the ESP32-C5
The C5 builds like the C6: a debug build with one 3 MB app slot, so it
updates over USB. At CORE_DEBUG_LEVEL=2 its image left 3.4 KB of the
1.5 MB OTA slot, under CI's 8 KB.

- platformio.ini: env esp32c5 (esp32-c5-devkitc-1).
- Chip model "c5", so the updater and releases name its files
  SomfyController.*.esp32c5.bin.
- Radio defaults: the framework's SPI pins (CSN 6, MOSI 8, MISO 9,
  SCK 10), TX 4, RX 5.
- Web UI: GPIO 0-28, without 13-14 (USB) and 15-22 (flash, PSRAM).
- CI builds and releases it; its bootloader sits at 0x2000.
- ADR 0003: amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:51:15 +01:00
cjkas
d596291677 build: bring back the ESP32-C3 build
With the old API gone, the C3 image is 1,498,512 bytes at
CORE_DEBUG_LEVEL=2 and leaves 72.6 KB of its 1.5 MB OTA slot. It was
76.5 KB over when ESP-IDF 5.5 dropped it.

- platformio.ini: env esp32c3, single core, esp32_3MB.csv, level 2.
- CI builds and releases it with the other chips.
- ADR 0003: amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:31:10 +01:00
cjkas
0ee52d6d9f fix: close the token bypass below a route, review fixes for 4b
The routes matched any path below them, and a web asset name there
passed the token check: GET /backup/a.js returned the backup under
Secured. Every route is now an exact match, and a web asset sits in
the root only.

- e2e: those paths answer 401 or 404; the restore test now fails when
  the chunks skip the token check (checked with the check removed).
- WEB_CORS lets the socket take Origin: null instead of skipping the
  Origin check.
- ADR 0006, GLOSSARY and README: the Origin check in either mode, /ws
  as a public route, the Host header behind a reverse proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:39:51 +01:00
cjkas
cfa298f053 fix: review fixes for step 4a
- e2e: the heap check uses a socket slot now, so it waits for the device
  to free it, and test_every_client_reconnecting_at_once closes its
  clients before the check runs.
- Comments and docs that still named removed things (the /controller read,
  the event buffer, memStatus, SocketEmitter, ModelSnapshot).
- ESPNetwork::emitSockets is emitStatus: one socket, and it sends the status.
- Unused includes, whitespace-only lines left by the toJSON removal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:11:54 +01:00
cjkas
ff7517e10f fix: review findings on the login throttle
- The throttle runs on a 64-bit clock in ms (esp_timer_get_time()), so a
  slot left alone for 49.7 days isn't brought back by millis() wrapping
  round to its old failure.
- Web.cpp turns the client into the throttle's key in one place, and
  says that an IPv6 client would share key 0; names are clearer
  (retryMs, vacantSlot).
- The shared slot's test now succeeds from the IP counted in it, so a
  success that cleared the shared slot would fail it.
- GLOSSARY.md names it the login throttle, with lockouts, and
  SECURITY_PLAN.md and ADR 0006 use that name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:44:39 +01:00
cjkas
4415beb4f7 fix: web UI client review findings (step 2a)
- One socket at a time: a socket being replaced is let go without a word,
  and connect() or a new token while a reconnect waits opens no second one.
  A new token reconnects at once, without a refused.
- Events that came before the schema go as soon as it is whole, ahead of
  the sync frames formatted after them, not at syncEnd over newer state.
- A schema's hash counts only once it has come whole, so a sync cut short
  gets it again.
- Feeds are subscribed again after a reconnect; an unknown feed is refused.
- A long op refused as busy without its id goes again.
- A sync without repeaters clears them; order changes name their list; a
  refused resync is called off.
- refused: a listener can close() to stop the retries, which the UI does
  once /loginContext says a login is needed. The client itself can't tell a
  refused token from a device that is down.
- localStorage keeps the schema by default. minify.py removes its work
  folder even when a bundle fails. ADR 0005 records the two bundles.
- Smaller: ProtoSchema.ROWS/withoutRows, ProtoModel.ITEMS, named type
  numbers, unused feeds map and FakeSocket.all gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:04:07 +01:00
cjkas
e25747d395 docs: ADRs 0005-0007 for socket protocol 2
ADR 0005: the web UI and Home Assistant talk to the device over one
websocket protocol (ops on loop(), sync with next, a JSON envelope a
binary encoding can reuse, short keys described by a schema).
ADR 0006: two security modes and eleven HTTP routes on port 80.
ADR 0007: the firmware no longer updates itself from GitHub.

ADR 0002 is amended: its single-writer rule stands, but edits now get
one reply and reads no longer go through a ModelSnapshot.
SECURITY_PLAN.md is revised to match, and keeps the token key, login
backoff, CORS and Origin work. PROTOCOL.md starts as an outline, and
GLOSSARY.md gains the socket protocol and security terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:50:33 +02:00
cjkas
968a31f963 docs: move open known issues to GitHub, drop the finished plans
KNOWN_ISSUES.md loses the fixed entries, and the open ones that are
worth fixing are now issues #77-#86, listed at the top. The three
finished plans go: the LoopQueue decisions nothing else recorded move
into ADR-0002's consequences, the shade behaviour plan's open items
become #85 and #86, and the comments that pointed at the plans now say
what they meant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:27:49 +02:00
cjkas
a1622e652d build: move to pioarduino 55.03.312 (Arduino 3.3.12, ESP-IDF 5.5)
IDF 4.4 gets no more security fixes, and the planned RMT radio needs
IDF 5's RMT driver.  See docs/adr/0003-pioarduino-idf55.md.

- Every env on one pinned pioarduino release.  C6 drops its own
  library list and flags but stays a debug build on huge_app.
- Release envs build at CORE_DEBUG_LEVEL=2 so the ESP32 image fits its
  1.5 MB OTA slot (17 KB free); debug envs stay at 3.
- Drop the C3: its image is 76.5 KB over the OTA slot on IDF 5.5.
- CI installs pioarduino's Core and checks firmware.bin against the app
  partition, failing under 8 KB free.  PlatformIO's "Flash:" figure
  reads ~45 KB low and passed a C3 image that doesn't fit.
- esp_task_wdt_reconfigure(): on 3.x the core already starts the watchdog
  and esp_task_wdt_init() fails, so the C6 build kept the 5 s default.
- An updated firmware is kept only once it has run 60 s with the
  network up; a reset before then rolls back to the previous one.
- ETH.begin() takes the 3.x argument order; Arduino 2.x branches gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:05:22 +02:00
cjkas
8d4c621d5a docs: plan domain modules behind a LoopQueue
ADR-0002 makes loop() the only writer of the shade model. The plan
puts ShadeRequests, ModelEditor and ModelSnapshot behind one LoopQueue
that the HTTP routes and MQTT commands feed, with the outputs fed by
per-field dirty bits. GLOSSARY.md gains the terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:38:43 +02:00
cjkas
c1b47f898c fix: give every NVS user its own Preferences handle
loop() and the async web server task shared one global Preferences.  A
begin() from one task failed while the other had a namespace open, and the
clear() or remove() after it then hit the other task's namespace: saving the
radio settings while a command committed its rolling code could erase every
rolling code.

Each function now opens NVS with a local Preferences.  NVS serializes access
internally and the destructor closes the handle, so no lock is added (see
docs/adr/0001).  No new tests: nothing here builds natively, and deleting the
global makes any missed use a compile error.

The read-then-commit races on rolling codes across tasks remain and are
tracked in docs/KNOWN_ISSUES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:51:05 +02:00