From b59c62bbc94a085dd9b02d8f687451af46f6c8da Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Sun, 7 Jun 2026 21:08:44 +0200 Subject: [PATCH] =?UTF-8?q?A1:=20S5F13-F18=20exception=20recovery=20messag?= =?UTF-8?q?es=20(E5=20=C2=A79.5-9.7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catalog gains the recover-request / recover-complete / recover-abort loop that closes E5's exception lifecycle. Wire shapes: S5F13/F14 Exception Recover Request / Acknowledge S5F15/F16 Exception Recover Complete Notify / Acknowledge S5F17/F18 Exception Recover Abort Request / Acknowledge Acks use the same AlarmAck byte already in use by S5F10/F12. EXRESULT on F15 is modelled as ASCII (the common case; vendor-specific richer shapes are a YAML edit). Round-trip tests cover all three pairs. Server dispatch is left for a later commit alongside the per-alarm AlarmStateMachine (Tranche C) — this commit is wire-coverage only. Co-Authored-By: Claude Opus 4.7 --- COMPLIANCE.md | 3 ++ data/messages.yaml | 64 +++++++++++++++++++++++++++++++++++++++++ tests/test_messages.cpp | 42 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index a6fce56..56753f9 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -172,6 +172,9 @@ YAML/handler extensions, not surgery): | S5F7 / S5F8 | H→E | ✅ | catalog | ✅ round-trip | | S5F9 / S5F10 | E→H | ✅ | catalog | ✅ round-trip (exception post) | | S5F11 / S5F12 | E→H | ✅ | catalog | ✅ round-trip (exception clear) | +| S5F13 / S5F14 | H→E | ✅ | catalog | ✅ round-trip (exception recover request) | +| S5F15 / S5F16 | E→H | ✅ | catalog | ✅ round-trip (exception recover complete) | +| S5F17 / S5F18 | H→E | ✅ | catalog | ✅ round-trip (exception recover abort) | | S6F1 / S6F2 | E→H | ✅ | catalog | ✅ round-trip | | S6F11 / S6F12 | E→H | ✅ | catalog | ✅ round-trip + demo | | S6F23 / S6F24 | H→E | ✅ | catalog | ✅ round-trip + demo | diff --git a/data/messages.yaml b/data/messages.yaml index c2a0f7c..832c3d3 100644 --- a/data/messages.yaml +++ b/data/messages.yaml @@ -725,6 +725,70 @@ messages: builder: s5f12_exception_clear_confirm body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack} + # S5F13 / S5F14 — Exception Recover Request / Acknowledge (E5 §9.5). + # Host asks the equipment to execute a specific recovery action from + # the EXRECVRA list that the prior S5F9 advertised. EXRECVRA on F13 + # is a single action string, distinct from the list of allowed actions + # on F9. F14 echoes ACKC5 as our AlarmAck byte. + - id: S5F13 + stream: 5 + function: 13 + w: true + builder: s5f13_exception_recover_request + parser: parse_s5f13 + body: + kind: list + struct_name: ExceptionRecoverRequest + fields: + - {name: exid, shape: {kind: scalar, item_type: U4}} + - {name: exrecvra, shape: {kind: scalar, item_type: ASCII}} + + - id: S5F14 + stream: 5 + function: 14 + builder: s5f14_exception_recover_ack + body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack} + + # S5F15 / S5F16 — Exception Recover Complete Notify / Acknowledge + # (E5 §9.6). Equipment signals that the recovery action requested by + # the matching S5F13 has finished; EXRESULT carries any structured + # outcome the equipment wishes to report (we model it as ASCII, which + # is the common case — vendor-specific richer shapes are a YAML edit). + - id: S5F15 + stream: 5 + function: 15 + w: true + builder: s5f15_exception_recover_complete_notify + parser: parse_s5f15 + body: + kind: list + struct_name: ExceptionRecoverComplete + fields: + - {name: exid, shape: {kind: scalar, item_type: U4}} + - {name: exresult, shape: {kind: scalar, item_type: ASCII}} + + - id: S5F16 + stream: 5 + function: 16 + builder: s5f16_exception_recover_complete_ack + body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack} + + # S5F17 / S5F18 — Exception Recover Abort Request / Acknowledge + # (E5 §9.7). Host aborts a recovery action currently in flight. + - id: S5F17 + stream: 5 + function: 17 + w: true + builder: s5f17_exception_recover_abort_request + parser: parse_s5f17 + body: {kind: scalar, item_type: U4, param: exid} + + - id: S5F18 + stream: 5 + function: 18 + builder: s5f18_exception_recover_abort_ack + body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack} + # ===================================================================== # S6 — Data collection # ===================================================================== diff --git a/tests/test_messages.cpp b/tests/test_messages.cpp index 835bb6e..723248d 100644 --- a/tests/test_messages.cpp +++ b/tests/test_messages.cpp @@ -498,6 +498,48 @@ TEST_CASE("S5F11 / S5F12 exception clear round-trip") { CHECK(*ack_byte(s5f12_exception_clear_confirm(AlarmAck::Accept)) == 0); } +TEST_CASE("S5F13 / S5F14 exception recover request round-trip") { + auto m = s5f13_exception_recover_request(42, "RETRY"); + CHECK(m.stream == 5); + CHECK(m.function == 13); + CHECK(m.reply_expected); + + auto parsed = parse_s5f13(m); + REQUIRE(parsed.has_value()); + CHECK(parsed->exid == 42); + CHECK(parsed->exrecvra == "RETRY"); + + CHECK(*ack_byte(s5f14_exception_recover_ack(AlarmAck::Accept)) == 0); +} + +TEST_CASE("S5F15 / S5F16 exception recover complete round-trip") { + auto m = s5f15_exception_recover_complete_notify(42, "OK"); + CHECK(m.stream == 5); + CHECK(m.function == 15); + CHECK(m.reply_expected); + + auto parsed = parse_s5f15(m); + REQUIRE(parsed.has_value()); + CHECK(parsed->exid == 42); + CHECK(parsed->exresult == "OK"); + + CHECK(*ack_byte(s5f16_exception_recover_complete_ack(AlarmAck::Accept)) == 0); +} + +TEST_CASE("S5F17 / S5F18 exception recover abort round-trip") { + auto m = s5f17_exception_recover_abort_request(42); + CHECK(m.stream == 5); + CHECK(m.function == 17); + CHECK(m.reply_expected); + + // Scalar U4 parser returns the value directly. + auto exid = parse_s5f17(m); + REQUIRE(exid.has_value()); + CHECK(*exid == 42); + + CHECK(*ack_byte(s5f18_exception_recover_abort_ack(AlarmAck::Accept)) == 0); +} + TEST_CASE("S7F19 / S7F20 EPPD list") { auto req = s7f19_current_eppd_request(); CHECK(req.stream == 7);