Files
secs-gem/include/secsgem/gem/ept_state.hpp
raphael 06f664dfab J: E116 time-bucket accounting
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>
2026-06-08 08:41:09 +02:00

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