#include "secsgem/gem/ept_state.hpp" namespace secsgem::gem { static_assert(static_cast(EptState::NonScheduledTime) == 0); static_assert(static_cast(EptState::ScheduledDowntime) == 1); static_assert(static_cast(EptState::UnscheduledDowntime) == 2); static_assert(static_cast(EptState::Engineering) == 3); static_assert(static_cast(EptState::Standby) == 4); static_assert(static_cast(EptState::Productive) == 5); const char* ept_state_name(EptState s) { switch (s) { case EptState::NonScheduledTime: return "NonScheduledTime"; case EptState::ScheduledDowntime: return "ScheduledDowntime"; case EptState::UnscheduledDowntime: return "UnscheduledDowntime"; case EptState::Engineering: return "Engineering"; case EptState::Standby: return "Standby"; case EptState::Productive: return "Productive"; case EptState::NoState: return "NoState"; } return "?"; } std::optional parse_ept_state(const std::string& s) { if (s == "NonScheduledTime") return EptState::NonScheduledTime; if (s == "ScheduledDowntime") return EptState::ScheduledDowntime; if (s == "UnscheduledDowntime") return EptState::UnscheduledDowntime; if (s == "Engineering") return EptState::Engineering; if (s == "Standby") return EptState::Standby; if (s == "Productive") return EptState::Productive; if (s == "NoState") return EptState::NoState; return std::nullopt; } const char* ept_event_name(EptEvent e) { switch (e) { case EptEvent::EnterNonScheduled: return "EnterNonScheduled"; case EptEvent::EnterScheduledDown: return "EnterScheduledDown"; case EptEvent::EnterUnscheduledDown: return "EnterUnscheduledDown"; case EptEvent::EnterEngineering: return "EnterEngineering"; case EptEvent::EnterStandby: return "EnterStandby"; case EptEvent::EnterProductive: return "EnterProductive"; } return "?"; } namespace { // Each event has exactly one target state regardless of source. EptState target_of(EptEvent e) { switch (e) { case EptEvent::EnterNonScheduled: return EptState::NonScheduledTime; case EptEvent::EnterScheduledDown: return EptState::ScheduledDowntime; case EptEvent::EnterUnscheduledDown: return EptState::UnscheduledDowntime; case EptEvent::EnterEngineering: return EptState::Engineering; case EptEvent::EnterStandby: return EptState::Standby; case EptEvent::EnterProductive: return EptState::Productive; } return EptState::NoState; } } // namespace EptTable default_ept_table() { EptTable t; // Cross-product: every source state accepts every event. The FSM is // a classifier, not a strict workflow, so all transitions are legal. for (auto src : {EptState::NonScheduledTime, EptState::ScheduledDowntime, EptState::UnscheduledDowntime, EptState::Engineering, EptState::Standby, EptState::Productive}) { for (auto ev : {EptEvent::EnterNonScheduled, EptEvent::EnterScheduledDown, EptEvent::EnterUnscheduledDown, EptEvent::EnterEngineering, EptEvent::EnterStandby, EptEvent::EnterProductive}) { t.add({src, ev, target_of(ev), std::nullopt}); } } return t; } EptStateMachine::EptStateMachine() : table_(default_ept_table()), entered_(std::chrono::steady_clock::now()), reset_anchor_(entered_) {} bool EptStateMachine::on_event(EptEvent e) { const auto* row = table_.find(state_, e); if (!row) return false; if (row->to && *row->to != state_) { auto prev = state_; auto now = std::chrono::steady_clock::now(); auto dwell = std::chrono::duration_cast( now - entered_); buckets_[static_cast(prev)] += dwell; state_ = *row->to; entered_ = now; if (on_change_) on_change_(prev, state_, e, dwell); } return true; } std::chrono::milliseconds EptStateMachine::accumulated(EptState s) const { const auto idx = static_cast(s); if (idx >= buckets_.size()) return std::chrono::milliseconds{0}; auto base = buckets_[idx]; if (state_ == s) { const auto now = std::chrono::steady_clock::now(); base += std::chrono::duration_cast(now - entered_); } return base; } std::chrono::milliseconds EptStateMachine::total_elapsed() const { return std::chrono::duration_cast( std::chrono::steady_clock::now() - reset_anchor_); } void EptStateMachine::reset_history() { buckets_.fill(std::chrono::milliseconds{0}); const auto now = std::chrono::steady_clock::now(); entered_ = now; reset_anchor_ = now; } } // namespace secsgem::gem