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,119 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "secsgem/gem/carrier_state.hpp" // shared CarrierTransition template
|
||||
|
||||
// E87 §6 Load Port state models — three independent FSMs per port:
|
||||
//
|
||||
// LPTS LoadPortTransferState - is a carrier being moved on/off?
|
||||
// LRS LoadPortReservationStatus - host reservation
|
||||
// LAS LoadPortAssociationStatus - is a carrier associated?
|
||||
//
|
||||
// Wire-byte values match E87-0716 §10.3.5-7.
|
||||
namespace secsgem::gem {
|
||||
|
||||
enum class LoadPortTransferState : uint8_t {
|
||||
OutOfService = 0,
|
||||
InService = 1, // ready, no carrier in motion
|
||||
Loading = 2, // carrier arriving
|
||||
Unloading = 3, // carrier leaving
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* load_port_transfer_state_name(LoadPortTransferState s);
|
||||
|
||||
enum class LoadPortTransferEvent {
|
||||
EnterService, // OutOfService -> InService
|
||||
LeaveService, // any -> OutOfService
|
||||
StartLoading, // InService -> Loading
|
||||
CompleteLoading, // Loading -> InService
|
||||
StartUnloading, // InService -> Unloading
|
||||
CompleteUnloading, // Unloading -> InService
|
||||
};
|
||||
|
||||
const char* load_port_transfer_event_name(LoadPortTransferEvent e);
|
||||
|
||||
enum class LoadPortReservationStatus : uint8_t {
|
||||
NotReserved = 0,
|
||||
Reserved = 1,
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* load_port_reservation_status_name(LoadPortReservationStatus s);
|
||||
|
||||
enum class LoadPortReservationEvent {
|
||||
Reserve, // NotReserved -> Reserved
|
||||
Release, // Reserved -> NotReserved
|
||||
};
|
||||
|
||||
const char* load_port_reservation_event_name(LoadPortReservationEvent e);
|
||||
|
||||
enum class LoadPortAssociationStatus : uint8_t {
|
||||
NotAssociated = 0,
|
||||
Associated = 1,
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* load_port_association_status_name(LoadPortAssociationStatus s);
|
||||
|
||||
enum class LoadPortAssociationEvent {
|
||||
Associate, // NotAssociated -> Associated
|
||||
Disassociate, // Associated -> NotAssociated
|
||||
};
|
||||
|
||||
const char* load_port_association_event_name(LoadPortAssociationEvent e);
|
||||
|
||||
using LoadPortTransferTable =
|
||||
CarrierTransitionTable<LoadPortTransferState, LoadPortTransferEvent>;
|
||||
using LoadPortReservationTable =
|
||||
CarrierTransitionTable<LoadPortReservationStatus, LoadPortReservationEvent>;
|
||||
using LoadPortAssociationTable =
|
||||
CarrierTransitionTable<LoadPortAssociationStatus, LoadPortAssociationEvent>;
|
||||
|
||||
LoadPortTransferTable default_load_port_transfer_table();
|
||||
LoadPortReservationTable default_load_port_reservation_table();
|
||||
LoadPortAssociationTable default_load_port_association_table();
|
||||
|
||||
class LoadPortStateMachine {
|
||||
public:
|
||||
using TransferChangeHandler =
|
||||
std::function<void(LoadPortTransferState from, LoadPortTransferState to,
|
||||
LoadPortTransferEvent)>;
|
||||
using ReservationChangeHandler =
|
||||
std::function<void(LoadPortReservationStatus from, LoadPortReservationStatus to,
|
||||
LoadPortReservationEvent)>;
|
||||
using AssociationChangeHandler =
|
||||
std::function<void(LoadPortAssociationStatus from, LoadPortAssociationStatus to,
|
||||
LoadPortAssociationEvent)>;
|
||||
|
||||
LoadPortStateMachine();
|
||||
|
||||
LoadPortTransferState transfer_state() const { return tx_state_; }
|
||||
LoadPortReservationStatus reservation_state() const { return rs_state_; }
|
||||
LoadPortAssociationStatus association_state() const { return as_state_; }
|
||||
|
||||
void set_transfer_handler(TransferChangeHandler h) { on_tx_ = std::move(h); }
|
||||
void set_reservation_handler(ReservationChangeHandler h) { on_rs_ = std::move(h); }
|
||||
void set_association_handler(AssociationChangeHandler h) { on_as_ = std::move(h); }
|
||||
|
||||
bool on_transfer_event(LoadPortTransferEvent e);
|
||||
bool on_reservation_event(LoadPortReservationEvent e);
|
||||
bool on_association_event(LoadPortAssociationEvent e);
|
||||
|
||||
private:
|
||||
LoadPortTransferTable tx_table_;
|
||||
LoadPortReservationTable rs_table_;
|
||||
LoadPortAssociationTable as_table_;
|
||||
LoadPortTransferState tx_state_ = LoadPortTransferState::InService;
|
||||
LoadPortReservationStatus rs_state_ = LoadPortReservationStatus::NotReserved;
|
||||
LoadPortAssociationStatus as_state_ = LoadPortAssociationStatus::NotAssociated;
|
||||
TransferChangeHandler on_tx_;
|
||||
ReservationChangeHandler on_rs_;
|
||||
AssociationChangeHandler on_as_;
|
||||
};
|
||||
|
||||
} // namespace secsgem::gem
|
||||
Reference in New Issue
Block a user