7bff01c363
Adds the six E116-0712 §6.2 buckets for classifying equipment time: NonScheduledTime (0) not scheduled to operate ScheduledDowntime (1) planned maintenance window UnscheduledDowntime (2) faults / unplanned stoppage Engineering (3) engineering / qualification time Standby (4) idle but available Productive (5) actively producing Wire-byte values pinned via static_assert to E116 §10.3. The FSM is a classifier rather than a strict lifecycle — every (state, event) pair is legal — but it remains data-driven through the shared CarrierTransitionTable template so the default cross-product is expressible declaratively. The state-change handler also surfaces dwell time (how long the previous state was held) computed off std::chrono::steady_clock, so accounting code can compute MTBF / availability / utilization from a single source without maintaining a parallel timestamp log. 4 test cases cover the initial state, every event firing, dwell-time reporting, and the no-op same-state event (no handler call). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
76 lines
2.5 KiB
C++
76 lines
2.5 KiB
C++
#pragma once
|
|
|
|
#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);
|
|
|
|
private:
|
|
EptTable table_;
|
|
EptState state_ = EptState::NonScheduledTime;
|
|
std::chrono::steady_clock::time_point entered_;
|
|
StateChangeHandler on_change_;
|
|
};
|
|
|
|
} // namespace secsgem::gem
|