A2: S2F49/F50 Enhanced Remote Command
Adds the OBJSPEC-scoped sibling of S2F41 with extended per-parameter ack shape (CPACK + CEPACK). Wire: S2F49 body <DATAID OBJSPEC RCMD <L,n <CPNAME CPVAL>>> S2F50 body <HCACK <L,n <CPNAME CPACK CEPACK>>> Server delegates to the existing HostCommandRegistry, logs OBJSPEC for audit, and currently returns empty cpacks (all-OK). Per-parameter failures will be wired when the command registry grows CEPACK-level validation; this commit is the catalog + dispatch scaffolding. secsgem-py defines these in its catalog but never dispatches them; this puts the C++ port marginally ahead on remote-command coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -175,6 +175,7 @@ YAML/handler extensions, not surgery):
|
|||||||
| S5F13 / S5F14 | H→E | ✅ | catalog | ✅ round-trip (exception recover request) |
|
| S5F13 / S5F14 | H→E | ✅ | catalog | ✅ round-trip (exception recover request) |
|
||||||
| S5F15 / S5F16 | E→H | ✅ | catalog | ✅ round-trip (exception recover complete) |
|
| S5F15 / S5F16 | E→H | ✅ | catalog | ✅ round-trip (exception recover complete) |
|
||||||
| S5F17 / S5F18 | H→E | ✅ | catalog | ✅ round-trip (exception recover abort) |
|
| S5F17 / S5F18 | H→E | ✅ | catalog | ✅ round-trip (exception recover abort) |
|
||||||
|
| S2F49 / S2F50 | H→E | ✅ | catalog + server | ✅ round-trip + dispatch (enhanced remote command, OBJSPEC + per-CP CPACK/CEPACK) |
|
||||||
| S6F1 / S6F2 | E→H | ✅ | catalog | ✅ round-trip |
|
| S6F1 / S6F2 | E→H | ✅ | catalog | ✅ round-trip |
|
||||||
| S6F11 / S6F12 | E→H | ✅ | catalog | ✅ round-trip + demo |
|
| S6F11 / S6F12 | E→H | ✅ | catalog | ✅ round-trip + demo |
|
||||||
| S6F23 / S6F24 | H→E | ✅ | catalog | ✅ round-trip + demo |
|
| S6F23 / S6F24 | H→E | ✅ | catalog | ✅ round-trip + demo |
|
||||||
|
|||||||
@@ -389,6 +389,30 @@ int main(int argc, char** argv) {
|
|||||||
return gem::s2f42_host_command_ack(result.ack, {});
|
return gem::s2f42_host_command_ack(result.ack, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// S2F49 — Enhanced Remote Command. OBJSPEC scopes the command at a
|
||||||
|
// specific object instance (e.g. a CJ or PJ id); for now we delegate
|
||||||
|
// to the same command registry as S2F41 and surface OBJSPEC in the
|
||||||
|
// log so downstream tooling can audit it. The richer CPACK/CEPACK
|
||||||
|
// shape lets us return per-parameter outcomes; until a command in
|
||||||
|
// the registry produces per-CP failures we just reply with an empty
|
||||||
|
// cpacks list, matching the spec's "all OK" interpretation.
|
||||||
|
router.on(2, 49, [model, logfn, emit_event, emit_alarm_set](const s2::Message& msg) {
|
||||||
|
auto cmd = gem::parse_s2f49(msg);
|
||||||
|
if (!cmd) return gem::s2f50_enhanced_host_command_ack(gem::HostCmdAck::ParameterInvalid, {});
|
||||||
|
auto result = model->commands.dispatch(cmd->rcmd, cmd->params);
|
||||||
|
logfn("S2F49 DATAID=" + std::to_string(cmd->dataid) +
|
||||||
|
" OBJSPEC=" + cmd->objspec + " RCMD=" + cmd->rcmd +
|
||||||
|
" -> S2F50 HCACK=" + std::to_string(static_cast<int>(result.ack)));
|
||||||
|
if (result.ack == gem::HostCmdAck::Accept) {
|
||||||
|
if (result.emit_ceid) emit_event(*result.emit_ceid);
|
||||||
|
if (result.set_alarm) emit_alarm_set(*result.set_alarm);
|
||||||
|
if (result.force_spool) {
|
||||||
|
model->spool.set_force_spool(*result.force_spool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gem::s2f50_enhanced_host_command_ack(result.ack, {});
|
||||||
|
});
|
||||||
|
|
||||||
router.on(2, 23, [model, logfn](const s2::Message& msg) {
|
router.on(2, 23, [model, logfn](const s2::Message& msg) {
|
||||||
auto req = gem::parse_s2f23(msg);
|
auto req = gem::parse_s2f23(msg);
|
||||||
auto ack = gem::TraceAck::Accept;
|
auto ack = gem::TraceAck::Accept;
|
||||||
|
|||||||
@@ -433,6 +433,56 @@ messages:
|
|||||||
- {name: name, shape: {kind: scalar, item_type: ASCII}}
|
- {name: name, shape: {kind: scalar, item_type: ASCII}}
|
||||||
- {name: code, shape: {kind: scalar, item_type: BINARY_BYTE}}
|
- {name: code, shape: {kind: scalar, item_type: BINARY_BYTE}}
|
||||||
|
|
||||||
|
# S2F49 / S2F50 — Enhanced Remote Command (E30 §6.4, E5 §10.4). Same
|
||||||
|
# intent as S2F41/F42 but routes a command at a specific object via
|
||||||
|
# OBJSPEC, carries a DATAID transaction handle, and the reply uses
|
||||||
|
# extended per-CP acks (CPACK + CEPACK) so the equipment can flag both
|
||||||
|
# "name unknown" (CPACK) and "value invalid" (CEPACK) independently.
|
||||||
|
- id: S2F49
|
||||||
|
stream: 2
|
||||||
|
function: 49
|
||||||
|
w: true
|
||||||
|
builder: s2f49_enhanced_host_command
|
||||||
|
parser: parse_s2f49
|
||||||
|
body:
|
||||||
|
kind: list
|
||||||
|
struct_name: EnhancedHostCommand
|
||||||
|
fields:
|
||||||
|
- {name: dataid, shape: {kind: scalar, item_type: U4}}
|
||||||
|
- {name: objspec, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: rcmd, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- name: params
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: CommandParameter
|
||||||
|
external_struct: true
|
||||||
|
fields:
|
||||||
|
- {name: name, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: value, shape: {kind: scalar, item_type: ITEM}}
|
||||||
|
|
||||||
|
- id: S2F50
|
||||||
|
stream: 2
|
||||||
|
function: 50
|
||||||
|
builder: s2f50_enhanced_host_command_ack
|
||||||
|
parser: parse_s2f50
|
||||||
|
body:
|
||||||
|
kind: list
|
||||||
|
struct_name: EnhancedHostCommandReply
|
||||||
|
fields:
|
||||||
|
- {name: hcack, shape: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck}}
|
||||||
|
- name: cpacks
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: EnhancedCommandParameterAck
|
||||||
|
fields:
|
||||||
|
- {name: name, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: cpack, shape: {kind: scalar, item_type: BINARY_BYTE}}
|
||||||
|
- {name: cepack, shape: {kind: scalar, item_type: BINARY_BYTE}}
|
||||||
|
|
||||||
# S2F23 / S2F24 — Trace Initialize Send (E30 §6.12). Host requests
|
# S2F23 / S2F24 — Trace Initialize Send (E30 §6.12). Host requests
|
||||||
# periodic SVID sampling: every DSPER, equipment captures the SVID
|
# periodic SVID sampling: every DSPER, equipment captures the SVID
|
||||||
# values; every REPGSZ samples it emits an S6F1 batch; after TOTSMP
|
# values; every REPGSZ samples it emits an S6F1 batch; after TOTSMP
|
||||||
|
|||||||
@@ -157,6 +157,37 @@ TEST_CASE("S2F42 round-trip with HCACK and CPACKs") {
|
|||||||
CHECK(parsed->cpacks[1].code == 3);
|
CHECK(parsed->cpacks[1].code == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("S2F49 / S2F50 enhanced remote command round-trip") {
|
||||||
|
std::vector<CommandParameter> params = {
|
||||||
|
{"LOTID", s2::Item::ascii("LOT-99")},
|
||||||
|
{"PPID", s2::Item::ascii("RECIPE-B")},
|
||||||
|
};
|
||||||
|
auto m = s2f49_enhanced_host_command(/*dataid=*/7, "CJ-1", "START", params);
|
||||||
|
CHECK(m.stream == 2);
|
||||||
|
CHECK(m.function == 49);
|
||||||
|
CHECK(m.reply_expected);
|
||||||
|
|
||||||
|
auto parsed = parse_s2f49(m);
|
||||||
|
REQUIRE(parsed.has_value());
|
||||||
|
CHECK(parsed->dataid == 7);
|
||||||
|
CHECK(parsed->objspec == "CJ-1");
|
||||||
|
CHECK(parsed->rcmd == "START");
|
||||||
|
REQUIRE(parsed->params.size() == 2);
|
||||||
|
CHECK(parsed->params[0].name == "LOTID");
|
||||||
|
CHECK(parsed->params[0].value == s2::Item::ascii("LOT-99"));
|
||||||
|
|
||||||
|
auto ack = s2f50_enhanced_host_command_ack(
|
||||||
|
HostCmdAck::ParameterInvalid,
|
||||||
|
{{"LOTID", 0, 0}, {"PPID", 3, 1}});
|
||||||
|
auto preply = parse_s2f50(ack);
|
||||||
|
REQUIRE(preply.has_value());
|
||||||
|
CHECK(preply->hcack == HostCmdAck::ParameterInvalid);
|
||||||
|
REQUIRE(preply->cpacks.size() == 2);
|
||||||
|
CHECK(preply->cpacks[1].name == "PPID");
|
||||||
|
CHECK(preply->cpacks[1].cpack == 3);
|
||||||
|
CHECK(preply->cpacks[1].cepack == 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("S2F42 no-params variant") {
|
TEST_CASE("S2F42 no-params variant") {
|
||||||
auto m = s2f42_host_command_ack(HostCmdAck::Accept, {});
|
auto m = s2f42_host_command_ack(HostCmdAck::Accept, {});
|
||||||
auto parsed = parse_s2f42(m);
|
auto parsed = parse_s2f42(m);
|
||||||
|
|||||||
Reference in New Issue
Block a user