docs: correct drifted and fabricated APIs in chapters 13/17/35/51
An audit of doc code blocks against the real headers found APIs that do not exist in the codebase, presented as authoritative walkthroughs: - ch35 (dispatch): an entirely fabricated callback architecture — HostCommandRegistry::set_emit_ceid_handler, CommandOutcome, emit_ceids. Rewritten to the real Spec/Result/dispatch + the new set_handler hook. - ch13 (E30): wrong store names — EventStore/ReportStore -> EventReportSubscriptions, SvidStore -> StatusVariableStore, AlarmStore/AlarmDispatcher -> AlarmRegistry, ClockStore -> Clock, TerminalServiceStore -> (no store), in both the capability tables and the worked S2F33 example. - ch17 (E116): EptStore/seconds/bucket_ -> EptStateMachine/milliseconds/buckets_. - ch51 (extending): stale host-command handler -> the real set_handler signature. Verified clean by grep: no fabricated symbols remain in docs/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-10
@@ -146,7 +146,7 @@ these isn't GEM-compliant, end of story.
|
|||||||
| State models | — | `ControlStateMachine`, `CommunicationStateMachine` |
|
| State models | — | `ControlStateMachine`, `CommunicationStateMachine` |
|
||||||
| Equipment Processing States | — | `ControlTransitionTable` (vendor supplies concrete states) |
|
| Equipment Processing States | — | `ControlTransitionTable` (vendor supplies concrete states) |
|
||||||
| Host-Initiated S1F13/F14 | S1F13 / S1F14 | `gem::CommunicationStateMachine` |
|
| Host-Initiated S1F13/F14 | S1F13 / S1F14 | `gem::CommunicationStateMachine` |
|
||||||
| Event Notification | S6F11 / S6F12 | `EventStore` + `EquipmentDataModel::compose_reports_for` |
|
| Event Notification | S6F11 / S6F12 | `EventReportSubscriptions` + `EquipmentDataModel::compose_reports_for` |
|
||||||
| On-Line Identification | S1F1 / S1F2 | Router handler in `apps/secs_server.cpp` |
|
| On-Line Identification | S1F1 / S1F2 | Router handler in `apps/secs_server.cpp` |
|
||||||
| Error Messages | S9F1/F3/F5/F7/F9/F11 | `Connection::emit_s9` + `Router::dispatch_with_s9` |
|
| Error Messages | S9F1/F3/F5/F7/F9/F11 | `Connection::emit_s9` + `Router::dispatch_with_s9` |
|
||||||
| Documentation | S1F19/F20, S1F21/F22, S1F23/F24 | `gem::compliance` / namelist handlers |
|
| Documentation | S1F19/F20, S1F21/F22, S1F23/F24 | `gem::compliance` / namelist handlers |
|
||||||
@@ -166,17 +166,17 @@ the SEMI spec, but mandatory for procurement."
|
|||||||
| Additional | Messages | Code |
|
| Additional | Messages | Code |
|
||||||
|---------------------------------------|---------------------------------------------------------------------------|-----------------------------------------------|
|
|---------------------------------------|---------------------------------------------------------------------------|-----------------------------------------------|
|
||||||
| Establish Communications | S1F13/F14 | `CommunicationStateMachine` (also in Fundamentals) |
|
| Establish Communications | S1F13/F14 | `CommunicationStateMachine` (also in Fundamentals) |
|
||||||
| Dynamic Event Report Configuration | S2F33/F34, S2F35/F36, S2F37/F38 | `ReportStore`, `EventStore` |
|
| Dynamic Event Report Configuration | S2F33/F34, S2F35/F36, S2F37/F38 | `EventReportSubscriptions` |
|
||||||
| Variable Data Collection | S1F21/F22 + DVID values via `vid_value` | `DataVariableStore` |
|
| Variable Data Collection | S1F21/F22 + DVID values via `vid_value` | `DataVariableStore` |
|
||||||
| Trace Data Collection | S2F23/F24, S6F1/F2 | `TraceStore` |
|
| Trace Data Collection | S2F23/F24, S6F1/F2 | `TraceStore` |
|
||||||
| Status Data Collection | S1F3/F4, S1F11/F12 | `SvidStore` |
|
| Status Data Collection | S1F3/F4, S1F11/F12 | `StatusVariableStore` |
|
||||||
| Alarm Management | S5F1/F2, S5F3/F4, S5F5/F6, S5F7/F8 | `AlarmStore`, `AlarmDispatcher` |
|
| Alarm Management | S5F1/F2, S5F3/F4, S5F5/F6, S5F7/F8 | `AlarmRegistry` |
|
||||||
| Remote Control | S2F41/F42, S2F49/F50, S2F21/F22 | `HostCommandRegistry` |
|
| Remote Control | S2F41/F42, S2F49/F50, S2F21/F22 | `HostCommandRegistry` |
|
||||||
| Equipment Constants | S2F13/F14, S2F15/F16, S2F29/F30 | `EquipmentConstantStore` |
|
| Equipment Constants | S2F13/F14, S2F15/F16, S2F29/F30 | `EquipmentConstantStore` |
|
||||||
| Process Program Management | S7F1–F6, S7F17–F20, S7F23–F26 | `RecipeStore` |
|
| Process Program Management | S7F1–F6, S7F17–F20, S7F23–F26 | `RecipeStore` |
|
||||||
| Material Movement | (handled by E40 + E94 + E87 + E90 + E157) | see chapters 14–16 |
|
| Material Movement | (handled by E40 + E94 + E87 + E90 + E157) | see chapters 14–16 |
|
||||||
| Equipment Terminal Services | S10F1/F2, S10F3/F4, S10F5/F6 | `TerminalServiceStore` |
|
| Equipment Terminal Services | S10F1/F2, S10F3/F4, S10F5/F6 | S10F1–F6 Router handlers (no dedicated store) |
|
||||||
| Clock | S2F17/F18, S2F31/F32 | `ClockStore` (+ E148 in chapter 19) |
|
| Clock | S2F17/F18, S2F31/F32 | `Clock` (+ E148 in chapter 19) |
|
||||||
| Limits Monitoring | S2F45/F46, S2F47/F48 | `LimitMonitorStore` |
|
| Limits Monitoring | S2F45/F46, S2F47/F48 | `LimitMonitorStore` |
|
||||||
| Spooling | S2F43/F44, S6F23/F24, S6F25/F26 | `SpoolStore` (persistent file-backed journal) |
|
| Spooling | S2F43/F44, S6F23/F24, S6F25/F26 | `SpoolStore` (persistent file-backed journal) |
|
||||||
|
|
||||||
@@ -193,13 +193,13 @@ Pick **Event Notification** — the canonical GEM scenario:
|
|||||||
|
|
||||||
```
|
```
|
||||||
1. Host sends S2F33 (DefineReport): "RPTID 100 = [SVID 1, SVID 5]"
|
1. Host sends S2F33 (DefineReport): "RPTID 100 = [SVID 1, SVID 5]"
|
||||||
2. Equipment stores the definition in ReportStore; replies S2F34(DRACK=0).
|
2. Equipment stores the definition in EventReportSubscriptions; replies S2F34(DRACK=0).
|
||||||
3. Host sends S2F35 (LinkEvent): "CEID 300 → RPTID 100"
|
3. Host sends S2F35 (LinkEvent): "CEID 300 → RPTID 100"
|
||||||
4. Equipment stores the link in EventStore; replies S2F36(LRACK=0).
|
4. Equipment stores the link in EventReportSubscriptions; replies S2F36(LRACK=0).
|
||||||
5. Host sends S2F37 (EnableEvent CEED=true, CEID=[300])
|
5. Host sends S2F37 (EnableEvent CEED=true, CEID=[300])
|
||||||
6. Equipment marks CEID 300 enabled in EventStore; replies S2F38(ERACK=0).
|
6. Equipment marks CEID 300 enabled in EventReportSubscriptions; replies S2F38(ERACK=0).
|
||||||
7. Later: some FSM transition decides to fire CEID 300.
|
7. Later: some FSM transition decides to fire CEID 300.
|
||||||
compose_reports_for(300) walks EventStore → ReportStore → SvidStore
|
compose_reports_for(300) walks EventReportSubscriptions → StatusVariableStore
|
||||||
and assembles {RPTID=100, V=[svid1_val, svid5_val]}.
|
and assembles {RPTID=100, V=[svid1_val, svid5_val]}.
|
||||||
8. Equipment emits S6F11 with the assembled body.
|
8. Equipment emits S6F11 with the assembled body.
|
||||||
9. Host replies S6F12(ACKC6=0).
|
9. Host replies S6F12(ACKC6=0).
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ tracks: **how long was the equipment in each state**.
|
|||||||
The store accumulates time-buckets:
|
The store accumulates time-buckets:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
class EptStore {
|
class EptStateMachine {
|
||||||
// For each EptState, accumulated wall-clock duration.
|
// For each EptState, accumulated wall-clock duration.
|
||||||
std::array<std::chrono::seconds, 6> bucket_;
|
std::array<std::chrono::milliseconds, 6> buckets_{};
|
||||||
|
|
||||||
// Current state + when it was entered (so the dwell so far is
|
// Current state + when it was entered (so the dwell so far is
|
||||||
// counted as part of the current bucket on read).
|
// counted as part of the current bucket on read).
|
||||||
|
|||||||
@@ -147,14 +147,16 @@ inline secs2::Message s2f42(uint8_t hcack, /* per-param acks */) {
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// apps/secs_server.cpp
|
// apps/secs_server.cpp
|
||||||
router->on(2, 41, [model](const secs2::Message& m) {
|
router.on(2, 41, [model, emit_event, emit_alarm_set](const s2::Message& msg) {
|
||||||
auto cmd = messages::parse_s2f41(m.body());
|
auto cmd = gem::parse_s2f41(msg);
|
||||||
if (!cmd) {
|
if (!cmd) // body didn't parse
|
||||||
// Body didn't parse — reply S2F42 with HCACK = 1 (invalid).
|
return gem::s2f42_host_command_ack(gem::HostCmdAck::ParameterInvalid, {});
|
||||||
return messages::s2f42(1, {});
|
auto result = model->commands.dispatch(cmd->rcmd, cmd->params);
|
||||||
|
if (result.ack == gem::HostCmdAck::Accept) { // apply declared side effects
|
||||||
|
if (result.emit_ceid) emit_event(*result.emit_ceid);
|
||||||
|
if (result.set_alarm) emit_alarm_set(*result.set_alarm);
|
||||||
}
|
}
|
||||||
auto outcome = model->commands.dispatch(cmd->rcmd, cmd->params);
|
return gem::s2f42_host_command_ack(result.ack, {});
|
||||||
return messages::s2f42(static_cast<uint8_t>(outcome.ack), outcome.cpacks);
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -164,33 +166,62 @@ router->on(2, 41, [model](const secs2::Message& m) {
|
|||||||
// include/secsgem/gem/store/host_commands.hpp
|
// include/secsgem/gem/store/host_commands.hpp
|
||||||
class HostCommandRegistry {
|
class HostCommandRegistry {
|
||||||
public:
|
public:
|
||||||
CommandOutcome dispatch(const std::string& rcmd, const ParamList& params) {
|
// Declarative default + optional side effects, loaded from equipment.yaml.
|
||||||
auto it = commands_.find(rcmd);
|
struct Spec { HostCmdAck ack; std::optional<uint32_t> emit_ceid, set_alarm; /* … */ };
|
||||||
if (it == commands_.end()) return {HostCmdAck::InvalidCommand, ...};
|
struct Result { HostCmdAck ack; std::optional<uint32_t> emit_ceid, set_alarm; /* … */ };
|
||||||
const auto& cmd = it->second;
|
// Application behaviour: runs real work and decides the ack (see §4).
|
||||||
// Apply configured side effects: emit_ceid, set_alarm, …
|
using Handler = std::function<HostCmdAck(const std::string& rcmd,
|
||||||
for (auto ceid : cmd.emit_ceids) on_emit_ceid_(ceid);
|
const std::vector<CommandParameter>&)>;
|
||||||
for (auto alid : cmd.set_alarms) alarm_registry_->set(alid);
|
|
||||||
return {cmd.default_ack, ...};
|
void register_command(std::string rcmd, Spec spec); // wired from YAML
|
||||||
|
void set_handler(std::string rcmd, Handler h); // wired from application code
|
||||||
|
|
||||||
|
Result dispatch(const std::string& rcmd,
|
||||||
|
const std::vector<CommandParameter>& params) const {
|
||||||
|
auto it = by_rcmd_.find(rcmd);
|
||||||
|
if (it == by_rcmd_.end())
|
||||||
|
return {HostCmdAck::InvalidCommand, {}, {}};
|
||||||
|
HostCmdAck ack = it->second.ack; // declarative default
|
||||||
|
if (auto h = handlers_.find(rcmd); h != handlers_.end() && h->second)
|
||||||
|
ack = h->second(rcmd, params); // application code overrides it
|
||||||
|
return {ack, it->second.emit_ceid, it->second.set_alarm};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. The side-effect dispatcher
|
### 4. Behaviour: declarative default vs. application code
|
||||||
|
|
||||||
Steps in `dispatch` like `on_emit_ceid_(ceid)` call back into
|
`dispatch` settles two things — *what ack the host gets* and *what side
|
||||||
the EAP:
|
effects fire*. They come from two layers:
|
||||||
|
|
||||||
|
**Declarative (YAML).** A row in `equipment.yaml` gives a command a static
|
||||||
|
`ack` plus optional `emit_ceid` / `set_alarm`. Those ride back on the
|
||||||
|
`Result`, and the Router handler (§2) applies them by calling the
|
||||||
|
`emit_event` / `emit_alarm_set` lambdas — which `asio::post` onto the
|
||||||
|
io_context and then build the S6F11 / S5F1. Fine for a fixed mapping
|
||||||
|
("FAULT always raises alarm 1").
|
||||||
|
|
||||||
|
**Application behaviour (the hook).** A static ack can't *do* anything —
|
||||||
|
start a recipe, read the command's parameters, decide based on tool
|
||||||
|
state. For that, register a handler. Its return value becomes the ack:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// Set up at startup:
|
// Set up at startup, alongside register_command:
|
||||||
model->commands.set_emit_ceid_handler([conn, model](uint32_t ceid) {
|
model->commands.set_handler("START",
|
||||||
if (!model->is_event_enabled(ceid)) return;
|
[&](const std::string&, const std::vector<gem::CommandParameter>& params) {
|
||||||
auto reports = model->compose_reports_for(ceid);
|
if (tool.busy()) return gem::HostCmdAck::CannotDoNow; // reject
|
||||||
auto msg = build_s6f11(ceid, reports);
|
tool.run_recipe(find_param(params, "PPID")); // real work
|
||||||
conn->send_data(std::move(msg));
|
return gem::HostCmdAck::Accept;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The same hook covers `S2F41`, `S2F21`, and `S2F49`, since all three call
|
||||||
|
`dispatch`. Because the Router applies declared side effects only on
|
||||||
|
`Accept`, a rejecting handler suppresses them for free; with no handler
|
||||||
|
the command stays purely declarative. This is the seam application code
|
||||||
|
— and the planned Python binding's `@on("START")` — uses to put real
|
||||||
|
behaviour behind a host command.
|
||||||
|
|
||||||
### 5. The wire
|
### 5. The wire
|
||||||
|
|
||||||
`conn->send_data(s6f11)` walks through `secs2::encode` →
|
`conn->send_data(s6f11)` walks through `secs2::encode` →
|
||||||
|
|||||||
@@ -118,14 +118,14 @@ set-alarm, register a custom handler:
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// At startup:
|
// At startup:
|
||||||
model->commands.register_handler("VENT",
|
model->commands.set_handler("VENT",
|
||||||
[model](const ParamList& params) -> CommandOutcome {
|
[model](const std::string& rcmd,
|
||||||
|
const std::vector<gem::CommandParameter>& params) {
|
||||||
// Actually vent the chamber here.
|
// Actually vent the chamber here.
|
||||||
if (!vacuum_safe_to_vent()) {
|
if (!vacuum_safe_to_vent())
|
||||||
return {HostCmdAck::CannotPerformNow, {}};
|
return gem::HostCmdAck::CannotDoNow;
|
||||||
}
|
|
||||||
hardware_vent_chamber();
|
hardware_vent_chamber();
|
||||||
return {HostCmdAck::Accept, {}};
|
return gem::HostCmdAck::Accept;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user