mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
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>
2 KiB
2 KiB
| status |
|---|
| accepted |
The loop task is the only writer of the shade model
The shade model (rooms, shades, groups and repeaters) is changed only from loop() (the Arduino loopTask). Code on any other task, mainly the HTTP handlers on the async_tcp task, hands its change over as a request through the LoopQueue and reads the shade model only from a ModelSnapshot that loop() fills. On a dual-core ESP32 the two tasks run at the same instant, and on a single-core one async_tcp preempts loop() mid-step, so a second writer races loop(). Examples: /linkRemote setting a rolling code that a received frame overwrites a moment later, /deleteShade clearing a shade halfway through checkMovement, both tasks writing /shades.cfg and the plain isDirty flag, and /controller streaming a document in which one shade has two positions.
Considered Options
- A mutex around the shade model. Rejected: every reader and writer has to remember to take it, and a missed lock is a silent bug. A chunked
/controllerresponse would hold it for seconds and stoploop()from receiving frames and moving shades in the meantime. - Keep the mix. Shade commands were already queued while model edits and reads ran on async_tcp. Rejected because the races above stay.
Consequences
- async_tcp must still never wait for
loop(), so a model edit's HTTP reply can't block until the change is made. The reply only says the request was accepted; the result reaches clients as a WebSocket event onceloop()has made the change. We chose that over a chunked HTTP response thatloop()completes, because pushing results to clients is what the WebSocket is for and it needs no pool of pending responses. - A read has to reply with data, so it does wait, without blocking: its chunked response sends nothing until
loop()has filled the read's ModelSnapshot. - MQTT commands already run on
loop()(mqtt.loop()is called from it). - With one writer, the domain modules are plain single-threaded code and can be tested natively without threads.