mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
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>
77 lines
3.2 KiB
C
77 lines
3.2 KiB
C
#ifndef MODEL_EDITOR_H
|
|
#define MODEL_EDITOR_H
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include "SomfyFrame.h"
|
|
#include "ShadeTypes.h"
|
|
|
|
// The rules ModelEditor applies when the shade model is edited: which id, sort
|
|
// order and remote address a new room, shade or group gets, how ids go into
|
|
// and out of a list, and which GPIO pins a shade may not use. Kept free of the
|
|
// shade model so it can be unit tested on the PC (pio test -e native). The
|
|
// edits themselves are applied on loop() (SomfyJson.cpp), since only loop()
|
|
// changes the shade model (docs/adr/0002-loop-task-is-the-only-writer-of-the-shade-model.md).
|
|
|
|
// The edits, one per edit route.
|
|
enum class model_edit_type_t : uint8_t {
|
|
add_room, add_shade, add_group,
|
|
save_room, save_shade, save_group,
|
|
delete_room, delete_shade, delete_group,
|
|
link_remote, unlink_remote,
|
|
link_to_group, unlink_from_group,
|
|
link_repeater, unlink_repeater,
|
|
set_paired,
|
|
room_order, shade_order, group_order
|
|
};
|
|
// What an edit did, sent to the clients as requestResult. id is the room,
|
|
// shade or group it added or changed, 0 when none. code is the validation
|
|
// error of a shade that wasn't saved, 0 otherwise.
|
|
struct edit_result_t {
|
|
bool ok = true;
|
|
uint8_t id = 0;
|
|
int8_t code = 0;
|
|
const char *desc = "";
|
|
static edit_result_t error(const char *desc, int8_t code = 0) {
|
|
edit_result_t r;
|
|
r.ok = false;
|
|
r.desc = desc;
|
|
r.code = code;
|
|
return r;
|
|
}
|
|
};
|
|
|
|
// The lowest id from 1 to lastId that isn't in usedIds, or 0 when every one
|
|
// is taken.
|
|
uint8_t firstFreeId(const uint8_t *usedIds, size_t count, uint8_t lastId);
|
|
// The sort order that puts a new item after every existing one.
|
|
int8_t nextSortOrder(const int8_t *orders, size_t count);
|
|
// candidate, or the next address after it that isn't in used. No two shades or
|
|
// groups may share a remote address.
|
|
uint32_t freeRemoteAddress(uint32_t candidate, const uint32_t *used, size_t count);
|
|
|
|
// A list of ids or addresses with 0 for an empty slot, kept packed at the front.
|
|
// Adding one that is already there changes nothing and succeeds; false when the
|
|
// list is full.
|
|
bool addToList(uint8_t *list, size_t size, uint8_t item);
|
|
bool addToList(uint32_t *list, size_t size, uint32_t item);
|
|
// Removes every copy and packs the rest to the front. False when it wasn't there.
|
|
bool removeFromList(uint8_t *list, size_t size, uint8_t item);
|
|
bool removeFromList(uint32_t *list, size_t size, uint32_t item);
|
|
// Packs the items to the front, keeping their order.
|
|
void packList(uint8_t *list, size_t size);
|
|
|
|
// Who else drives a GPIO pin.
|
|
struct pin_users_t {
|
|
bool (*uses)(uint8_t pin, void *ctx) = nullptr;
|
|
void *ctx = nullptr;
|
|
bool usesPin(uint8_t pin) const { return this->uses && this->uses(pin, this->ctx); }
|
|
};
|
|
#define PIN_CONFLICT_RADIO -10
|
|
#define PIN_CONFLICT_ETHERNET -11
|
|
#define PIN_CONFLICT_SHADE -12
|
|
// Whether a shade of type driven over proto may use these pins (255 for none):
|
|
// 0, or which of the radio, the Ethernet PHY or another shade already uses one.
|
|
// Only the pins the shade type and protocol actually drive are checked.
|
|
int8_t gpioPinConflict(shade_types type, radio_proto proto, uint8_t upPin, uint8_t downPin, uint8_t myPin,
|
|
const pin_users_t &radio, const pin_users_t ðernet, const pin_users_t &otherShades);
|
|
#endif
|