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>
78 lines
3 KiB
C++
78 lines
3 KiB
C++
#include "ModelEditor.h"
|
|
|
|
uint8_t firstFreeId(const uint8_t *usedIds, size_t count, uint8_t lastId) {
|
|
for(uint16_t id = 1; id <= lastId; id++) {
|
|
bool used = false;
|
|
for(size_t i = 0; i < count && !used; i++) used = usedIds[i] == id;
|
|
if(!used) return static_cast<uint8_t>(id);
|
|
}
|
|
return 0;
|
|
}
|
|
int8_t nextSortOrder(const int8_t *orders, size_t count) {
|
|
int8_t order = -1;
|
|
for(size_t i = 0; i < count; i++)
|
|
if(orders[i] > order) order = orders[i];
|
|
return order + 1;
|
|
}
|
|
uint32_t freeRemoteAddress(uint32_t candidate, const uint32_t *used, size_t count) {
|
|
// Start over after every bump: the new address may be taken too.
|
|
for(size_t i = 0; i < count; i++) {
|
|
if(used[i] == candidate) {
|
|
candidate++;
|
|
i = static_cast<size_t>(-1);
|
|
}
|
|
}
|
|
return candidate;
|
|
}
|
|
|
|
template<typename T>
|
|
static bool addTo(T *list, size_t size, T item) {
|
|
for(size_t i = 0; i < size; i++)
|
|
if(list[i] == item) return true;
|
|
for(size_t i = 0; i < size; i++) {
|
|
if(list[i] == 0) {
|
|
list[i] = item;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
template<typename T>
|
|
static bool removeFrom(T *list, size_t size, T item) {
|
|
bool removed = false;
|
|
size_t j = 0;
|
|
for(size_t i = 0; i < size; i++) {
|
|
if(list[i] == 0) continue;
|
|
if(list[i] == item) removed = true;
|
|
else list[j++] = list[i];
|
|
}
|
|
while(j < size) list[j++] = 0;
|
|
return removed;
|
|
}
|
|
void packList(uint8_t *list, size_t size) { removeFrom<uint8_t>(list, size, 0); }
|
|
bool addToList(uint8_t *list, size_t size, uint8_t item) { return addTo<uint8_t>(list, size, item); }
|
|
bool addToList(uint32_t *list, size_t size, uint32_t item) { return addTo<uint32_t>(list, size, item); }
|
|
bool removeFromList(uint8_t *list, size_t size, uint8_t item) { return removeFrom<uint8_t>(list, size, item); }
|
|
bool removeFromList(uint32_t *list, size_t size, uint32_t item) { return removeFrom<uint32_t>(list, size, item); }
|
|
|
|
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) {
|
|
if(proto != radio_proto::GP_Relay && proto != radio_proto::GP_Remote) return 0;
|
|
// A dry contact or a single button gate driven by a remote only uses the down
|
|
// pin; a two way dry contact and a relay don't use My.
|
|
if(type == shade_types::drycontact ||
|
|
((type == shade_types::garage1 || type == shade_types::lgate1 || type == shade_types::cgate1 || type == shade_types::rgate1)
|
|
&& proto == radio_proto::GP_Remote)) upPin = myPin = 255;
|
|
else if(type == shade_types::drycontact2) myPin = 255;
|
|
if(proto == radio_proto::GP_Relay) myPin = 255;
|
|
const uint8_t pins[] = { upPin, downPin, myPin };
|
|
auto clash = [&pins](const pin_users_t &users) {
|
|
for(uint8_t pin : pins)
|
|
if(pin != 255 && users.usesPin(pin)) return true;
|
|
return false;
|
|
};
|
|
if(clash(radio)) return PIN_CONFLICT_RADIO;
|
|
if(clash(ethernet)) return PIN_CONFLICT_ETHERNET;
|
|
if(clash(otherShades)) return PIN_CONFLICT_SHADE;
|
|
return 0;
|
|
}
|