K2: SubstrateIDStatus (third E90 axis)
Adds the substrate-ID verification FSM that E90 §6.4.6 calls for: NotConfirmed initial; equipment hasn't read the ID yet WaitingForHost ID has been read; awaiting host accept/reject Confirmed host confirmed (or force-bound) Mismatched host rejected — recoverable via Bind Events: Read NotConfirmed -> WaitingForHost Confirm WaitingForHost -> Confirmed Mismatch WaitingForHost -> Mismatched Bind any -> Confirmed (force-bind) Reset any -> NotConfirmed Wire-byte values pinned via static_assert. The third axis is now exposed on SubstrateStateMachine alongside location_state() and processing_state(); set_id_handler() observes transitions. Existing two-axis API is unchanged. 4 new test cases cover the happy path, Mismatch+Bind recovery, Reset from any state, and same-state event handler suppression. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -68,13 +68,38 @@ enum class SubstrateProcessingEvent {
|
|||||||
|
|
||||||
const char* substrate_processing_event_name(SubstrateProcessingEvent e);
|
const char* substrate_processing_event_name(SubstrateProcessingEvent e);
|
||||||
|
|
||||||
|
// Substrate ID Status (E90 §6.4.6) — the third independent axis.
|
||||||
|
// Captures whether the equipment has read & confirmed the wafer's ID.
|
||||||
|
enum class SubstrateIDStatus : uint8_t {
|
||||||
|
NotConfirmed = 0,
|
||||||
|
WaitingForHost = 1, // ID read; awaiting host confirmation
|
||||||
|
Confirmed = 2,
|
||||||
|
Mismatched = 3,
|
||||||
|
NoState = 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* substrate_id_status_name(SubstrateIDStatus s);
|
||||||
|
|
||||||
|
enum class SubstrateIDEvent {
|
||||||
|
Read, // NotConfirmed -> WaitingForHost (equipment scanned ID)
|
||||||
|
Confirm, // WaitingForHost -> Confirmed (host accepted)
|
||||||
|
Mismatch, // WaitingForHost -> Mismatched (host rejected)
|
||||||
|
Bind, // any -> Confirmed (host force-bind)
|
||||||
|
Reset, // any -> NotConfirmed
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* substrate_id_event_name(SubstrateIDEvent e);
|
||||||
|
|
||||||
using SubstrateTable =
|
using SubstrateTable =
|
||||||
CarrierTransitionTable<SubstrateState, SubstrateEvent>;
|
CarrierTransitionTable<SubstrateState, SubstrateEvent>;
|
||||||
using SubstrateProcessingTable =
|
using SubstrateProcessingTable =
|
||||||
CarrierTransitionTable<SubstrateProcessingState, SubstrateProcessingEvent>;
|
CarrierTransitionTable<SubstrateProcessingState, SubstrateProcessingEvent>;
|
||||||
|
using SubstrateIDTable =
|
||||||
|
CarrierTransitionTable<SubstrateIDStatus, SubstrateIDEvent>;
|
||||||
|
|
||||||
SubstrateTable default_substrate_table();
|
SubstrateTable default_substrate_table();
|
||||||
SubstrateProcessingTable default_substrate_processing_table();
|
SubstrateProcessingTable default_substrate_processing_table();
|
||||||
|
SubstrateIDTable default_substrate_id_table();
|
||||||
|
|
||||||
class SubstrateStateMachine {
|
class SubstrateStateMachine {
|
||||||
public:
|
public:
|
||||||
@@ -84,25 +109,34 @@ class SubstrateStateMachine {
|
|||||||
std::function<void(SubstrateProcessingState from,
|
std::function<void(SubstrateProcessingState from,
|
||||||
SubstrateProcessingState to,
|
SubstrateProcessingState to,
|
||||||
SubstrateProcessingEvent)>;
|
SubstrateProcessingEvent)>;
|
||||||
|
using IDChangeHandler =
|
||||||
|
std::function<void(SubstrateIDStatus from, SubstrateIDStatus to,
|
||||||
|
SubstrateIDEvent)>;
|
||||||
|
|
||||||
SubstrateStateMachine();
|
SubstrateStateMachine();
|
||||||
|
|
||||||
SubstrateState location_state() const { return loc_; }
|
SubstrateState location_state() const { return loc_; }
|
||||||
SubstrateProcessingState processing_state() const { return proc_; }
|
SubstrateProcessingState processing_state() const { return proc_; }
|
||||||
|
SubstrateIDStatus id_state() const { return id_; }
|
||||||
|
|
||||||
void set_location_handler(LocationChangeHandler h) { on_loc_ = std::move(h); }
|
void set_location_handler(LocationChangeHandler h) { on_loc_ = std::move(h); }
|
||||||
void set_processing_handler(ProcessingChangeHandler h) { on_proc_ = std::move(h); }
|
void set_processing_handler(ProcessingChangeHandler h) { on_proc_ = std::move(h); }
|
||||||
|
void set_id_handler(IDChangeHandler h) { on_id_ = std::move(h); }
|
||||||
|
|
||||||
bool on_location_event(SubstrateEvent e);
|
bool on_location_event(SubstrateEvent e);
|
||||||
bool on_processing_event(SubstrateProcessingEvent e);
|
bool on_processing_event(SubstrateProcessingEvent e);
|
||||||
|
bool on_id_event(SubstrateIDEvent e);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SubstrateTable loc_table_;
|
SubstrateTable loc_table_;
|
||||||
SubstrateProcessingTable proc_table_;
|
SubstrateProcessingTable proc_table_;
|
||||||
|
SubstrateIDTable id_table_;
|
||||||
SubstrateState loc_ = SubstrateState::AtSource;
|
SubstrateState loc_ = SubstrateState::AtSource;
|
||||||
SubstrateProcessingState proc_ = SubstrateProcessingState::NeedsProcessing;
|
SubstrateProcessingState proc_ = SubstrateProcessingState::NeedsProcessing;
|
||||||
|
SubstrateIDStatus id_ = SubstrateIDStatus::NotConfirmed;
|
||||||
LocationChangeHandler on_loc_;
|
LocationChangeHandler on_loc_;
|
||||||
ProcessingChangeHandler on_proc_;
|
ProcessingChangeHandler on_proc_;
|
||||||
|
IDChangeHandler on_id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace secsgem::gem
|
} // namespace secsgem::gem
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ static_assert(static_cast<uint8_t>(SubstrateProcessingState::Stopped) ==
|
|||||||
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Rejected) == 5);
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Rejected) == 5);
|
||||||
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Lost) == 6);
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Lost) == 6);
|
||||||
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Skipped) == 7);
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Skipped) == 7);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateIDStatus::NotConfirmed) == 0);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateIDStatus::WaitingForHost) == 1);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateIDStatus::Confirmed) == 2);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateIDStatus::Mismatched) == 3);
|
||||||
|
|
||||||
const char* substrate_state_name(SubstrateState s) {
|
const char* substrate_state_name(SubstrateState s) {
|
||||||
switch (s) {
|
switch (s) {
|
||||||
@@ -56,6 +60,28 @@ const char* substrate_event_name(SubstrateEvent e) {
|
|||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* substrate_id_status_name(SubstrateIDStatus s) {
|
||||||
|
switch (s) {
|
||||||
|
case SubstrateIDStatus::NotConfirmed: return "NotConfirmed";
|
||||||
|
case SubstrateIDStatus::WaitingForHost: return "WaitingForHost";
|
||||||
|
case SubstrateIDStatus::Confirmed: return "Confirmed";
|
||||||
|
case SubstrateIDStatus::Mismatched: return "Mismatched";
|
||||||
|
case SubstrateIDStatus::NoState: return "NoState";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* substrate_id_event_name(SubstrateIDEvent e) {
|
||||||
|
switch (e) {
|
||||||
|
case SubstrateIDEvent::Read: return "Read";
|
||||||
|
case SubstrateIDEvent::Confirm: return "Confirm";
|
||||||
|
case SubstrateIDEvent::Mismatch: return "Mismatch";
|
||||||
|
case SubstrateIDEvent::Bind: return "Bind";
|
||||||
|
case SubstrateIDEvent::Reset: return "Reset";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
const char* substrate_processing_event_name(SubstrateProcessingEvent e) {
|
const char* substrate_processing_event_name(SubstrateProcessingEvent e) {
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case SubstrateProcessingEvent::StartProcessing: return "StartProcessing";
|
case SubstrateProcessingEvent::StartProcessing: return "StartProcessing";
|
||||||
@@ -97,9 +123,29 @@ SubstrateProcessingTable default_substrate_processing_table() {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubstrateIDTable default_substrate_id_table() {
|
||||||
|
using S = SubstrateIDStatus;
|
||||||
|
using E = SubstrateIDEvent;
|
||||||
|
SubstrateIDTable t;
|
||||||
|
t.add({S::NotConfirmed, E::Read, S::WaitingForHost, std::nullopt});
|
||||||
|
t.add({S::WaitingForHost, E::Confirm, S::Confirmed, std::nullopt});
|
||||||
|
t.add({S::WaitingForHost, E::Mismatch, S::Mismatched, std::nullopt});
|
||||||
|
t.add({S::Mismatched, E::Bind, S::Confirmed, std::nullopt});
|
||||||
|
// Bind from any state force-confirms.
|
||||||
|
t.add({S::NotConfirmed, E::Bind, S::Confirmed, std::nullopt});
|
||||||
|
t.add({S::WaitingForHost, E::Bind, S::Confirmed, std::nullopt});
|
||||||
|
// Reset returns any state to NotConfirmed.
|
||||||
|
for (auto src : {S::NotConfirmed, S::WaitingForHost,
|
||||||
|
S::Confirmed, S::Mismatched}) {
|
||||||
|
t.add({src, E::Reset, S::NotConfirmed, std::nullopt});
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
SubstrateStateMachine::SubstrateStateMachine()
|
SubstrateStateMachine::SubstrateStateMachine()
|
||||||
: loc_table_(default_substrate_table()),
|
: loc_table_(default_substrate_table()),
|
||||||
proc_table_(default_substrate_processing_table()) {}
|
proc_table_(default_substrate_processing_table()),
|
||||||
|
id_table_(default_substrate_id_table()) {}
|
||||||
|
|
||||||
bool SubstrateStateMachine::on_location_event(SubstrateEvent e) {
|
bool SubstrateStateMachine::on_location_event(SubstrateEvent e) {
|
||||||
const auto* row = loc_table_.find(loc_, e);
|
const auto* row = loc_table_.find(loc_, e);
|
||||||
@@ -123,4 +169,15 @@ bool SubstrateStateMachine::on_processing_event(SubstrateProcessingEvent e) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SubstrateStateMachine::on_id_event(SubstrateIDEvent e) {
|
||||||
|
const auto* row = id_table_.find(id_, e);
|
||||||
|
if (!row) return false;
|
||||||
|
if (row->to && *row->to != id_) {
|
||||||
|
auto prev = id_;
|
||||||
|
id_ = *row->to;
|
||||||
|
if (on_id_) on_id_(prev, id_, e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace secsgem::gem
|
} // namespace secsgem::gem
|
||||||
|
|||||||
@@ -71,6 +71,48 @@ TEST_CASE("SubstrateStore: create + dedup + location update") {
|
|||||||
CHECK(s.get("W-001")->fsm->location_state() == SubstrateState::AtWork);
|
CHECK(s.get("W-001")->fsm->location_state() == SubstrateState::AtWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Substrate ID Status (E90 §6.4.6) -----------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("Substrate IDS: NotConfirmed -> WaitingForHost -> Confirmed") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
CHECK(s.id_state() == SubstrateIDStatus::NotConfirmed);
|
||||||
|
CHECK(s.on_id_event(SubstrateIDEvent::Read));
|
||||||
|
CHECK(s.id_state() == SubstrateIDStatus::WaitingForHost);
|
||||||
|
CHECK(s.on_id_event(SubstrateIDEvent::Confirm));
|
||||||
|
CHECK(s.id_state() == SubstrateIDStatus::Confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate IDS: Mismatch + Bind recovers") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
s.on_id_event(SubstrateIDEvent::Read);
|
||||||
|
CHECK(s.on_id_event(SubstrateIDEvent::Mismatch));
|
||||||
|
CHECK(s.id_state() == SubstrateIDStatus::Mismatched);
|
||||||
|
CHECK(s.on_id_event(SubstrateIDEvent::Bind));
|
||||||
|
CHECK(s.id_state() == SubstrateIDStatus::Confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate IDS: Reset from any state returns to NotConfirmed") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
s.on_id_event(SubstrateIDEvent::Read);
|
||||||
|
s.on_id_event(SubstrateIDEvent::Confirm);
|
||||||
|
CHECK(s.on_id_event(SubstrateIDEvent::Reset));
|
||||||
|
CHECK(s.id_state() == SubstrateIDStatus::NotConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate IDS: change handler observes transitions only") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
int calls = 0;
|
||||||
|
s.set_id_handler([&](SubstrateIDStatus, SubstrateIDStatus, SubstrateIDEvent) {
|
||||||
|
++calls;
|
||||||
|
});
|
||||||
|
s.on_id_event(SubstrateIDEvent::Read);
|
||||||
|
s.on_id_event(SubstrateIDEvent::Confirm);
|
||||||
|
CHECK(calls == 2);
|
||||||
|
// Re-confirming from Confirmed: no row, no call.
|
||||||
|
s.on_id_event(SubstrateIDEvent::Confirm);
|
||||||
|
CHECK(calls == 2);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("SubstrateStore: history records every transition") {
|
TEST_CASE("SubstrateStore: history records every transition") {
|
||||||
SubstrateStore s;
|
SubstrateStore s;
|
||||||
s.create("W-H", "CAR-X", 3);
|
s.create("W-H", "CAR-X", 3);
|
||||||
|
|||||||
Reference in New Issue
Block a user