ESPSomfy-RTS/src/SomfyGroup.cpp
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

87 lines
3.2 KiB
C++

#include "esp_log.h"
#include "Somfy.h"
#include "ModelEditor.h"
static const char *TAG = "Somfy";
extern SomfyShadeController somfy;
void SomfyGroup::clear() {
this->setGroupId(255);
this->setRemoteAddress(0);
this->repeats = 0;
this->roomId = 0;
this->name[0] = 0x00;
memset(&this->linkedShades, 0x00, sizeof(this->linkedShades));
}
// False when the group is full. The caller saves the change.
bool SomfyGroup::linkShade(uint8_t shadeId) { return addToList(this->linkedShades, SOMFY_MAX_GROUPED_SHADES, shadeId); }
// False when the shade wasn't in the group. The caller saves the change.
bool SomfyGroup::unlinkShade(uint8_t shadeId) { return removeFromList(this->linkedShades, SOMFY_MAX_GROUPED_SHADES, shadeId); }
// Linked shade ids are kept packed so a search can stop at the first 0.
void SomfyGroup::compressLinkedShadeIds() { packList(this->linkedShades, SOMFY_MAX_GROUPED_SHADES); }
bool SomfyGroup::hasShadeId(uint8_t shadeId) {
for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) {
if(this->linkedShades[i] == 0) break;
if(this->linkedShades[i] == shadeId) return true;
}
return false;
}
void SomfyGroup::updateFlags() {
uint8_t oldFlags = this->flags;
this->flags = 0;
for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) {
if(this->linkedShades[i] != 0) {
SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]);
if(shade) this->flags |= shade->flags;
}
else break;
}
if(oldFlags != this->flags) this->emitState();
}
int8_t SomfyGroup::p_direction(int8_t dir) {
int8_t old = this->direction;
if(old != dir) {
this->direction = dir;
this->publish("direction", this->direction);
}
return old;
}
bool SomfyGroup::sendCommand(somfy_commands cmd) { return this->sendCommand(cmd, this->repeats); }
bool SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) {
ESP_LOGI(TAG, "[Group %u] sendCommand cmd=%s repeat=%u", this->getGroupId(), translateSomfyCommand(cmd).c_str(), repeat);
// This sendCommand function will always be called externally. sendCommand at the remote level
// is expected to be called internally when the motor needs commanded.
if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type;
// Leave the group and its shades alone if the radio didn't take the command.
if(!SomfyRemote::sendCommand(cmd, repeat, stepSize)) return false;
switch(cmd) {
case somfy_commands::My:
this->p_direction(0);
break;
case somfy_commands::Up:
this->p_direction(-1);
break;
case somfy_commands::Down:
this->p_direction(1);
break;
default:
break;
}
for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) {
if(this->linkedShades[i] != 0) {
SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]);
if(shade) {
ESP_LOGI(TAG, "[Group %u] processInternalCommand on shade %u cmd=%s", this->getGroupId(), shade->getShadeId(), translateSomfyCommand(cmd).c_str());
shade->processInternalCommand(cmd, repeat);
shade->emitCommand(cmd, "group", this->getRemoteAddress());
}
}
}
this->updateFlags();
this->emitState();
return true;
}
bool SomfyGroup::save() { somfy.commit(); return true; }