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
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>
78 lines
3.0 KiB
C++
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;
|
|
}
|