// 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. // // This is increment 1: the universal "report state to the host" RPCs plus // control-state visibility. Events, alarms, and the host->tool Subscribe stream // follow (see docs/DAEMON_ROADMAP.md). #include #include #include #include #include #include "secsgem/gem/default_handlers.hpp" #include "secsgem/gem/name_index.hpp" #include "secsgem/gem/runtime.hpp" #include "secsgem/secs2/item.hpp" #include "secsgem/v1/equipment.grpc.pb.h" #include "secsgem/v1/equipment.pb.h" namespace gem = secsgem::gem; namespace s2 = secsgem::secs2; namespace pb = secsgem::v1; namespace { // proto Value -> SECS-II Item. Increment 1 uses a direct mapping; format-aware // conversion (honouring the variable's declared wire format) is a refinement. s2::Item to_item(const pb::Value& v) { switch (v.kind_case()) { case pb::Value::kText: return s2::Item::ascii(v.text()); case pb::Value::kInteger: return s2::Item::i8(static_cast(v.integer())); case pb::Value::kReal: return s2::Item::f8(v.real()); case pb::Value::kBoolean: return s2::Item::boolean(v.boolean()); default: return s2::Item::ascii(""); } } pb::ControlState::State to_proto_state(gem::ControlState s) { switch (s) { case gem::ControlState::EquipmentOffline: return pb::ControlState::EQUIPMENT_OFFLINE; case gem::ControlState::AttemptOnline: return pb::ControlState::ATTEMPT_ONLINE; case gem::ControlState::HostOffline: return pb::ControlState::HOST_OFFLINE; case gem::ControlState::OnlineLocal: return pb::ControlState::ONLINE_LOCAL; case gem::ControlState::OnlineRemote: return pb::ControlState::ONLINE_REMOTE; } return pb::ControlState::EQUIPMENT_OFFLINE; } class EquipmentService final : public pb::Equipment::Service { public: explicit EquipmentService(gem::EquipmentRuntime& rt) : rt_(rt) {} grpc::Status SetVariables(grpc::ServerContext*, const pb::VariableUpdate* req, pb::Ack* resp) override { for (const auto& kv : req->values()) { auto vid = gem::resolve_variable(rt_.model(), kv.first); if (!vid) { resp->set_code(pb::Ack::PARAMETER_INVALID); resp->set_message("no variable named '" + kv.first + "'"); return grpc::Status::OK; } rt_.set_variable(*vid, to_item(kv.second)); } resp->set_code(pb::Ack::ACCEPT); return grpc::Status::OK; } grpc::Status GetControlState(grpc::ServerContext*, const pb::Empty*, pb::ControlState* resp) override { resp->set_state(to_proto_state(rt_.control_state())); return grpc::Status::OK; } private: gem::EquipmentRuntime& rt_; }; 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(std::stoi(hsms_port)); cfg.log = [](const std::string& m) { std::cout << "[gemd] " << m << std::endl; }; std::unique_ptr rt; try { rt = std::make_unique(cfg); } catch (const std::exception& e) { std::cerr << "[gemd] config error: " << e.what() << std::endl; return 1; } gem::register_default_handlers(*rt); rt->run_async(); // engine + HSMS link on a background thread EquipmentService service(*rt); grpc::ServerBuilder builder; builder.AddListeningPort(grpc_addr, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr 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; }