Files
secs-gem/apps/secs_gemd.cpp
raphael 42044e92e2
tests / build-and-test (push) Successful in 2m42s
tests / thread-sanitizer (push) Successful in 2m50s
tests / tshark-dissector (push) Successful in 2m24s
tests / secs4j-interop (push) Successful in 37s
tests / python-interop (push) Successful in 2m56s
tests / libfuzzer (push) Successful in 3m44s
ci(interop): one-command external-validation suite + CI lanes for the daemon
tools/run_interop.sh runs ALL nine validation steps with a PASS/FAIL summary:
build, unit (464), daemon-unit (41), secsgem-py host vs server (31 checks),
secs_conformance (47), gRPC+secsgem-py daemon bridge, spool persistence
across restart, tshark HSMS dissector, secs4java8 (55 checks). Verified green
end-to-end. The unit suite is partly self-referential (our parsers validate
our builders); these external validators are the real oracle — now they run
with one command instead of by hand. Two bugs found by running it: unbounded
ninja at -O3 OOM-kills cc1plus in memory-constrained Docker VMs (build with
-j 2) and bash-3.2 lacks negative array subscripts.

CI: grpc deps added to the build job so secs_gemd + secs_gemd_tests build and
RUN in CI (previously the daemon silently dropped out — now fails loudly if
missing), plus a python-interop lane running py-host/conformance/daemon
harnesses against localhost in one container (no docker-in-docker).

Service hardening while in there: reject proto Values with no kind set at
the RPC edge (previously silently became ASCII ""), TODO markers for list
element formats and daemon graceful shutdown. New tests: unset-Value guard
+ a property test iterating ALL configured variables via gRPC asserting each
keeps its declared SECS-II format (daemon tests 16 -> 41 assertions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:08:37 +02:00

78 lines
3.0 KiB
C++

// secs_gemd — the SECS/GEM daemon.
//
// Runs the GEM engine (EquipmentRuntime + the default handlers) on a background
// thread, owning the HSMS link to the host, and exposes a small name-based gRPC
// API (proto/secsgem/v1/equipment.proto) so a tool's software — in any language
// — can drive the equipment without linking C++ or knowing SEMI.
//
// Increment 1: the universal "report state to the host" RPCs (SetVariables,
// FireEvent) plus control-state visibility. Alarms, GetVariables, and the
// host->tool Subscribe command stream follow (see docs/DAEMON_ROADMAP.md).
#include <grpcpp/grpcpp.h>
#include <cstdint>
#include <iostream>
#include <memory>
#include <string>
#include "equipment_service.hpp"
#include "secsgem/gem/default_handlers.hpp"
#include "secsgem/gem/runtime.hpp"
namespace gem = secsgem::gem;
namespace {
std::string arg(int argc, char** argv, const std::string& key, const std::string& def) {
for (int i = 1; i + 1 < argc; ++i)
if (key == argv[i]) return argv[i + 1];
return def;
}
} // namespace
int main(int argc, char** argv) {
const std::string hsms_port = arg(argc, argv, "--port", "5000");
const std::string grpc_addr = arg(argc, argv, "--grpc", "0.0.0.0:50051");
const std::string cfgdir = arg(argc, argv, "--config-dir", "data");
gem::EquipmentRuntime::Config cfg;
cfg.equipment_yaml = cfgdir + "/equipment.yaml";
cfg.control_state_yaml = cfgdir + "/control_state.yaml";
cfg.process_job_yaml = cfgdir + "/process_job_state.yaml";
cfg.control_job_yaml = cfgdir + "/control_job_state.yaml";
cfg.port = static_cast<uint16_t>(std::stoi(hsms_port));
cfg.log = [](const std::string& m) { std::cout << "[gemd] " << m << std::endl; };
std::unique_ptr<gem::EquipmentRuntime> rt;
try {
rt = std::make_unique<gem::EquipmentRuntime>(cfg);
} catch (const std::exception& e) {
std::cerr << "[gemd] config error: " << e.what() << std::endl;
return 1;
}
gem::register_default_handlers(*rt);
// Construct the service before starting the io thread: its constructor
// snapshots the name->id/format maps from the model (see the service's
// threading note).
secsgem::daemon::EquipmentService service(*rt);
rt->run_async(); // engine + HSMS link on a background thread
grpc::ServerBuilder builder;
builder.AddListeningPort(grpc_addr, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
if (!server) {
std::cerr << "[gemd] failed to start gRPC server on " << grpc_addr << std::endl;
return 1;
}
std::cout << "[gemd] gRPC on " << grpc_addr
<< "; HSMS equipment on :" << hsms_port << std::endl;
// TODO(daemon): graceful shutdown — handle SIGTERM/SIGINT by calling
// server->Shutdown() and rt->stop() so in-flight RPCs drain and the spool
// journal flushes, instead of dying mid-write when supervised (systemd/
// docker stop). See DAEMON_ROADMAP Phase E.
server->Wait();
return 0;
}