F2: EPT joined to EquipmentDataModel + server-side CEID emission

EquipmentDataModel now carries an EptStateMachine as a value member
alongside the other top-level state machines.  Server installs a
state-change handler that maps every EPT transition to a CEID emission
through the existing emit_event path:

  1100 NonScheduledTime    1103 Engineering
  1101 ScheduledDowntime   1104 Standby
  1102 UnscheduledDowntime 1105 Productive

CEIDs land in the 1100+ block to keep clear of the demo equipment.yaml
(100s/200s/400s) and E90 (900s).  Log lines include the dwell time of
the previous state so trace-level diagnostics show utilization without
extra tooling.

Application code drives transitions by calling model->ept.on_event(...);
the existing event-report machinery (subscription state, S6F11
batching, spool) gates wire emission so EPT events spool on offline
hosts like every other CEID.

Closes Tranche F — E116 Equipment Performance Tracking end-to-end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 03:16:56 +02:00
parent 7bff01c363
commit cdf4049016
3 changed files with 44 additions and 0 deletions
+25
View File
@@ -16,6 +16,7 @@
#include "secsgem/endpoint.hpp"
#include "secsgem/gem/control_state.hpp"
#include "secsgem/gem/data_model.hpp"
#include "secsgem/gem/e116_constants.hpp"
#include "secsgem/gem/e90_constants.hpp"
#include "secsgem/gem/messages.hpp"
#include "secsgem/gem/router.hpp"
@@ -340,6 +341,30 @@ int main(int argc, char** argv) {
}
});
// ---- E116 EPT state-change emitter -----------------------------------
model->ept.set_state_change_handler(
[logfn, emit_event](gem::EptState from, gem::EptState to,
gem::EptEvent ev, std::chrono::milliseconds dwell) {
logfn(std::string("EPT: ") + gem::ept_state_name(from) + " -> " +
gem::ept_state_name(to) + " (" + gem::ept_event_name(ev) +
", dwelt " + std::to_string(dwell.count()) + "ms)");
switch (to) {
case gem::EptState::NonScheduledTime:
emit_event(gem::e116::kCeidNonScheduledTime); break;
case gem::EptState::ScheduledDowntime:
emit_event(gem::e116::kCeidScheduledDowntime); break;
case gem::EptState::UnscheduledDowntime:
emit_event(gem::e116::kCeidUnscheduledDowntime); break;
case gem::EptState::Engineering:
emit_event(gem::e116::kCeidEngineering); break;
case gem::EptState::Standby:
emit_event(gem::e116::kCeidStandby); break;
case gem::EptState::Productive:
emit_event(gem::e116::kCeidProductive); break;
case gem::EptState::NoState: break;
}
});
// ---- Build the SECS dispatch table once -------------------------------
gem::Router router;
+2
View File
@@ -3,6 +3,7 @@
#include "secsgem/gem/store/alarms.hpp"
#include "secsgem/gem/store/carriers.hpp"
#include "secsgem/gem/store/clock.hpp"
#include "secsgem/gem/ept_state.hpp"
#include "secsgem/gem/store/control_jobs.hpp"
#include "secsgem/gem/store/equipment_constants.hpp"
#include "secsgem/gem/store/event_reports.hpp"
@@ -41,6 +42,7 @@ struct EquipmentDataModel {
CarrierStore carriers;
LoadPortStore load_ports;
SubstrateStore substrates;
EptStateMachine ept;
// Convenience: VID -> value lookup spanning SVIDs and DVIDs.
std::optional<s2::Item> vid_value(uint32_t vid) const {
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
// E116 §6 standard collection-event identifiers, one per top-level
// state. Equipment fires the matching CEID on transition into that
// state; hosts subscribe via the existing S2F33 / S2F35 / S2F37 flow.
namespace secsgem::gem::e116 {
inline constexpr uint32_t kCeidNonScheduledTime = 1100;
inline constexpr uint32_t kCeidScheduledDowntime = 1101;
inline constexpr uint32_t kCeidUnscheduledDowntime = 1102;
inline constexpr uint32_t kCeidEngineering = 1103;
inline constexpr uint32_t kCeidStandby = 1104;
inline constexpr uint32_t kCeidProductive = 1105;
} // namespace secsgem::gem::e116