#pragma once // The gRPC Equipment service: translates proto/secsgem/v1 RPCs onto an // EquipmentRuntime. Header-only so both secs_gemd and the daemon tests share // one definition. #include #include #include #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 secsgem::daemon { namespace gem = secsgem::gem; namespace s2 = secsgem::secs2; namespace pb = secsgem::v1; // proto Value -> SECS-II Item. Increment 1 uses a direct mapping; format-aware // conversion (honouring the variable's declared wire format) is a refinement. inline 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(""); } } inline 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 FireEvent(grpc::ServerContext*, const pb::Event* req, pb::Ack* resp) override { // Optional per-fire variable values, then trigger the collection event. for (const auto& kv : req->data()) { 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)); } auto ceid = gem::resolve_event(rt_.model(), req->name()); if (!ceid) { resp->set_code(pb::Ack::PARAMETER_INVALID); resp->set_message("no event named '" + req->name() + "'"); return grpc::Status::OK; } rt_.emit_event(*ceid); 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_; }; } // namespace secsgem::daemon