#include "secsgem/gem/control_state.hpp" #include namespace secsgem::gem { const char* control_state_name(ControlState s) { switch (s) { case ControlState::EquipmentOffline: return "EquipmentOffline"; case ControlState::AttemptOnline: return "AttemptOnline"; case ControlState::HostOffline: return "HostOffline"; case ControlState::OnlineLocal: return "OnlineLocal"; case ControlState::OnlineRemote: return "OnlineRemote"; } return "?"; } std::optional parse_control_state(const std::string& s) { if (s == "EquipmentOffline") return ControlState::EquipmentOffline; if (s == "AttemptOnline") return ControlState::AttemptOnline; if (s == "HostOffline") return ControlState::HostOffline; if (s == "OnlineLocal") return ControlState::OnlineLocal; if (s == "OnlineRemote") return ControlState::OnlineRemote; return std::nullopt; } bool is_online(ControlState s) { return s == ControlState::OnlineLocal || s == ControlState::OnlineRemote; } const char* control_event_name(ControlEvent e) { switch (e) { case ControlEvent::OperatorSwitchOnline: return "OperatorSwitchOnline"; case ControlEvent::OperatorSwitchOffline: return "OperatorSwitchOffline"; case ControlEvent::OperatorSwitchLocal: return "OperatorSwitchLocal"; case ControlEvent::OperatorSwitchRemote: return "OperatorSwitchRemote"; case ControlEvent::AttemptComplete: return "AttemptComplete"; case ControlEvent::AttemptFailed: return "AttemptFailed"; case ControlEvent::HostRequestOnline: return "HostRequestOnline"; case ControlEvent::HostRequestOffline: return "HostRequestOffline"; } return "?"; } std::optional parse_control_event(const std::string& s) { if (s == "operator_online") return ControlEvent::OperatorSwitchOnline; if (s == "operator_offline") return ControlEvent::OperatorSwitchOffline; if (s == "operator_local") return ControlEvent::OperatorSwitchLocal; if (s == "operator_remote") return ControlEvent::OperatorSwitchRemote; if (s == "attempt_complete") return ControlEvent::AttemptComplete; if (s == "attempt_failed") return ControlEvent::AttemptFailed; if (s == "host_request_online") return ControlEvent::HostRequestOnline; if (s == "host_request_offline") return ControlEvent::HostRequestOffline; return std::nullopt; } void ControlTransitionTable::add(ControlTransition row) { rows_.push_back(row); } const ControlTransition* ControlTransitionTable::find(ControlState from, ControlEvent on) const { for (const auto& r : rows_) { if (r.from == from && r.on == on) return &r; } return nullptr; } ControlTransitionTable ControlTransitionTable::default_table() { using S = ControlState; using E = ControlEvent; using A = OnlineAck; ControlTransitionTable t; // Host: Request Online t.add({S::HostOffline, E::HostRequestOnline, S::AttemptOnline, S::OnlineRemote, static_cast(A::Accept)}); t.add({S::OnlineLocal, E::HostRequestOnline, std::nullopt, std::nullopt, static_cast(A::AlreadyOnline)}); t.add({S::OnlineRemote, E::HostRequestOnline, std::nullopt, std::nullopt, static_cast(A::AlreadyOnline)}); t.add({S::EquipmentOffline, E::HostRequestOnline, std::nullopt, std::nullopt, static_cast(A::NotAccept)}); t.add({S::AttemptOnline, E::HostRequestOnline, std::nullopt, std::nullopt, static_cast(A::NotAccept)}); // Host: Request Offline (always accept, idempotent) for (auto from : {S::EquipmentOffline, S::HostOffline, S::AttemptOnline}) { t.add({from, E::HostRequestOffline, std::nullopt, std::nullopt, static_cast(OfflineAck::Accept)}); } t.add({S::OnlineLocal, E::HostRequestOffline, S::HostOffline, std::nullopt, static_cast(OfflineAck::Accept)}); t.add({S::OnlineRemote, E::HostRequestOffline, S::HostOffline, std::nullopt, static_cast(OfflineAck::Accept)}); // Operator: Online (-> Local by default) t.add({S::EquipmentOffline, E::OperatorSwitchOnline, S::AttemptOnline, S::OnlineLocal, std::nullopt}); t.add({S::HostOffline, E::OperatorSwitchOnline, S::AttemptOnline, S::OnlineLocal, std::nullopt}); // Operator: Offline t.add({S::OnlineLocal, E::OperatorSwitchOffline, S::HostOffline, std::nullopt, std::nullopt}); t.add({S::OnlineRemote, E::OperatorSwitchOffline, S::HostOffline, std::nullopt, std::nullopt}); t.add({S::AttemptOnline, E::OperatorSwitchOffline, S::HostOffline, std::nullopt, std::nullopt}); // Operator: Local <-> Remote t.add({S::OnlineRemote, E::OperatorSwitchLocal, S::OnlineLocal, std::nullopt, std::nullopt}); t.add({S::OnlineLocal, E::OperatorSwitchRemote, S::OnlineRemote, std::nullopt, std::nullopt}); return t; } ControlStateMachine::ControlStateMachine() : ControlStateMachine(ControlTransitionTable::default_table(), ControlState::HostOffline) {} ControlStateMachine::ControlStateMachine(ControlTransitionTable table, ControlState initial) : table_(std::move(table)), state_(initial) {} void ControlStateMachine::transition(ControlState next, ControlEvent trigger) { if (state_ == next) return; const ControlState prev = state_; state_ = next; if (on_change_) on_change_(prev, next, trigger); } const ControlTransition* ControlStateMachine::fire(ControlEvent on) { const ControlTransition* row = table_.find(state_, on); if (!row) return nullptr; if (row->to) transition(*row->to, on); if (row->then) transition(*row->then, ControlEvent::AttemptComplete); return row; } bool ControlStateMachine::operator_online() { return fire(ControlEvent::OperatorSwitchOnline) != nullptr; } bool ControlStateMachine::operator_offline() { return fire(ControlEvent::OperatorSwitchOffline) != nullptr; } bool ControlStateMachine::operator_local() { return fire(ControlEvent::OperatorSwitchLocal) != nullptr; } bool ControlStateMachine::operator_remote() { return fire(ControlEvent::OperatorSwitchRemote) != nullptr; } OnlineAck ControlStateMachine::on_host_request_online() { const ControlTransition* row = fire(ControlEvent::HostRequestOnline); if (!row || !row->ack_code) return OnlineAck::NotAccept; return static_cast(*row->ack_code); } OfflineAck ControlStateMachine::on_host_request_offline() { const ControlTransition* row = fire(ControlEvent::HostRequestOffline); if (!row || !row->ack_code) return OfflineAck::Accept; return static_cast(*row->ack_code); } } // namespace secsgem::gem