Commit graph

721 commits

Author SHA1 Message Date
cjkas
3212230f01 fix: web UI review findings (step 2b)
- Saving the security settings threw: the form's values were named
  security, hiding the Security object the new token goes to. A save from
  None to None no longer reconnects, as the device closes nobody then.
- Each sync redraws the lists and reloads the settings, so a reconnect
  shows what changed meanwhile; closing the config panel drops its feeds
  even while the socket is down.
- A 401 on backup or an upload opens the login, as a refused socket does
  (Security.tokenRefused); a failed firmware upload says so.
- The general and security panels fill in what the device leaves out at
  its default, so a save never writes back what the form showed before.
  The hostname holds 31 characters, as the device does.
- The views (settings with defaults, a group's shades, frames, scans, the
  network and radio panels, op errors) move to data-src/js/views.js, with
  tests. runOp is the one op path with the busy retry; repeatOf,
  commandArgs, gone(); dead update*List and editedRepeaters removed.
- Tests for ProtoClient.off and a listener removing itself mid-event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 20:43:09 +01:00
cjkas
0df740df6a feat: web UI on socket protocol 2 (step 2b)
The UI talks to the device over the one socket at /ws: the sync fills the
shade model, its events redraw the lists, and every read, command, edit,
setting, scan and reboot is an op. Only /loginContext, /login and the file
transfers stay on HTTP, so a page load opens no other connection.

- The 8080 socket, editJSON/requestResult and the /controller, /rooms,
  /shades, /groups, /room, /shade, /group, settings and command routes are
  no longer used by the UI.
- An edit's callback gets the item once its event is in (ProtoClient
  waitFor); busy edits and saves are sent again.
- The token is kept in sessionStorage. When /ws is refused and
  /loginContext says a login is needed, the client stops retrying and the
  login panel opens. Backup and uploads send the token.
- A banner shows when the device speaks another protocol version.
- Removed dead code: the /pairShade call and its unused /unpairShade twin,
  loadLogin, login.html (broken, linked from nowhere), Secure Config Only's
  checkbox (protocol 2 has two modes) and the HTTP helpers nothing uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 20:12:54 +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
99c6019431 feat: web UI protocol 2 client layer, JS and CSS bundles (step 2a)
- data-src/js: ProtoSchema (schema from the sync, short keys and names,
  defaults, cached by hash), ProtoModel (rooms, shades, groups, repeaters,
  header and status from the sync and the events) and ProtoClient (/ws,
  hello, sync with next, op() as a Promise of its reply, events, feeds,
  reconnect). Nothing uses them yet: 2b moves the UI onto them.
