ESPSomfy-RTS/src/LoopQueue.h
cjkas e70e499d75 refactor: apply shade model edits on loop() through ModelEditor
The 19 routes that changed rooms, shades, groups and repeaters on the web
server task (add, save, delete, link and unlink, setPaired, the sort
orders) now copy their request into a model_edit_t, queue it on the
LoopQueue and reply {status:accepted,requestId:N}. loop() applies it
(SomfyJson.cpp, next to fromJSON) and broadcasts requestResult {requestId,
ok, id, code, desc}. One edit at a time; a second gets 503, a body over
4 KB gets 413.

ModelEditor.h/.cpp holds the rules, tested natively: the lowest free id,
the next sort order, a free remote address, adding to and removing from
the group-member and repeater lists, and the GPIO pin-conflict check. They
replace the getNext*Id/getMax*Order copies, the remote-address search,
the list code and the inline pin checks. The pin check now uses the
protocol being saved and checks every pin against Ethernet, not only up.

/getNextRoom, /getNextShade, /getNextGroup, /groupOptions and a new
/repeaters (the UI already called it) are ModelSnapshot reads.

The web UI's editJSON() sends an edit, waits for its requestResult, then
reads the item back from /shade, /group, /room or /repeaters, so the
callbacks that used the old reply bodies are unchanged. No result within
5 s offers a reload; reads and edits told Busy are retried. It now also
refreshes its lists on shadeAdded/shadeRemoved/groupAdded/groupRemoved
from other clients, and procRoomRemoved keeps the right rooms.

Also fixed: a shade that fails validation is no longer added; linked
repeaters and sort orders are saved; linking reports a full list.

27 new native tests, 319 in all. Flash: esp32devdbg -11,484 bytes,
esp32dev -7,180 bytes.

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

63 lines
2.6 KiB
C++

#ifndef LOOP_QUEUE_H
#define LOOP_QUEUE_H
#include <stdint.h>
#include "ShadeRequests.h"
// The one queue through which other tasks hand work to loop(), which alone
// changes the shade model (docs/adr/0002-loop-task-is-the-only-writer-of-the-shade-model.md).
// The items and the hold rule are kept free of FreeRTOS so they can be unit
// tested on the PC (pio test -e native).
// Big enough for Home Assistant firing a command at every shade at once.
#define LOOP_QUEUE_SIZE 32
// What a restore_shades item restores, as bits of restoreParts.
#define RESTORE_SETTINGS 0x01
#define RESTORE_SHADES 0x02
#define RESTORE_NETWORK 0x04
#define RESTORE_TRANSCEIVER 0x08
#define RESTORE_REPEATERS 0x10
#define RESTORE_MQTT 0x20
enum class loop_item_type_t : uint8_t {
shade_request, // run by ShadeRequests
restore_shades, // restore from /shades.tmp, then reboot
load_shades, // replace the shades with /shades.tmp
snapshot_fill, // fill a read's ModelSnapshot
model_edit // apply an edit with ModelEditor
};
struct model_edit_t;
struct loop_item_t {
loop_item_type_t type = loop_item_type_t::shade_request;
shade_request_t request; // shade_request
uint8_t restoreParts = 0; // restore_shades: RESTORE_* bits
uint8_t slot = 0; // snapshot_fill: the SnapshotSlots slot
model_edit_t *edit = nullptr; // model_edit: loop() runs and frees it
};
// Whether the item sends anything on air.
inline bool loopItemUsesRadio(const loop_item_t &item) {
return item.type == loop_item_type_t::shade_request && shadeRequestUsesRadio(item.request.type);
}
// Whether the item at the head of the queue waits for the radio. The client
// was already told it was accepted so it stays queued until the radio has room
// rather than have the radio drop it. While TX is suspended nothing gets
// through anyway so nothing is held back then, except an unpair: the client was
// told the shade is unpaired, so it waits until TX resumes.
inline bool holdLoopItem(const loop_item_t &item, bool txSuspended, bool txHasRoom) {
if(txSuspended) return item.type == loop_item_type_t::shade_request && item.request.type == shade_request_type_t::unpair;
return loopItemUsesRadio(item) && !txHasRoom;
}
class LoopQueue {
public:
void begin();
// Hands the item to loop() without waiting, so it is safe from any task,
// including async_tcp. False when the queue is full.
bool submit(const loop_item_t &item);
// Runs the waiting items in order. Only from loop().
void drain();
private:
void *queue = nullptr;
};
#endif