94c26c0771
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>
160 lines
5.7 KiB
C++
160 lines
5.7 KiB
C++
#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
|