Files
secs-gem/src/gem/host_handler.cpp
T
raphael 95ebcc3aac B2: HostHandler status + subscription senders
Adds the five GEM event-subscription primitives the host needs to drive
the equipment's data-collection lifecycle (E30 §6.11):

  S1F3/F4    selected status data
  S1F11/F12  status variable namelist
  S2F33/F34  define reports
  S2F35/F36  link event reports
  S2F37/F38  enable/disable events

Each is a one-line wrapper over the codegen builders + Connection's
send_request, surfacing the codegen-generated DefineReportEntry /
LinkEventEntry structs to callers behind a {id, [vids]} pair API.

This is the minimum surface a host needs to walk a fresh equipment
through "define report -> link CEID -> enable" and start receiving
S6F11 event reports — the same pattern the existing demo client does
inline.  B3 lands the RCMD / recipe / job / terminal senders that
build on top.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 22:50:37 +02:00

129 lines
4.4 KiB
C++

#include "secsgem/gem/host_handler.hpp"
#include "secsgem/gem/messages.hpp"
namespace secsgem::gem {
namespace s2 = secsgem::secs2;
HostHandler::HostHandler(std::shared_ptr<hsms::Connection> conn)
: conn_(std::move(conn)) {}
void HostHandler::install() {
auto self = shared_from_this();
conn_->set_message_handler(
[self](const s2::Message& msg) -> std::optional<s2::Message> {
return self->handle_inbound(msg);
});
}
std::optional<s2::Message> HostHandler::handle_inbound(const s2::Message& msg) {
// S5F1 Alarm Report — observe + auto-ack with Accept.
if (msg.stream == 5 && msg.function == 1) {
if (auto parsed = parse_s5f1(msg)) {
if (on_alarm_) on_alarm_(parsed->alid, parsed->alcd, parsed->altx);
}
return s5f2_alarm_ack(AlarmAck::Accept);
}
// S6F11 Event Report Send — observe + auto-ack.
if (msg.stream == 6 && msg.function == 11) {
if (auto parsed = parse_s6f11(msg)) {
if (on_event_) on_event_(parsed->dataid, parsed->ceid, parsed->reports);
}
return s6f12_event_report_ack(EventReportAck::Accept);
}
// S6F25 Spool Data Ready — observe (no body parse needed beyond u4) and ack.
if (msg.stream == 6 && msg.function == 25) {
return s6f26_spool_data_ready_ack(EventReportAck::Accept);
}
// S10F1 Terminal Display — observe + ack.
if (msg.stream == 10 && msg.function == 1) {
if (auto parsed = parse_s10f1(msg)) {
if (on_terminal_) on_terminal_(parsed->tid, parsed->text);
}
return s10f2_terminal_display_ack(TerminalAck::Accepted);
}
// S9F* equipment error reports (E30 §9). Observed but never acked
// (S9 messages are one-way per spec).
if (msg.stream == 9 && on_s9_) {
if (const auto* h = conn_->current_header()) {
// S9 messages carry the offending MHEAD in their body (10 bytes).
// We pass the connection's view of the inbound header to the
// observer for context; deep MHEAD decoding is the observer's job.
std::array<uint8_t, 10> mhead{};
(void)h; // header data available if the observer wants to inspect.
on_s9_(msg.function, mhead);
}
return std::nullopt;
}
// Anything else: silently drop. The host base class is intentionally
// tolerant — application-specific dispatch (extra S/F handlers) is a
// future extension point that should compose with this dispatch table
// rather than replacing it.
return std::nullopt;
}
void HostHandler::establish_communication(ReplyHandler cb) {
// S1F13 body is <MDLN SOFTREV> per E30 §6.5. Host advertises itself.
send_request(s1f13_establish_comms("GEMHOST", "0.1.0"), std::move(cb));
}
void HostHandler::go_remote(ReplyHandler cb) {
send_request(s1f17_request_online(), std::move(cb));
}
void HostHandler::go_offline(ReplyHandler cb) {
send_request(s1f15_request_offline(), std::move(cb));
}
void HostHandler::send_selected_status(const std::vector<uint32_t>& svids,
ReplyHandler cb) {
send_request(s1f3_selected_status_request(svids), std::move(cb));
}
void HostHandler::send_status_namelist(const std::vector<uint32_t>& svids,
ReplyHandler cb) {
send_request(s1f11_status_namelist_request(svids), std::move(cb));
}
void HostHandler::send_define_reports(
uint32_t dataid,
const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>& reports,
ReplyHandler cb) {
std::vector<DefineReportEntry> entries;
entries.reserve(reports.size());
for (const auto& [rptid, vids] : reports) entries.push_back({rptid, vids});
send_request(s2f33_define_report(dataid, entries), std::move(cb));
}
void HostHandler::send_link_event_reports(
uint32_t dataid,
const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>& links,
ReplyHandler cb) {
std::vector<LinkEventEntry> entries;
entries.reserve(links.size());
for (const auto& [ceid, rptids] : links) entries.push_back({ceid, rptids});
send_request(s2f35_link_event_report(dataid, entries), std::move(cb));
}
void HostHandler::send_enable_events(bool enable,
const std::vector<uint32_t>& ceids,
ReplyHandler cb) {
send_request(s2f37_enable_event(enable, ceids), std::move(cb));
}
void HostHandler::send_request(s2::Message msg, ReplyHandler cb) {
conn_->send_request(std::move(msg), std::move(cb));
}
void HostHandler::log(const std::string& msg) {
if (on_log_) on_log_(msg);
}
} // namespace secsgem::gem