#pragma once #include #include #include namespace secsgem::gem { // E30 Control State Model (§6.2). Drives whether the equipment is // communicating with a host and, if so, who is in control. enum class ControlState { EquipmentOffline, // equipment is offline; no host comms attempted AttemptOnline, // transient: equipment trying to come online HostOffline, // HSMS up but host has not established control OnlineLocal, // online, operator in control; host observes only OnlineRemote, // online, host in control }; const char* control_state_name(ControlState s); bool is_online(ControlState s); // What triggered a state change — surfaced to the on_change handler so the UI // or logs can show *why* the state moved. enum class ControlEvent { OperatorSwitchOnline, OperatorSwitchOffline, OperatorSwitchLocal, OperatorSwitchRemote, AttemptComplete, AttemptFailed, HostRequestOnline, // S1F17 HostRequestOffline, // S1F15 }; const char* control_event_name(ControlEvent e); // S1F18 ONLACK codes. enum class OnlineAck : uint8_t { Accept = 0, NotAccept = 1, AlreadyOnline = 2, }; // S1F16 OFLACK codes. enum class OfflineAck : uint8_t { Accept = 0, }; // S1F14 COMMACK codes. enum class CommAck : uint8_t { Accept = 0, Denied = 1, }; // Drives the E30 control state. Pure state machine — no IO. The Server layer // owns one of these per equipment and dispatches host-initiated events and // operator actions into it. class ControlStateMachine { public: struct Config { ControlState initial = ControlState::HostOffline; // When ATTEMPT_ONLINE completes via a host request, do we land in REMOTE // (host in control) or LOCAL (host observes only)? For host-initiated // online this defaults to REMOTE; for operator-initiated online it follows // `operator_default_remote`. bool host_request_grants_remote = true; bool operator_default_remote = false; }; using StateChangeHandler = std::function; ControlStateMachine(); explicit ControlStateMachine(Config cfg); ControlState state() const { return state_; } bool online() const { return is_online(state_); } void set_state_change_handler(StateChangeHandler h) { on_change_ = std::move(h); } // Operator actions. Each returns true if a transition occurred, false if the // current state didn't permit it. bool operator_online(); bool operator_offline(); bool operator_local(); bool operator_remote(); // Host-initiated requests. The SM responds with the SEMI-mandated ack code // and performs any transition. OnlineAck on_host_request_online(); OfflineAck on_host_request_offline(); private: void transition(ControlState next, ControlEvent trigger); Config cfg_; ControlState state_; StateChangeHandler on_change_; }; } // namespace secsgem::gem