06f664dfab
EptStateMachine now retains per-state cumulative dwell time so the host can read it as SVIDs. The implementation is zero-overhead while the FSM is idle (no timers, no background work) — on every transition we add the prior state's dwell to its bucket and reset the entered_ timestamp. Live dwell in the current state is included in accumulated() via a now-vs-entered_ delta at read time. New public API: accumulated(EptState) per-state cumulative ms (incl. live dwell) total_elapsed() denominator for utilization ratios reset_history() S2F43-style history clear This closes the gap I called out: previously we emitted CEIDs on transition but didn't accumulate the bucket the host actually queries for utilization metrics. Wiring these into specific SVIDs is the application's job (equipment.yaml declares SVIDs against any read callable); the runtime data is now there. 4 new test cases cover accumulation, live-dwell inclusion, and reset. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
93 lines
3.2 KiB
C++
93 lines
3.2 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "secsgem/gem/carrier_state.hpp" // shared CarrierTransition template
|
|
|
|
// E116 §6 Equipment Performance Tracking — top-level equipment state
|
|
// model that classifies operating time into productive / non-productive
|
|
// categories. Each transition is timestamped so the application can
|
|
// compute MTBF / availability / utilization from the same source.
|
|
//
|
|
// The states correspond directly to the SEMI-defined buckets in E116-
|
|
// 0712 §6.2; the wire byte values match the order the spec presents.
|
|
namespace secsgem::gem {
|
|
|
|
enum class EptState : uint8_t {
|
|
NonScheduledTime = 0, // equipment not scheduled to operate
|
|
ScheduledDowntime = 1, // scheduled maintenance
|
|
UnscheduledDowntime = 2, // unscheduled downtime (faults)
|
|
Engineering = 3, // engineering / qualification time
|
|
Standby = 4, // idle, available but not producing
|
|
Productive = 5, // actively producing
|
|
NoState = 255,
|
|
};
|
|
|
|
const char* ept_state_name(EptState s);
|
|
std::optional<EptState> parse_ept_state(const std::string& s);
|
|
|
|
// Events correspond to transitions the equipment can declare; events
|
|
// are intentionally state-neutral (any -> X) since EPT is a "what kind
|
|
// of time is this" classifier rather than a strict lifecycle.
|
|
enum class EptEvent {
|
|
EnterNonScheduled,
|
|
EnterScheduledDown,
|
|
EnterUnscheduledDown,
|
|
EnterEngineering,
|
|
EnterStandby,
|
|
EnterProductive,
|
|
};
|
|
|
|
const char* ept_event_name(EptEvent e);
|
|
|
|
using EptTable = CarrierTransitionTable<EptState, EptEvent>;
|
|
EptTable default_ept_table();
|
|
|
|
class EptStateMachine {
|
|
public:
|
|
using StateChangeHandler =
|
|
std::function<void(EptState from, EptState to, EptEvent,
|
|
std::chrono::milliseconds dwell_before_change)>;
|
|
|
|
EptStateMachine();
|
|
|
|
EptState state() const { return state_; }
|
|
void set_state_change_handler(StateChangeHandler h) { on_change_ = std::move(h); }
|
|
|
|
// Fire a transition. Returns true if a row matched. The dwell time
|
|
// (how long we sat in the previous state) is computed off the
|
|
// monotonic clock and surfaced to the handler so accounting code
|
|
// doesn't need a parallel timestamp tracker.
|
|
bool on_event(EptEvent e);
|
|
|
|
// --- E116 time-bucket accounting --------------------------------------
|
|
// Total accumulated time spent in `s` since the FSM was constructed
|
|
// or since reset_history() was last called. Time spent in the
|
|
// *current* state up to now is included.
|
|
std::chrono::milliseconds accumulated(EptState s) const;
|
|
|
|
// Total time across all states (= time since reset_history()). Useful
|
|
// as the denominator for utilization / availability ratios.
|
|
std::chrono::milliseconds total_elapsed() const;
|
|
|
|
// S2F43-style history clear. Sets every bucket to zero and re-anchors
|
|
// the current-state timer at "now".
|
|
void reset_history();
|
|
|
|
private:
|
|
EptTable table_;
|
|
EptState state_ = EptState::NonScheduledTime;
|
|
std::chrono::steady_clock::time_point entered_;
|
|
std::chrono::steady_clock::time_point reset_anchor_;
|
|
std::array<std::chrono::milliseconds, 6> buckets_{}; // indexed by EptState 0..5
|
|
StateChangeHandler on_change_;
|
|
};
|
|
|
|
} // namespace secsgem::gem
|