feat(gem): add host-command behaviour hook to HostCommandRegistry

Host commands were declarative-only: dispatch() returned the YAML-defined
HCACK plus side effects, and ignored the command parameters entirely (the
param list was a commented-out argument). Equipment could acknowledge a
command but never run anything in response — the pvd_tool example worked
around this by hard-coding behaviour in a C++ router handler.

Add set_handler(rcmd, fn): a registered handler receives the live CPNAME/
CPVAL parameters and returns the HCACK, overriding the declarative default.
Live on S2F41/F21/F49 via the shared dispatch(). No handler => byte-for-byte
the previous declarative behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 18:00:44 +02:00
parent dae6bfd747
commit 0090791968
2 changed files with 92 additions and 2 deletions
+25 -2
View File
@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <string>
@@ -48,19 +49,41 @@ class HostCommandRegistry {
std::optional<bool> force_spool;
};
// Behaviour hook: vendor code that actually *does* something when the host
// sends a command (run a recipe, move hardware, validate the parameters).
// The handler receives the live <CPNAME, CPVAL> parameter list and returns
// the HCACK to send back; returning anything other than Accept suppresses
// the Spec's declarative side effects, since the router only applies
// emit_ceid / set_alarm / force_spool on Accept. Without a handler a
// command stays purely declarative (the YAML Spec's static ack), which is
// the pre-existing behaviour.
using Handler = std::function<HostCmdAck(
const std::string& rcmd, const std::vector<CommandParameter>& params)>;
void register_command(std::string rcmd, Spec spec) {
by_rcmd_.insert_or_assign(std::move(rcmd), std::move(spec));
}
// Attach behaviour to an already-registered command. Independent of
// register_command so the data dictionary (YAML) and the behaviour
// (application code) can be wired separately.
void set_handler(std::string rcmd, Handler h) {
handlers_.insert_or_assign(std::move(rcmd), std::move(h));
}
bool has(const std::string& rcmd) const { return by_rcmd_.count(rcmd) > 0; }
bool has_handler(const std::string& rcmd) const { return handlers_.count(rcmd) > 0; }
Result dispatch(const std::string& rcmd,
const std::vector<CommandParameter>& /*params*/) const {
const std::vector<CommandParameter>& params) const {
auto it = by_rcmd_.find(rcmd);
if (it == by_rcmd_.end()) return {HostCmdAck::InvalidCommand, std::nullopt, std::nullopt, std::nullopt};
return {it->second.ack, it->second.emit_ceid, it->second.set_alarm, it->second.force_spool};
HostCmdAck ack = it->second.ack;
if (auto hit = handlers_.find(rcmd); hit != handlers_.end() && hit->second)
ack = hit->second(rcmd, params); // vendor behaviour decides the outcome
return {ack, it->second.emit_ceid, it->second.set_alarm, it->second.force_spool};
}
private:
std::map<std::string, Spec> by_rcmd_;
std::map<std::string, Handler> handlers_;
};
} // namespace secsgem::gem