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>
87 lines
3.2 KiB
C++
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; }
|