D1: E87 carrier and load-port state machines
Per-carrier triple FSM: CIDS (id verification), CSMS (slot-map), CAS
(access). Per-port triple FSM: LPTS (transfer), LRS (reservation), LAS
(association). Wire-byte enum values pinned via static_assert to match
E87-0716 §10.3.
CarrierStateMachine combines the three carrier-side FSMs because they
are independent but always observed together; same for LoadPortState-
Machine. Generic CarrierTransitionTable<State, Event> template is
reused across all six tables — same row shape as the PJ/CJ/Exception
tables that already exist.
Default tables cover the spec's documented transitions:
CIDS: NotConfirmed <-> Confirmed/Mismatched/Unknown, Cancel returns
to NotConfirmed from any state, Bind force-confirms.
CSMS: NotRead -> Read -> {Mismatched, Reset}.
CAS: NotAccessed -> InAccess -> Complete (terminal).
LPTS: OutOfService <-> InService <-> Loading/Unloading.
LRS / LAS: simple boolean toggle pairs.
15 test cases assert the happy-path lifecycles, cross-state cancels,
and that change handlers fire only on real transitions (Read in
NotConfirmed is a no-op, not a handler call).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// E87 §6 Carrier Management — three independent state models that
|
||||
// every carrier carries simultaneously:
|
||||
//
|
||||
// CIDS CarrierIDStatus id-verification lifecycle
|
||||
// CSMS CarrierSlotMapStatus slot-map verification lifecycle
|
||||
// CAS CarrierAccessingStatus is access in progress?
|
||||
//
|
||||
// Each FSM is per-carrier (keyed by CARRIERID inside CarrierStore) and
|
||||
// shares the table-driven pattern used by ProcessJobStateMachine /
|
||||
// ExceptionStateMachine. The wire-byte values match the on-the-wire
|
||||
// enums (E87-0716 §10.3).
|
||||
namespace secsgem::gem {
|
||||
|
||||
// CIDS — CARRIER ID Status (E87-0716 §10.3.2).
|
||||
enum class CarrierIDStatus : uint8_t {
|
||||
NotConfirmed = 0, // initial; carrier has arrived, ID not yet verified
|
||||
Confirmed = 1, // host accepted the ID
|
||||
Mismatched = 2, // host rejected the ID
|
||||
Unknown = 3, // carrier ID could not be read
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* carrier_id_status_name(CarrierIDStatus s);
|
||||
std::optional<CarrierIDStatus> parse_carrier_id_status(const std::string& s);
|
||||
|
||||
enum class CarrierIDEvent {
|
||||
Read, // equipment internal: an ID was read
|
||||
ProceedWithCarrier, // host: accept the ID (NotConfirmed -> Confirmed)
|
||||
CancelCarrier, // host: reject (any -> NotConfirmed)
|
||||
IDFailed, // equipment internal: read attempt failed
|
||||
Bind, // host explicit bind: any -> Confirmed (E87 §10.2.5)
|
||||
};
|
||||
|
||||
const char* carrier_id_event_name(CarrierIDEvent e);
|
||||
|
||||
// CSMS — CARRIER Slot Map Status (E87-0716 §10.3.3).
|
||||
enum class SlotMapStatus : uint8_t {
|
||||
NotRead = 0,
|
||||
Read = 1,
|
||||
Mismatched = 2,
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* slot_map_status_name(SlotMapStatus s);
|
||||
|
||||
enum class SlotMapEvent {
|
||||
Read, // equipment internal: slot map was scanned
|
||||
Confirm, // host: matches what was expected
|
||||
Mismatch, // host: did not match expected
|
||||
Reset, // any -> NotRead (e.g. carrier removed)
|
||||
};
|
||||
|
||||
const char* slot_map_event_name(SlotMapEvent e);
|
||||
|
||||
// CAS — CARRIER Accessing Status (E87-0716 §10.3.4).
|
||||
enum class CarrierAccessStatus : uint8_t {
|
||||
NotAccessed = 0,
|
||||
InAccess = 1,
|
||||
Complete = 2, // (CarrierCompleted in some texts)
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* carrier_access_status_name(CarrierAccessStatus s);
|
||||
|
||||
enum class CarrierAccessEvent {
|
||||
BeginAccess, // equipment internal: first substrate-move at carrier
|
||||
EndAccess, // equipment internal: last substrate processed
|
||||
Cancel, // any -> Complete (cancelled before access began)
|
||||
};
|
||||
|
||||
const char* carrier_access_event_name(CarrierAccessEvent e);
|
||||
|
||||
// One row of each transition table; same shape as the PJ/CJ FSM rows
|
||||
// elsewhere in the project.
|
||||
template <typename State, typename Event>
|
||||
struct CarrierTransition {
|
||||
State from;
|
||||
Event on;
|
||||
std::optional<State> to;
|
||||
std::optional<uint8_t> ack_code;
|
||||
};
|
||||
|
||||
using CarrierIDTransition = CarrierTransition<CarrierIDStatus, CarrierIDEvent>;
|
||||
using SlotMapTransition = CarrierTransition<SlotMapStatus, SlotMapEvent>;
|
||||
using CarrierAccessTransition = CarrierTransition<CarrierAccessStatus, CarrierAccessEvent>;
|
||||
|
||||
template <typename State, typename Event>
|
||||
class CarrierTransitionTable {
|
||||
public:
|
||||
using Row = CarrierTransition<State, Event>;
|
||||
void add(Row r) { rows_.push_back(r); }
|
||||
const Row* find(State from, Event on) const {
|
||||
for (const auto& r : rows_) if (r.from == from && r.on == on) return &r;
|
||||
return nullptr;
|
||||
}
|
||||
std::size_t size() const { return rows_.size(); }
|
||||
const std::vector<Row>& rows() const { return rows_; }
|
||||
|
||||
private:
|
||||
std::vector<Row> rows_;
|
||||
};
|
||||
|
||||
using CarrierIDTable = CarrierTransitionTable<CarrierIDStatus, CarrierIDEvent>;
|
||||
using SlotMapTable = CarrierTransitionTable<SlotMapStatus, SlotMapEvent>;
|
||||
using CarrierAccessTable = CarrierTransitionTable<CarrierAccessStatus, CarrierAccessEvent>;
|
||||
|
||||
CarrierIDTable default_carrier_id_table();
|
||||
SlotMapTable default_slot_map_table();
|
||||
CarrierAccessTable default_carrier_access_table();
|
||||
|
||||
// Per-carrier triple-FSM. All three substates are independent — a
|
||||
// carrier in CAS::InAccess may still be CIDS::NotConfirmed if the host
|
||||
// hasn't acknowledged the ID yet; the application is responsible for
|
||||
// any cross-FSM constraints it cares about.
|
||||
class CarrierStateMachine {
|
||||
public:
|
||||
using IDChangeHandler =
|
||||
std::function<void(CarrierIDStatus from, CarrierIDStatus to, CarrierIDEvent)>;
|
||||
using SlotMapChangeHandler =
|
||||
std::function<void(SlotMapStatus from, SlotMapStatus to, SlotMapEvent)>;
|
||||
using AccessChangeHandler =
|
||||
std::function<void(CarrierAccessStatus from, CarrierAccessStatus to,
|
||||
CarrierAccessEvent)>;
|
||||
|
||||
CarrierStateMachine();
|
||||
|
||||
CarrierIDStatus id_status() const { return id_state_; }
|
||||
SlotMapStatus slot_map_status() const { return sm_state_; }
|
||||
CarrierAccessStatus access_status() const { return acc_state_; }
|
||||
|
||||
void set_id_handler(IDChangeHandler h) { on_id_change_ = std::move(h); }
|
||||
void set_slot_map_handler(SlotMapChangeHandler h) { on_sm_change_ = std::move(h); }
|
||||
void set_access_handler(AccessChangeHandler h) { on_acc_change_ = std::move(h); }
|
||||
|
||||
bool on_id_event(CarrierIDEvent e);
|
||||
bool on_slot_map_event(SlotMapEvent e);
|
||||
bool on_access_event(CarrierAccessEvent e);
|
||||
|
||||
private:
|
||||
CarrierIDTable id_table_;
|
||||
SlotMapTable sm_table_;
|
||||
CarrierAccessTable acc_table_;
|
||||
CarrierIDStatus id_state_ = CarrierIDStatus::NotConfirmed;
|
||||
SlotMapStatus sm_state_ = SlotMapStatus::NotRead;
|
||||
CarrierAccessStatus acc_state_ = CarrierAccessStatus::NotAccessed;
|
||||
IDChangeHandler on_id_change_;
|
||||
SlotMapChangeHandler on_sm_change_;
|
||||
AccessChangeHandler on_acc_change_;
|
||||
};
|
||||
|
||||
} // namespace secsgem::gem
|
||||
Reference in New Issue
Block a user