From 330d8fa0de4b3f32ea2135098c4f6b9549deeaa6 Mon Sep 17 00:00:00 2001 From: cjkas Date: Sun, 25 Oct 2026 12:43:06 +0100 Subject: [PATCH] fix: e2e failures on the device: edit replies before their events, /ws closed on reboot - runModelEdit sent an edit's events (room, roomRemoved, the orders, repeaters) before its reply, where PROTOCOL.md says they follow it. A client waiting for the reply read past them (test_adding_and_deleting_a_room). - A restart dropped the /ws connections without a close, so clients never saw them go (test_reboot). ESPNetwork::end() now closes them too. Co-Authored-By: Claude Opus 5.5 --- src/ESPNetwork.cpp | 1 + src/SomfyJson.cpp | 4 +++- src/outputs/ProtoSocket.cpp | 1 + src/outputs/ProtoSocket.h | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ESPNetwork.cpp b/src/ESPNetwork.cpp index cefdc7b..60b3839 100644 --- a/src/ESPNetwork.cpp +++ b/src/ESPNetwork.cpp @@ -57,6 +57,7 @@ void ESPNetwork::end() { SSDP.end(); mqtt.end(); sockEmit.end(); + protoSocket.end(); delay(100); } bool ESPNetwork::setup() { diff --git a/src/SomfyJson.cpp b/src/SomfyJson.cpp index 881fd9a..a1d5f52 100644 --- a/src/SomfyJson.cpp +++ b/src/SomfyJson.cpp @@ -266,9 +266,11 @@ static void markEdited(const model_edit_t &edit, const edit_result_t &result) { void runModelEdit(model_edit_t *edit) { const edit_result_t result = applyModelEdit(*edit); if(!result.ok) ESP_LOGW(TAG, "Edit %u refused: %s", static_cast(edit->type), result.desc); - else markEdited(*edit, result); + // The reply first: the edit's events follow it (docs/PROTOCOL.md), so a + // client that waits for the reply doesn't read past them. if(edit->replyTo.client) protoSocket.replyEdit(edit->replyTo, edit->type, result); else emitRequestResult(edit->requestId, result); + if(result.ok) markEdited(*edit, result); cancelModelEdit(edit); } diff --git a/src/outputs/ProtoSocket.cpp b/src/outputs/ProtoSocket.cpp index 5c6afa8..34c68e0 100644 --- a/src/outputs/ProtoSocket.cpp +++ b/src/outputs/ProtoSocket.cpp @@ -415,6 +415,7 @@ void ProtoSocket::begin(AsyncWebServer &server) { ws.onEvent(ProtoSocket::wsEvent); server.addHandler(&ws); } +void ProtoSocket::end() { ws.closeAll(1001, "Restarting"); } bool ProtoSocket::hasClients() const { for(const proto_slot_t &s : slots) if(s.clientId.load()) return true; diff --git a/src/outputs/ProtoSocket.h b/src/outputs/ProtoSocket.h index ee3ca8b..b6b3c38 100644 --- a/src/outputs/ProtoSocket.h +++ b/src/outputs/ProtoSocket.h @@ -24,6 +24,9 @@ class ProtoSocket { void begin(AsyncWebServer &server); // Moves each client's sync on. loop() only. void loop(); + // Closes every client, before a reboot: a restart drops the connections + // without a word, so clients wouldn't know they had gone. + void end(); bool hasClients() const; // The reply to a command op once loop() has run its shade request. void replyRequest(const proto_reply_to_t &to, const shade_request_t &req, shade_request_result_t result);