100%/A: S5F7/F8 list enabled alarms
tests / build-and-test (push) Failing after 32s

Closes the small remaining hole in Alarm Management.  S5F7 is
header-only; S5F8 has the same wire shape as S5F6 (vector of
<L,3 <B ALCD> <U4 ALID> <A ALTX>>) but only includes alarms whose
enabled flag is set.  Codegen handles both as a list_of with
struct_name=AlarmListing; server pre-computes the per-row ALCD from
(severity_category, active state) before passing the rows in.

COMPLIANCE.md: Alarm Management Additional capability flips .

Tests: 78 cases / 454 assertions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 01:52:11 +02:00
parent 813e011409
commit 88205037ec
4 changed files with 59 additions and 2 deletions
+2 -2
View File
@@ -84,7 +84,7 @@ Legend:
| Variable Data Collection | ✅ | E30 §6.11| — | `DVID` table; values resolvable via `vid_value`. |
| Trace Data Collection | ⬜ | E30 §6.12| S2F23/F24, S6F1/F2 | Not implemented. |
| Status Data Collection | ✅ | E30 §6.13| S1F3/F4, S1F11/F12 | |
| Alarm Management | 🟡 | E30 §6.14| S5F1/F2, S5F3/F4, S5F5/F6 | F1F6 implemented; **S5F7/F8 list-enabled-alarms not implemented**. ALCD bit-7 set/cleared, lower-7 category supported. |
| Alarm Management | | E30 §6.14| S5F1/F2, S5F3/F4, S5F5/F6, S5F7/F8 | Full set: emit, ack, enable/disable, list directory, list enabled. ALCD bit-7 set/cleared, lower-7 category. |
| Remote Control | ✅ | E30 §6.15| S2F41/F42 | Full HCACK 7-value enum + per-parameter CPACKs. |
| Equipment Constants | ✅ | E30 §6.16| S2F13/F14, S2F15/F16, S2F29/F30 | **EAC range validation against min/max is NOT performed** — set always accepts a known ECID; this would reject out-of-range with EAC=4 in a conformant equipment. |
| Process Program Management | 🟡 | E30 §6.17| S7F3/F4, S7F5/F6, S7F19/F20 | Unformatted PP send/request/list. **No E42 enhanced PP**, **no S7F23/F24/F25/F26 PP verify**, **no PPID validation**. PPBODY treated as raw bytes (Binary item). |
@@ -123,7 +123,7 @@ Legend:
| S5F1 / S5F2 | E→H | ✅ | `messages.hpp` | ✅ round-trip |
| S5F3 / S5F4 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
| S5F5 / S5F6 | H→E | ✅ | `messages.hpp` | ✅ in demo |
| S5F7 / S5F8 | H→E | | | — |
| S5F7 / S5F8 | H→E | | catalog | ✅ round-trip |
| S6F1 / S6F2 | E→H | ⬜ | — | — |
| S6F5 / S6F6 | H↔E | ⬜ | — | multi-block |
| S6F7 / S6F8 | H↔E | ⬜ | — | multi-block |
+12
View File
@@ -335,6 +335,18 @@ int main(int argc, char** argv) {
logfn(std::string("S5F3 -> S5F4 ACKC5=") + std::to_string(static_cast<int>(ack)));
return gem::s5f4_enable_alarm_ack(ack);
});
router.on(5, 7, [model, logfn](const s2::Message&) {
std::vector<gem::AlarmListing> rows;
for (const auto& a : model->alarms.all()) {
if (!model->alarms.enabled(a.id)) continue;
const uint8_t alcd = (a.severity_category & 0x7F) |
static_cast<uint8_t>(model->alarms.active(a.id) ? 0x80 : 0x00);
rows.push_back({alcd, a.id, a.text});
}
logfn("S5F7 -> S5F8 (" + std::to_string(rows.size()) + " enabled)");
return gem::s5f8_list_enabled_alarms_data(rows);
});
router.on(5, 5, [model, logfn](const s2::Message& msg) {
auto ids = gem::parse_u4_list_body(msg);
std::vector<gem::Alarm> alarms;
+24
View File
@@ -503,6 +503,30 @@ messages:
# S5F6 is hand-written (per-row ALCD requires an active-callback API).
# S5F7 / S5F8 — List Enabled Alarms. Same wire shape as S5F6 but only
# alarms with bit-7 of their stored "enabled" flag are returned.
- id: S5F7
stream: 5
function: 7
w: true
builder: s5f7_list_enabled_alarms_request
- id: S5F8
stream: 5
function: 8
builder: s5f8_list_enabled_alarms_data
parser: parse_s5f8
body:
kind: list_of
name: items
element:
kind: list
struct_name: AlarmListing
fields:
- {name: alcd, shape: {kind: scalar, item_type: BINARY_BYTE}}
- {name: alid, shape: {kind: scalar, item_type: U4}}
- {name: altx, shape: {kind: scalar, item_type: ASCII}}
# =====================================================================
# S6 — Data collection
# =====================================================================
+21
View File
@@ -151,6 +151,27 @@ TEST_CASE("S9F13 conversation timeout carries MEXP + EDID") {
CHECK(parsed->edid == "EQUIP-01");
}
// ---- S5F7 / S5F8 List Enabled Alarms -----------------------------------
TEST_CASE("S5F7 header-only; S5F8 round-trips alarm listing") {
auto req = s5f7_list_enabled_alarms_request();
CHECK(req.stream == 5);
CHECK(req.function == 7);
CHECK(req.reply_expected);
CHECK_FALSE(req.body.has_value());
std::vector<AlarmListing> rows = {{0x84, 1, "Chiller Temp High"},
{0x01, 2, "Door Open"}};
auto m = s5f8_list_enabled_alarms_data(rows);
auto parsed = parse_s5f8(m);
REQUIRE(parsed.has_value());
REQUIRE(parsed->size() == 2);
CHECK((*parsed)[0].alcd == 0x84);
CHECK((*parsed)[0].alid == 1);
CHECK((*parsed)[1].alcd == 0x01);
CHECK((*parsed)[1].altx == "Door Open");
}
// ---- S1F19 / S1F20 Get GEM Compliance ----------------------------------
TEST_CASE("S1F19 is header-only and S1F20 round-trips capability list") {