feat(daemon): FireEvent + event name resolution + in-process gRPC tests
- name_index: add resolve_event(name) -> CEID (unit-tested). - equipment_service.hpp: extract the gRPC service + value/state conversion into a shared header; add FireEvent (optional per-fire variable values, then trigger the collection event by name). secs_gemd slims to main(). - test_daemon_service: real in-process gRPC integration test (client stub -> service -> EquipmentRuntime) proving SetVariables lands in the model, GetControlState reports the state, FireEvent and unknown-name paths behave. Separate secs_gemd_tests target (links grpc++/proto), gated on the daemon. Core suite 459/459 (2799 assertions); daemon gRPC tests 15/15. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
#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 <grpcpp/grpcpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#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<int64_t>(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
|
||||
Reference in New Issue
Block a user