- minify.py joins js/*.js and index.js into index.js, and the three CSS
  files into app.css, so a page load opens fewer connections.
- test/js: node --test on the three modules, replaying the recorded
  fixtures; a CI job runs it and the firmware build waits for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:00:23 +01:00
cjkas
2c815d30f1 fix: deletes send their removal after the reply; test_reboot waits for the restart
- shadeRemoved, groupRemoved and roomRemoved went out from the deletes,
  before the edit's reply. They now come from editChanges and go out with
  the edit's other events, after it (test_adding_and_deleting_a_room).
- test_reboot reconnected in the moment between the sockets closing and the
  restart, and waited for a hello that never came. It now waits, retries a
  connect without hello, and checks the uptime went back.

e2e on the device (esp32s3dbg): 72 passed, 3 skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:07:17 +01:00
cjkas
17bb571635 test: re-record protocol 2 fixtures with the edit and settings ops
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:43:06 +01:00
cjkas
330d8fa0de fix: e2e failures on the device: edit replies before their events, /ws closed on reboot
- runModelEdit sent an edit's events (room, roomRemoved, the orders,
  repeaters) before its reply, where PROTOCOL.md says they follow it. A
  client waiting for the reply read past them (test_adding_and_deleting_a_room).
- A restart dropped the /ws connections without a close, so clients never
  saw them go (test_reboot). ESPNetwork::end() now closes them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:43:06 +01:00
cjkas
72e99d14c5 fix: drop Secure Config Only permissions from protocol 2
The socket has two modes, None and Secured (ADR 0006), and ignores the
ConfigOnly bit, so the security section no longer reads or saves it.
The HTTP routes keep it until step 4 removes the mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:11:19 +01:00
cjkas
096eef0581 fix: settings ops review findings
- The frequency scan ops and reboot run on loop() like every op (ADR 0005).
  endFrequencyScan goes to the front of the loop queue, so it never waits
  behind an unpair that waits for the scan to end.
- A security save that leaves the device Secured, or changes its mode,
  closes every client, the saver too once its reply has gone. None to None
  closes nobody.
- proto must be a radio_proto value; frequency, rxBandwidth and deviation
  are checked against what the CC1101 can be set to.
- wifiNetworks sends fewer networks instead of none when they don't fit.
- A client WiFi scan doesn't start while the device joins a network.
- rebootTime is set before reboot; cancelSettingsSave is freeSettingsSave.
- PROTOCOL.md: pins that may be -1, and the fields the routes sent that
  moved elsewhere. e2e: the WiFi scan test goes by the connection in use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:27:53 +01:00
cjkas
2517f40709 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>
2026-10-25 09:52:09 +01:00
cjkas
97a4e98183
Merge pull request #102 from cjkas/feat/proto-edit-ops
feat: edit ops and the reads their forms need, for socket protocol 2
2026-10-24 22:43:07 +02:00
cjkas
a99d84b29d
Merge pull request #104 from cjkas/chore/s3dbg
added s3 dbg board
2026-10-24 22:42:49 +02:00
cjkas
50a03a1711 added s3 dbg board 2026-10-24 22:41:47 +02:00
cjkas
ce8b0d0ed2
Merge pull request #103 from cjkas/fix/cc1101-spi-bus-hang
fix: CC1101 init hangs the ESP32-S3 when the radio is enabled
2026-10-24 21:15:00 +02:00
cjkas
642cfd0c94 fix: CC1101 init hangs the ESP32-S3 when the radio is enabled
Init() starts the SPI bus and resets the chip, then every register write
calls SpiStart() again while the bus is still up.  SpiStart() ran pinMode()
on SCK, MOSI and MISO.  On Arduino 3.x that takes the pins from the SPI bus,
which stops it (spiStopBus) while SPIClass still holds the handle, so the
following SPI.begin() returns early and the bus stays down.

On the S3 the stopped bus also has clk_gate cleared, so spiTransferByte()
waits on cmd.usr forever and the task watchdog aborts async_tcp when the
radio settings are saved.  On the ESP32 the transfer completes on detached
pins, so the first register write (FSCTRL1) is silently lost.

SPI.begin() attaches and configures the bus pins itself, so on ESP32
targets SpiStart() now only sets SS, which the driver drives by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:53:10 +02:00
cjkas
970256e7f6 fix: edit ops second review findings
- A group's sunSensor and a shade's flags are refused by name: the routes
  don't set them.
- setPaired needs paired, and without a shade id it is a bad request.
- groupOptions' payload is parsed in ProtoEdits (parseReadPayload).
- The assembler's mutex is static, so creating it can't fail.
- protoIsReadOp and protoIsShadeOp are gone; the tests use protoOpKind.
- Native tests for missing(), offeredRemoteAddress and the edit ops
  against the TYPE table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:29:34 +02:00
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
77237111ea feat: edit ops and the reads their forms need, for socket protocol 2
The edit routes become ops 26-44 (addRoom ... groupSortOrder): ProtoEdits
turns an op's payload into the route's request, full names and real
booleans, and loop() applies it with applyModelEdit() as the route
does. The reply goes to the client that sent it, with an add's new id,
or the route's reason and a shade's validation code; the HTTP routes
keep sending requestResult.

getNextRoom, getNextShade, getNextGroup and groupOptions (45-48) are
answered by loop() from the shade model, through a new LoopQueue item.

Messages that arrive in pieces, a frame over one TCP segment or several
frames, are put back together by ProtoAssembly: one at a time across
clients, up to 4 KB, in a buffer held only while one is on its way.
A message in pieces is now told from its first frame's opcode, which
also stops a fragmented message's last frame drawing a second reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 14:29:53 +02:00
cjkas
ea973809e2
Merge pull request #100 from cjkas/feat/proto-command-ops
feat: command ops for socket protocol 2
2026-10-24 14:01:00 +02:00
cjkas
d46d8734af
Merge pull request #99 from cjkas/feat/socket-protocol-2
feat: socket protocol 2 core on ws://host/ws
2026-10-24 14:00:18 +02:00
cjkas
5737a58790 fix: command op replies that said ok when nothing was done
From the review of #100:
- setMyPosition on a moving shade replies busy ("The shade is moving"),
  and setPositions with only a tilt, for a shade without one, replies
  bad request ("The shade has no tilt"). Both said ok before.
- PROTOCOL.md lists where the ops differ from the routes, and how
  sendRemoteCommand is answered.
- The LoopQueue hold comment covers protocol 2 replies and an unpair
  held through a frequency scan.
- One shadeRequestIsForGroup() for the group cases; ProtoOps shares one
  reader for "one or both" fields and one for ids, and the radio
  refused text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:46:00 +02:00
cjkas
598d708b12 test: re-record protocol 2 fixtures with the command ops
The schema now lists the ops 16-25 and shadeCommanded. Scrubbed as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:42:41 +02:00
cjkas
0b54264ee3 feat: command ops for socket protocol 2
shadeCommand, groupCommand, tiltCommand, repeatCommand, setPositions,
setSensor, setMyPosition, unpairShade, setRollingCode (types 16-24) are
read on async_tcp by ProtoOps and handed to loop() through the
LoopQueue, which replies once the request has run: ok, not found or
radio unavailable. A bad payload, a paused radio or a full queue is
answered straight away. sendRemoteCommand (25) touches nothing of the
shade model, so it goes to the radio from async_tcp like its route.

ShadeRequests::run() now says how a request went, not only whether
the radio took it. An op reads only the fields that were sent, so
setPositions can leave the tilt as it is. The shadeCommand event is
renamed shadeCommanded, since the op has the route's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:42:41 +02:00
cjkas
9fe05105ff test: sync_waits_for_next lets events through
The 30 s memory status could land in its 2 s window and fail it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:40:08 +02:00
cjkas
dc2f1478b6 fix: protocol 2 core review findings
From the review of #99:
- A sync could stall when a client took a slot the moment another left:
  loop() used up the new client's sync on the old one. The slots move
  to ProtoSlots, a pure module with native tests, and loop() takes a
  slot's wants only for the client it read (takeWants).
- Deleting a group sends its shades, whose inGroup changed. Deleting a
  shade unlinks it from its groups and sends them, so a new shade given
  its id doesn't join them.
- next and sync with an id get an ok reply, and a message in pieces
  gets one bad request, not one per piece.
- Feeds check for loop() before they write the event buffer, and log
  an event too large, as events do.
- editChanges() (ModelEditor, native tests) says what clients get after
  an edit; the edit result carries the shade a group gained or lost.
  order() takes a sort_list_t.
- The frame writers move to ProtoFrame. Renames: pf to proto_field_t,
  the X-macro letters to TYPE/FIELD/ERR/FEED, proto_request_t to
  proto_message_t, REQ_* to WANTS_*, fmt to eventJson. The event
  bodies share sendItem(), the connection type name lives in
  ESPNetwork, and the loop() task check in SocketEmitter.
- e2e: the heap floor check skips when the low since boot is already
  under it; tests for replies to next and sync and a long message.
- SECURITY_PLAN.md splits step 1 into 1a-1d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:17:15 +02:00
cjkas
d869bb53d5 test: record protocol 2 fixtures from a device
hello, the schema and a whole sync as the device sent them, recorded with pytest test/e2e --record. Remote addresses, rolling codes, names, the server id, hostname and SSID are replaced with fake values while recording, since with an address and its rolling code anyone could forge frames for a real installation's shades.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:03:07 +02:00
cjkas
10521cf78d test: start the e2e suite for socket protocol 2
A pytest suite run by hand against a device (test/e2e/README.md): hello, the schema and its hash, the sync with next and resync, replies to bad frames, feed subscriptions, the status event, every client syncing at once, a sixth client refused, a page load, and the socket under the device's security mode. It never transmits, changes settings or reboots. --record saves hello, the sync and the schema as fixtures.

PROTOCOL.md now describes the protocol as built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:03:07 +02:00
cjkas
482e495678 feat: socket protocol 2 core on ws://host/ws
The /ws endpoint runs next to the old socket on port 8080.  The
handshake checks the token under security, the device sends hello,
then a sync of the schema (unless the client has it), header, rooms,
shades, groups, repeaters and status, paced by the client's next.
Events go out with the old ones: shadeState or the whole shade on a
config change, groups, rooms, removals, orders, repeaters,
shadeCommand, memory, wifi, ethernet, and the frames and
frequencyScan feeds.

The table of types, fields, errors and feeds is defined once and is
sent as the schema.  The writer, item writers, sync and client frame
parser are pure and natively tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:03:07 +02:00
cjkas
a36888f8a1
Merge pull request #98 from cjkas/feat/remove-github-update
feat: remove the GitHub update
2026-10-24 10:46:58 +02:00
cjkas
f4ecfb773f
Merge pull request #97 from cjkas/docs/adr-0005-websocket-api
docs: ADRs 0005-0007 for socket protocol 2
2026-10-24 10:46:36 +02:00
cjkas
ef753f53bb
Merge pull request #96 from cjkas/fix/alloc-failure-log
feat: log failed heap allocations before the abort
2026-10-24 10:46:19 +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
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
e2bab0d492 feat: log failed heap allocations before the abort
An allocation that fails inside library code (the web server's
operator new) aborts with only a backtrace.  A failed-allocation
callback now prints the requested size, the caps, the function and
what the heap had left (free, largest block, minimum free), using the
ROM printf as the logger may need the heap itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:40:25 +02:00
cjkas
539a49eb77
Merge pull request #92 from cjkas/chore/debug-serial-921600
chore: run the debug build's serial port at 921600
2026-10-21 21:47:13 +02:00
cjkas
cb9ca4ce29 Merge remote-tracking branch 'origin/main' into chore/debug-serial-921600 2026-10-21 21:43:36 +02:00
cjkas
0ba8a14f62
Merge pull request #93 from cjkas/chore/main-cpp-not-ino
chore: make SomfyController a .cpp instead of an .ino
2026-10-21 21:10:07 +02:00
cjkas
304fa399d0 chore: make SomfyController a .cpp instead of an .ino
PlatformIO converts an .ino into a fresh .cpp on every build, so the
main file recompiled and the firmware relinked even when nothing had
changed (about 25s of a no-change build of esp32dev).  As a .cpp it
only rebuilds when it changes.  The release assets keep their
SomfyController.ino.*.bin names, which the workflow sets itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:03:58 +02:00
cjkas
18f11929f3 chore: run the debug build's serial port at 921600
The debug build logs at info level, and each line holds up the task that
logs it for ~9ms at 115200.  At 921600 that drops to ~1ms.  SERIAL_BAUD
defaults to 115200, so the other envs are unchanged.  The ROM's boot
banner still prints at 115200 and shows as garbage in the monitor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:18:55 +02:00
cjkas
9696489554
Merge pull request #91 from cjkas/fix/close-stalled-ws-clients
fix: close WebSocket clients whose queue is full
2026-10-21 20:01:47 +02:00
cjkas
e31eafe255 fix: close WebSocket clients whose queue is full
A client that stops reading (a frozen browser tab) keeps its TCP link
alive, so neither AsyncTCP's ack timeout nor lwIP ever closes it.  With
setCloseClientOnQueueFull(false) every message dropped for it printed a
warning, and each warning held up loop() for about 10ms on the UART.
One such client stayed full for 44 minutes and made every step of
loop() slow while shades moved.

Closing it frees the queue.  The web UI and Home Assistant reconnect
and get the full state again from delayInit().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:51:46 +02:00
cjkas
63c2d180e0
Merge pull request #90 from cjkas/fix/socket-emits-on-loop
fix: send socket events only from loop()
2026-10-21 19:11:42 +02:00
cjkas
25d548ef07 fix: send socket events only from loop()
Every socket event is formatted into one static buffer by one shared
formatter.  setConnected() runs on the WiFi/Ethernet event task and sent
memStatus, wifiStrength and ethernet, then ran sockEmit.loop(), while
loop() could be building its own event.  _safecat() checks the room and
then writes at _len, so another task moving _len in between could write
past the end of g_response.

setConnected() now sends nothing.  loop() broadcasts the status every
STATUS_EMIT_MS (30 s): memStatus, then ethernet, or wifiStrength when it
moved 3 dBm or changed channel.  A client that connects still gets all
of it at once from initClients(), so needsBroadcast and the 1.5 s tick
go, and so does the 1.5 s memStatus for the config panel.

SocketEmitter records loop()'s task in begin().  Off it, beginEmit()
logs and hands out a formatter that writes nothing, and endEmit(),
endEmitRoom() and loop() do nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:27:21 +02:00
cjkas
474c7c0f42
Merge pull request #88 from cjkas/fix/task-wdt-unsubscribed-tasks
fix: only touch the task watchdog from tasks that are subscribed to it
2026-10-21 14:04:08 +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
e9109c56e0
Merge pull request #87 from cjkas/docs/prune-finished-plans
docs: move open known issues to GitHub, drop the finished plans
2026-10-21 13:29:45 +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
57aa01e817
Merge pull request #70 from cjkas/feat/eth-framework-independent
feat: keep Ethernet settings independent of the framework
2026-10-21 13:11:31 +02:00
cjkas
2c1085ee9b Merge branch 'main' into feat/eth-framework-independent
Brings in the coredump bounds fix (#74). Utils.h, Utils.cpp and
test_utils.cpp conflicted only because both sides added helpers in the
same spot; kept otaProven/releaseAssetName and formatBacktrace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:09:16 +02:00
cjkas
4dbef4fbae
Merge pull request #71 from cjkas/feat/idf55
build: move to pioarduino 55.03.312 (Arduino 3.3.12, ESP-IDF 5.5)
2026-10-21 12:49:49 +02:00