mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
fix: edit ops second review findings
- A group's sunSensor and a shade's flags are refused by name: the routes don't set them. - setPaired needs paired, and without a shade id it is a bad request. - groupOptions' payload is parsed in ProtoEdits (parseReadPayload). - The assembler's mutex is static, so creating it can't fail. - protoIsReadOp and protoIsShadeOp are gone; the tests use protoOpKind. - Native tests for missing(), offeredRemoteAddress and the edit ops against the TYPE table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8fa087aa89
commit
970256e7f6
13 changed files with 107 additions and 34 deletions
|
|
@ -55,7 +55,7 @@ void LoopQueue::drain() {
|
|||
const shade_request_result_t result = shadeRequests.run(item.request);
|
||||
if(result == shade_request_result_t::radio_refused)
|
||||
ESP_LOGW(TAG, "Radio didn't take shade request %u", static_cast<uint8_t>(item.request.type));
|
||||
if(item.replyTo.client) protoSocket.replyRequest(item.replyTo, item.request, result);
|
||||
protoSocket.replyRequest(item.replyTo, item.request, result);
|
||||
break;
|
||||
}
|
||||
case loop_item_type_t::restore_shades: {
|
||||
|
|
|
|||
|
|
@ -35,10 +35,20 @@ bool protoEditType(uint8_t type, model_edit_type_t &out) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
bool protoIsReadOp(uint8_t type) { return protoOpKind(type) == proto_op_t::read; }
|
||||
const char *parseReadPayload(uint8_t type, JsonObjectConst d, uint8_t &groupId) {
|
||||
groupId = 0;
|
||||
if(type != static_cast<uint8_t>(proto_type_t::groupOptions)) return nullptr;
|
||||
JsonVariantConst v = d[protoField(proto_field_t::groupId).key];
|
||||
if(v.isNull()) return "groupId is missing";
|
||||
if(!v.is<uint8_t>()) return "groupId must be 0-255";
|
||||
groupId = v.as<uint8_t>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The fields each edit's route reads, so a field it would ignore is refused
|
||||
// rather than dropped without a word.
|
||||
// rather than dropped without a word. A shade's flags aren't among them:
|
||||
// they hold light, which is set on its own, and the sun and wind state, which
|
||||
// setSensor sets. A group's sunSensor is worked out from its shades.
|
||||
static const proto_field_t ROOM_FIELDS[] = {proto_field_t::name, proto_field_t::sortOrder};
|
||||
static const proto_field_t SHADE_FIELDS[] = {
|
||||
proto_field_t::name, proto_field_t::roomId, proto_field_t::remoteAddress, proto_field_t::shadeType,
|
||||
|
|
@ -50,7 +60,7 @@ static const proto_field_t SHADE_FIELDS[] = {
|
|||
};
|
||||
static const proto_field_t GROUP_FIELDS[] = {
|
||||
proto_field_t::name, proto_field_t::roomId, proto_field_t::remoteAddress, proto_field_t::bitLength,
|
||||
proto_field_t::proto, proto_field_t::flipCommands, proto_field_t::sunSensor, proto_field_t::repeats
|
||||
proto_field_t::proto, proto_field_t::flipCommands, proto_field_t::repeats
|
||||
};
|
||||
template<size_t N>
|
||||
static bool in(proto_field_t field, const proto_field_t (&fields)[N]) {
|
||||
|
|
@ -130,8 +140,7 @@ const char *expandEditPayload(model_edit_type_t type, JsonObjectConst d, JsonDoc
|
|||
// The route takes the ids on their own, in their new order.
|
||||
for(JsonPairConst kv : d) {
|
||||
if(strcmp(kv.key().c_str(), protoField(proto_field_t::ids).key) == 0) continue;
|
||||
snprintf(why, sizeof(why), "A sort order takes only ids");
|
||||
return why;
|
||||
return "A sort order takes only ids";
|
||||
}
|
||||
JsonVariantConst ids = d[protoField(proto_field_t::ids).key];
|
||||
if(!ids.is<JsonArrayConst>()) return "ids is missing";
|
||||
|
|
@ -160,5 +169,7 @@ const char *expandEditPayload(model_edit_type_t type, JsonObjectConst d, JsonDoc
|
|||
return why;
|
||||
}
|
||||
}
|
||||
// A left-out field is not given, so it can't stand for unpaired.
|
||||
if(type == model_edit_type_t::set_paired && obj["paired"].isNull()) return "paired is missing";
|
||||
return doc.overflowed() ? "Too large" : nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ bool protoEditType(uint8_t type, model_edit_type_t &out);
|
|||
// a bad request reply. As with every op, only what was sent is in it.
|
||||
const char *expandEditPayload(model_edit_type_t type, JsonObjectConst d, JsonDocument &doc);
|
||||
|
||||
// getNextRoom, getNextShade, getNextGroup and groupOptions: answered by
|
||||
// loop() from the shade model.
|
||||
bool protoIsReadOp(uint8_t type);
|
||||
// A read op's payload (getNextRoom, getNextShade, getNextGroup and
|
||||
// groupOptions, answered by loop() from the shade model): groupOptions' group,
|
||||
// left 0 for the others. nullptr when it is good; otherwise what is wrong
|
||||
// with it.
|
||||
const char *parseReadPayload(uint8_t type, JsonObjectConst d, uint8_t &groupId);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@ bool protoCommand(const char *name, somfy_commands &cmd) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool protoIsShadeOp(uint8_t type) { return protoOpKind(type) == proto_op_t::shadeRequest; }
|
||||
|
||||
// The repeat count and step size a command may carry.
|
||||
static void readRepeat(OpReader &r, shade_request_t &req) {
|
||||
int32_t n;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
// Every command op except sendRemoteCommand becomes a shade request that
|
||||
// loop() runs.
|
||||
bool protoIsShadeOp(uint8_t type);
|
||||
// nullptr when the payload is good and req is filled in; otherwise what is
|
||||
// wrong with it, for a bad request reply.
|
||||
const char *parseShadeOp(uint8_t type, JsonObjectConst d, shade_request_t &req);
|
||||
|
|
|
|||
|
|
@ -411,7 +411,8 @@ edit_result_t applyModelEdit(model_edit_t &edit) {
|
|||
return done();
|
||||
}
|
||||
case model_edit_type_t::set_paired: {
|
||||
SomfyShade *shade = obj["shadeId"].isNull() ? nullptr : somfy.getShadeById(obj["shadeId"]);
|
||||
if(obj["shadeId"].isNull()) return edit_result_t::error("No shade id was supplied.");
|
||||
SomfyShade *shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if(!shade) return edit_result_t::missing("Shade not found to pair");
|
||||
shade->paired = obj["paired"] | false;
|
||||
shade->save();
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ static BufferedJsonFormatter eventJson;
|
|||
// whoever was sending), so it is only touched under its lock, and never while
|
||||
// replying.
|
||||
static MessageAssembler assembler(PROTO_MAX_CLIENT_FRAME);
|
||||
// Static, so creating it can't fail.
|
||||
static StaticSemaphore_t assemblerMutexBuffer;
|
||||
static SemaphoreHandle_t assemblerMutex = nullptr;
|
||||
struct AssemblerLock {
|
||||
AssemblerLock() { xSemaphoreTake(assemblerMutex, portMAX_DELAY); }
|
||||
|
|
@ -212,12 +214,8 @@ static void queueReadOp(AsyncWebSocketClient *client, const proto_message_t &msg
|
|||
loop_item_t item;
|
||||
item.type = loop_item_type_t::proto_read;
|
||||
item.readOp = msg.type;
|
||||
if(msg.type == static_cast<uint8_t>(proto_type_t::groupOptions)) {
|
||||
JsonVariantConst groupId = d[protoField(proto_field_t::groupId).key];
|
||||
if(groupId.isNull()) { replyError(client, msg.id, proto_error_t::badRequest, "groupId is missing"); return; }
|
||||
if(!groupId.is<uint8_t>()) { replyError(client, msg.id, proto_error_t::badRequest, "groupId must be 0-255"); return; }
|
||||
item.groupId = groupId.as<uint8_t>();
|
||||
}
|
||||
const char *bad = parseReadPayload(msg.type, d, item.groupId);
|
||||
if(bad) { replyError(client, msg.id, proto_error_t::badRequest, bad); return; }
|
||||
item.replyTo = {client->id(), msg.id};
|
||||
if(!loopQueue.submit(item)) replyError(client, msg.id, proto_error_t::busy, "Command queue full");
|
||||
}
|
||||
|
|
@ -371,7 +369,7 @@ void ProtoSocket::wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
|
|||
}
|
||||
|
||||
void ProtoSocket::begin(AsyncWebServer &server) {
|
||||
assemblerMutex = xSemaphoreCreateMutex();
|
||||
assemblerMutex = xSemaphoreCreateMutexStatic(&assemblerMutexBuffer);
|
||||
ws.handleHandshake(authorized);
|
||||
ws.onEvent(ProtoSocket::wsEvent);
|
||||
server.addHandler(&ws);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue