Files
secs-gem/apps/secs_gemd.cpp
T
raphael 99bfa794fc fix(daemon): honour declared SECS-II formats + make service thread-safe
Audit fixes for two real bugs in the gRPC service:

1. Format compliance: to_item() wrote F8/I8 regardless of the variable's
   declared wire format, so values contradicted the S1F11/S1F21 namelists
   (ChamberPressure is F4, WaferCounter U4; the interop trace showed <F8 2.5>
   on the wire). Conversion now targets the declared format — verified
   end-to-end: secsgem-py now receives <F4 2.5> in S6F11.

2. Thread safety: gRPC handler threads called resolve_variable/resolve_event,
   copying live store entries (including Item values) while the io thread
   mutates them. The service now snapshots the immutable name->id/format maps
   at construction (before run_async, per the documented ordering); all writes
   already post to the io thread. Remaining known narrow race (GetControlState
   enum read) documented in DAEMON_ROADMAP.

Also: drop a stale tools/run_interop.sh reference from docker-compose.yml.
Tests: daemon in-process 16/16 (new F4/U4 format assertions), core 459/459,
secsgem-py interop green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:35:53 +02:00

74 lines
2.7 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;
server->Wait();
return 0;
}