E1: SubstrateStateMachine + SubstrateStore (E90 §6)
Per-substrate dual FSM with two orthogonal axes:
Location (STS):
AtSource -> AtWork (Acquire) -> AtDestination (Release)
AtWork -> AtSource (Return; processing aborted before completion)
Processing:
NeedsProcessing -> InProcess (Start) -> Processed (End)
InProcess -> {Aborted, Stopped, Rejected, Lost} terminal
NeedsProcessing -> {Skipped, Lost} terminal
Wire-byte values pinned via static_assert to E90-0716 §10.3.
SubstrateStore mirrors the CarrierStore pattern: non-movable, per-row
SubstrateStateMachine heap-allocated with handlers dispatching through
the store's location/processing callbacks; fire_location_event accepts
an optional new_location string so the application can carry
equipment-specific module names alongside the FSM state.
Joins EquipmentDataModel alongside carriers / load_ports. 9 test
cases cover initial state, full location lifecycle, all five
processing exits, and store-level dual-axis observer firing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@ add_library(secsgem
|
|||||||
src/gem/exception_state.cpp
|
src/gem/exception_state.cpp
|
||||||
src/gem/carrier_state.cpp
|
src/gem/carrier_state.cpp
|
||||||
src/gem/load_port_state.cpp
|
src/gem/load_port_state.cpp
|
||||||
|
src/gem/substrate_state.cpp
|
||||||
src/gem/host_handler.cpp
|
src/gem/host_handler.cpp
|
||||||
src/config/loader.cpp
|
src/config/loader.cpp
|
||||||
src/endpoint.cpp
|
src/endpoint.cpp
|
||||||
@@ -102,6 +103,7 @@ add_executable(secsgem_tests
|
|||||||
tests/test_exceptions.cpp
|
tests/test_exceptions.cpp
|
||||||
tests/test_carrier_state.cpp
|
tests/test_carrier_state.cpp
|
||||||
tests/test_carriers.cpp
|
tests/test_carriers.cpp
|
||||||
|
tests/test_substrates.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(secsgem_tests PRIVATE secsgem doctest::doctest)
|
target_link_libraries(secsgem_tests PRIVATE secsgem doctest::doctest)
|
||||||
target_compile_definitions(secsgem_tests PRIVATE
|
target_compile_definitions(secsgem_tests PRIVATE
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "secsgem/gem/store/host_commands.hpp"
|
#include "secsgem/gem/store/host_commands.hpp"
|
||||||
#include "secsgem/gem/store/limits.hpp"
|
#include "secsgem/gem/store/limits.hpp"
|
||||||
#include "secsgem/gem/store/process_jobs.hpp"
|
#include "secsgem/gem/store/process_jobs.hpp"
|
||||||
|
#include "secsgem/gem/store/substrates.hpp"
|
||||||
#include "secsgem/gem/store/recipes.hpp"
|
#include "secsgem/gem/store/recipes.hpp"
|
||||||
#include "secsgem/gem/store/spool.hpp"
|
#include "secsgem/gem/store/spool.hpp"
|
||||||
#include "secsgem/gem/store/status_variables.hpp"
|
#include "secsgem/gem/store/status_variables.hpp"
|
||||||
@@ -39,6 +40,7 @@ struct EquipmentDataModel {
|
|||||||
ExceptionStore exceptions;
|
ExceptionStore exceptions;
|
||||||
CarrierStore carriers;
|
CarrierStore carriers;
|
||||||
LoadPortStore load_ports;
|
LoadPortStore load_ports;
|
||||||
|
SubstrateStore substrates;
|
||||||
|
|
||||||
// Convenience: VID -> value lookup spanning SVIDs and DVIDs.
|
// Convenience: VID -> value lookup spanning SVIDs and DVIDs.
|
||||||
std::optional<s2::Item> vid_value(uint32_t vid) const {
|
std::optional<s2::Item> vid_value(uint32_t vid) const {
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "secsgem/gem/substrate_state.hpp"
|
||||||
|
|
||||||
|
namespace secsgem::gem {
|
||||||
|
|
||||||
|
struct Substrate {
|
||||||
|
std::string substid; // SUBSTID — E90 wafer identifier
|
||||||
|
std::string carrierid; // CARRIERID where this wafer originated
|
||||||
|
uint8_t slot = 0; // 1-based slot in the source carrier
|
||||||
|
std::string location; // free-form: equipment-defined module name
|
||||||
|
std::unique_ptr<SubstrateStateMachine> fsm;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SubstrateStore {
|
||||||
|
public:
|
||||||
|
using LocationChangeHandler =
|
||||||
|
std::function<void(const std::string& substid, SubstrateState from,
|
||||||
|
SubstrateState to, SubstrateEvent)>;
|
||||||
|
using ProcessingChangeHandler =
|
||||||
|
std::function<void(const std::string& substid,
|
||||||
|
SubstrateProcessingState from,
|
||||||
|
SubstrateProcessingState to,
|
||||||
|
SubstrateProcessingEvent)>;
|
||||||
|
|
||||||
|
SubstrateStore() = default;
|
||||||
|
SubstrateStore(const SubstrateStore&) = delete;
|
||||||
|
SubstrateStore& operator=(const SubstrateStore&) = delete;
|
||||||
|
SubstrateStore(SubstrateStore&&) = delete;
|
||||||
|
SubstrateStore& operator=(SubstrateStore&&) = delete;
|
||||||
|
|
||||||
|
void set_location_handler(LocationChangeHandler h) { on_loc_ = std::move(h); }
|
||||||
|
void set_processing_handler(ProcessingChangeHandler h) { on_proc_ = std::move(h); }
|
||||||
|
|
||||||
|
enum class CreateResult { Created, Denied_AlreadyExists };
|
||||||
|
|
||||||
|
CreateResult create(std::string substid, std::string carrierid = "",
|
||||||
|
uint8_t slot = 0) {
|
||||||
|
if (subs_.count(substid)) return CreateResult::Denied_AlreadyExists;
|
||||||
|
auto fsm = std::make_unique<SubstrateStateMachine>();
|
||||||
|
const std::string id = substid;
|
||||||
|
fsm->set_location_handler(
|
||||||
|
[this, id](SubstrateState f, SubstrateState t, SubstrateEvent e) {
|
||||||
|
if (on_loc_) on_loc_(id, f, t, e);
|
||||||
|
});
|
||||||
|
fsm->set_processing_handler(
|
||||||
|
[this, id](SubstrateProcessingState f, SubstrateProcessingState t,
|
||||||
|
SubstrateProcessingEvent e) {
|
||||||
|
if (on_proc_) on_proc_(id, f, t, e);
|
||||||
|
});
|
||||||
|
subs_.emplace(substid,
|
||||||
|
Substrate{substid, std::move(carrierid), slot, "", std::move(fsm)});
|
||||||
|
return CreateResult::Created;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has(const std::string& id) const { return subs_.count(id) > 0; }
|
||||||
|
const Substrate* get(const std::string& id) const {
|
||||||
|
auto it = subs_.find(id);
|
||||||
|
return it == subs_.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
Substrate* get(const std::string& id) {
|
||||||
|
auto it = subs_.find(id);
|
||||||
|
return it == subs_.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fire_location_event(const std::string& id, SubstrateEvent e,
|
||||||
|
std::string new_location = "") {
|
||||||
|
auto* s = get(id);
|
||||||
|
if (!s) return false;
|
||||||
|
if (!new_location.empty()) s->location = std::move(new_location);
|
||||||
|
return s->fsm->on_location_event(e);
|
||||||
|
}
|
||||||
|
bool fire_processing_event(const std::string& id, SubstrateProcessingEvent e) {
|
||||||
|
auto* s = get(id);
|
||||||
|
return s && s->fsm->on_processing_event(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remove(const std::string& id) { return subs_.erase(id) > 0; }
|
||||||
|
std::size_t size() const { return subs_.size(); }
|
||||||
|
std::vector<std::string> ids() const {
|
||||||
|
std::vector<std::string> out;
|
||||||
|
out.reserve(subs_.size());
|
||||||
|
for (const auto& kv : subs_) out.push_back(kv.first);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, Substrate> subs_;
|
||||||
|
LocationChangeHandler on_loc_;
|
||||||
|
ProcessingChangeHandler on_proc_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace secsgem::gem
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "secsgem/gem/carrier_state.hpp" // shared CarrierTransition template
|
||||||
|
|
||||||
|
// E90 §6 Substrate Tracking — per-substrate state model.
|
||||||
|
//
|
||||||
|
// E90 actually defines several related state models per substrate:
|
||||||
|
//
|
||||||
|
// STS Substrate State the processing lifecycle
|
||||||
|
// SLS Substrate Location where the wafer physically is
|
||||||
|
// IDS Substrate ID Status has the ID been verified
|
||||||
|
//
|
||||||
|
// This first pass covers the most-used STS model (E90-0716 §10.3.1) at
|
||||||
|
// full fidelity and exposes a Location string the application can
|
||||||
|
// update as it sees substrate moves. IDS / cross-state guards mirror
|
||||||
|
// the CIDS pattern from E87 and remain a follow-on.
|
||||||
|
namespace secsgem::gem {
|
||||||
|
|
||||||
|
// Wire-byte values per E90-0716 §10.3.1 STS.
|
||||||
|
enum class SubstrateState : uint8_t {
|
||||||
|
AtSource = 0,
|
||||||
|
AtWork = 1, // in-process at a module
|
||||||
|
AtDestination = 2, // moved to final location
|
||||||
|
NoState = 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* substrate_state_name(SubstrateState s);
|
||||||
|
std::optional<SubstrateState> parse_substrate_state(const std::string& s);
|
||||||
|
|
||||||
|
// "Processing" lifecycle (E90 §6.4.4); orthogonal to STS.
|
||||||
|
enum class SubstrateProcessingState : uint8_t {
|
||||||
|
NeedsProcessing = 0,
|
||||||
|
InProcess = 1,
|
||||||
|
Processed = 2,
|
||||||
|
Aborted = 3,
|
||||||
|
Stopped = 4,
|
||||||
|
Rejected = 5,
|
||||||
|
Lost = 6,
|
||||||
|
Skipped = 7,
|
||||||
|
NoState = 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* substrate_processing_state_name(SubstrateProcessingState s);
|
||||||
|
|
||||||
|
enum class SubstrateEvent {
|
||||||
|
Acquire, // AtSource -> AtWork (machine picked up the wafer)
|
||||||
|
Release, // AtWork -> AtDestination (deposited)
|
||||||
|
Return, // AtWork -> AtSource (returned without processing)
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* substrate_event_name(SubstrateEvent e);
|
||||||
|
|
||||||
|
enum class SubstrateProcessingEvent {
|
||||||
|
StartProcessing, // NeedsProcessing -> InProcess
|
||||||
|
EndProcessing, // InProcess -> Processed
|
||||||
|
Abort, // any -> Aborted
|
||||||
|
Stop, // any -> Stopped
|
||||||
|
Reject, // any -> Rejected
|
||||||
|
ReportLost, // any -> Lost
|
||||||
|
Skip, // any -> Skipped
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* substrate_processing_event_name(SubstrateProcessingEvent e);
|
||||||
|
|
||||||
|
using SubstrateTable =
|
||||||
|
CarrierTransitionTable<SubstrateState, SubstrateEvent>;
|
||||||
|
using SubstrateProcessingTable =
|
||||||
|
CarrierTransitionTable<SubstrateProcessingState, SubstrateProcessingEvent>;
|
||||||
|
|
||||||
|
SubstrateTable default_substrate_table();
|
||||||
|
SubstrateProcessingTable default_substrate_processing_table();
|
||||||
|
|
||||||
|
class SubstrateStateMachine {
|
||||||
|
public:
|
||||||
|
using LocationChangeHandler =
|
||||||
|
std::function<void(SubstrateState from, SubstrateState to, SubstrateEvent)>;
|
||||||
|
using ProcessingChangeHandler =
|
||||||
|
std::function<void(SubstrateProcessingState from,
|
||||||
|
SubstrateProcessingState to,
|
||||||
|
SubstrateProcessingEvent)>;
|
||||||
|
|
||||||
|
SubstrateStateMachine();
|
||||||
|
|
||||||
|
SubstrateState location_state() const { return loc_; }
|
||||||
|
SubstrateProcessingState processing_state() const { return proc_; }
|
||||||
|
|
||||||
|
void set_location_handler(LocationChangeHandler h) { on_loc_ = std::move(h); }
|
||||||
|
void set_processing_handler(ProcessingChangeHandler h) { on_proc_ = std::move(h); }
|
||||||
|
|
||||||
|
bool on_location_event(SubstrateEvent e);
|
||||||
|
bool on_processing_event(SubstrateProcessingEvent e);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SubstrateTable loc_table_;
|
||||||
|
SubstrateProcessingTable proc_table_;
|
||||||
|
SubstrateState loc_ = SubstrateState::AtSource;
|
||||||
|
SubstrateProcessingState proc_ = SubstrateProcessingState::NeedsProcessing;
|
||||||
|
LocationChangeHandler on_loc_;
|
||||||
|
ProcessingChangeHandler on_proc_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace secsgem::gem
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#include "secsgem/gem/substrate_state.hpp"
|
||||||
|
|
||||||
|
namespace secsgem::gem {
|
||||||
|
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateState::AtSource) == 0);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateState::AtWork) == 1);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateState::AtDestination) == 2);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::NeedsProcessing) == 0);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::InProcess) == 1);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Processed) == 2);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Aborted) == 3);
|
||||||
|
static_assert(static_cast<uint8_t>(SubstrateProcessingState::Stopped) == 4);
|
||||||
|
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::Skipped) == 7);
|
||||||
|
|
||||||
|
const char* substrate_state_name(SubstrateState s) {
|
||||||
|
switch (s) {
|
||||||
|
case SubstrateState::AtSource: return "AtSource";
|
||||||
|
case SubstrateState::AtWork: return "AtWork";
|
||||||
|
case SubstrateState::AtDestination: return "AtDestination";
|
||||||
|
case SubstrateState::NoState: return "NoState";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SubstrateState> parse_substrate_state(const std::string& s) {
|
||||||
|
if (s == "AtSource") return SubstrateState::AtSource;
|
||||||
|
if (s == "AtWork") return SubstrateState::AtWork;
|
||||||
|
if (s == "AtDestination") return SubstrateState::AtDestination;
|
||||||
|
if (s == "NoState") return SubstrateState::NoState;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* substrate_processing_state_name(SubstrateProcessingState s) {
|
||||||
|
switch (s) {
|
||||||
|
case SubstrateProcessingState::NeedsProcessing: return "NeedsProcessing";
|
||||||
|
case SubstrateProcessingState::InProcess: return "InProcess";
|
||||||
|
case SubstrateProcessingState::Processed: return "Processed";
|
||||||
|
case SubstrateProcessingState::Aborted: return "Aborted";
|
||||||
|
case SubstrateProcessingState::Stopped: return "Stopped";
|
||||||
|
case SubstrateProcessingState::Rejected: return "Rejected";
|
||||||
|
case SubstrateProcessingState::Lost: return "Lost";
|
||||||
|
case SubstrateProcessingState::Skipped: return "Skipped";
|
||||||
|
case SubstrateProcessingState::NoState: return "NoState";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* substrate_event_name(SubstrateEvent e) {
|
||||||
|
switch (e) {
|
||||||
|
case SubstrateEvent::Acquire: return "Acquire";
|
||||||
|
case SubstrateEvent::Release: return "Release";
|
||||||
|
case SubstrateEvent::Return: return "Return";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* substrate_processing_event_name(SubstrateProcessingEvent e) {
|
||||||
|
switch (e) {
|
||||||
|
case SubstrateProcessingEvent::StartProcessing: return "StartProcessing";
|
||||||
|
case SubstrateProcessingEvent::EndProcessing: return "EndProcessing";
|
||||||
|
case SubstrateProcessingEvent::Abort: return "Abort";
|
||||||
|
case SubstrateProcessingEvent::Stop: return "Stop";
|
||||||
|
case SubstrateProcessingEvent::Reject: return "Reject";
|
||||||
|
case SubstrateProcessingEvent::ReportLost: return "ReportLost";
|
||||||
|
case SubstrateProcessingEvent::Skip: return "Skip";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
SubstrateTable default_substrate_table() {
|
||||||
|
using S = SubstrateState;
|
||||||
|
using E = SubstrateEvent;
|
||||||
|
SubstrateTable t;
|
||||||
|
t.add({S::AtSource, E::Acquire, S::AtWork, std::nullopt});
|
||||||
|
t.add({S::AtWork, E::Release, S::AtDestination, std::nullopt});
|
||||||
|
t.add({S::AtWork, E::Return, S::AtSource, std::nullopt});
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubstrateProcessingTable default_substrate_processing_table() {
|
||||||
|
using S = SubstrateProcessingState;
|
||||||
|
using E = SubstrateProcessingEvent;
|
||||||
|
SubstrateProcessingTable t;
|
||||||
|
// From NeedsProcessing.
|
||||||
|
t.add({S::NeedsProcessing, E::StartProcessing, S::InProcess, std::nullopt});
|
||||||
|
t.add({S::NeedsProcessing, E::Skip, S::Skipped, std::nullopt});
|
||||||
|
t.add({S::NeedsProcessing, E::ReportLost, S::Lost, std::nullopt});
|
||||||
|
// From InProcess.
|
||||||
|
t.add({S::InProcess, E::EndProcessing, S::Processed, std::nullopt});
|
||||||
|
t.add({S::InProcess, E::Abort, S::Aborted, std::nullopt});
|
||||||
|
t.add({S::InProcess, E::Stop, S::Stopped, std::nullopt});
|
||||||
|
t.add({S::InProcess, E::Reject, S::Rejected, std::nullopt});
|
||||||
|
t.add({S::InProcess, E::ReportLost, S::Lost, std::nullopt});
|
||||||
|
// Processed/Aborted/etc are terminal.
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubstrateStateMachine::SubstrateStateMachine()
|
||||||
|
: loc_table_(default_substrate_table()),
|
||||||
|
proc_table_(default_substrate_processing_table()) {}
|
||||||
|
|
||||||
|
bool SubstrateStateMachine::on_location_event(SubstrateEvent e) {
|
||||||
|
const auto* row = loc_table_.find(loc_, e);
|
||||||
|
if (!row) return false;
|
||||||
|
if (row->to && *row->to != loc_) {
|
||||||
|
auto prev = loc_;
|
||||||
|
loc_ = *row->to;
|
||||||
|
if (on_loc_) on_loc_(prev, loc_, e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubstrateStateMachine::on_processing_event(SubstrateProcessingEvent e) {
|
||||||
|
const auto* row = proc_table_.find(proc_, e);
|
||||||
|
if (!row) return false;
|
||||||
|
if (row->to && *row->to != proc_) {
|
||||||
|
auto prev = proc_;
|
||||||
|
proc_ = *row->to;
|
||||||
|
if (on_proc_) on_proc_(prev, proc_, e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace secsgem::gem
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "secsgem/gem/store/substrates.hpp"
|
||||||
|
#include "secsgem/gem/substrate_state.hpp"
|
||||||
|
|
||||||
|
using namespace secsgem::gem;
|
||||||
|
|
||||||
|
TEST_CASE("SubstrateStateMachine: initial AtSource / NeedsProcessing") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
CHECK(s.location_state() == SubstrateState::AtSource);
|
||||||
|
CHECK(s.processing_state() == SubstrateProcessingState::NeedsProcessing);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate location: AtSource -> AtWork -> AtDestination") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
CHECK(s.on_location_event(SubstrateEvent::Acquire));
|
||||||
|
CHECK(s.location_state() == SubstrateState::AtWork);
|
||||||
|
CHECK(s.on_location_event(SubstrateEvent::Release));
|
||||||
|
CHECK(s.location_state() == SubstrateState::AtDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate location: Return from AtWork sends back to AtSource") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
s.on_location_event(SubstrateEvent::Acquire);
|
||||||
|
CHECK(s.on_location_event(SubstrateEvent::Return));
|
||||||
|
CHECK(s.location_state() == SubstrateState::AtSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate processing: happy path Start -> End -> Processed") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
CHECK(s.on_processing_event(SubstrateProcessingEvent::StartProcessing));
|
||||||
|
CHECK(s.processing_state() == SubstrateProcessingState::InProcess);
|
||||||
|
CHECK(s.on_processing_event(SubstrateProcessingEvent::EndProcessing));
|
||||||
|
CHECK(s.processing_state() == SubstrateProcessingState::Processed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate processing: Abort/Stop/Reject/Lost are terminal") {
|
||||||
|
for (auto ev : {SubstrateProcessingEvent::Abort,
|
||||||
|
SubstrateProcessingEvent::Stop,
|
||||||
|
SubstrateProcessingEvent::Reject,
|
||||||
|
SubstrateProcessingEvent::ReportLost}) {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
s.on_processing_event(SubstrateProcessingEvent::StartProcessing);
|
||||||
|
CHECK(s.on_processing_event(ev));
|
||||||
|
// Terminal: no further transitions accepted.
|
||||||
|
CHECK_FALSE(s.on_processing_event(SubstrateProcessingEvent::EndProcessing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Substrate processing: Skip from NeedsProcessing") {
|
||||||
|
SubstrateStateMachine s;
|
||||||
|
CHECK(s.on_processing_event(SubstrateProcessingEvent::Skip));
|
||||||
|
CHECK(s.processing_state() == SubstrateProcessingState::Skipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- SubstrateStore -----------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("SubstrateStore: create + dedup + location update") {
|
||||||
|
SubstrateStore s;
|
||||||
|
CHECK(s.create("W-001", "CAR-A", /*slot=*/1) ==
|
||||||
|
SubstrateStore::CreateResult::Created);
|
||||||
|
CHECK(s.create("W-001") == SubstrateStore::CreateResult::Denied_AlreadyExists);
|
||||||
|
REQUIRE(s.has("W-001"));
|
||||||
|
CHECK(s.get("W-001")->carrierid == "CAR-A");
|
||||||
|
CHECK(s.get("W-001")->slot == 1);
|
||||||
|
|
||||||
|
CHECK(s.fire_location_event("W-001", SubstrateEvent::Acquire, "MOD-A"));
|
||||||
|
CHECK(s.get("W-001")->location == "MOD-A");
|
||||||
|
CHECK(s.get("W-001")->fsm->location_state() == SubstrateState::AtWork);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SubstrateStore: change handlers observe both axes independently") {
|
||||||
|
SubstrateStore s;
|
||||||
|
std::vector<std::pair<std::string, SubstrateState>> loc_log;
|
||||||
|
std::vector<std::pair<std::string, SubstrateProcessingState>> proc_log;
|
||||||
|
s.set_location_handler([&](const std::string& id, SubstrateState,
|
||||||
|
SubstrateState to, SubstrateEvent) {
|
||||||
|
loc_log.emplace_back(id, to);
|
||||||
|
});
|
||||||
|
s.set_processing_handler([&](const std::string& id, SubstrateProcessingState,
|
||||||
|
SubstrateProcessingState to,
|
||||||
|
SubstrateProcessingEvent) {
|
||||||
|
proc_log.emplace_back(id, to);
|
||||||
|
});
|
||||||
|
s.create("W-X");
|
||||||
|
s.fire_location_event("W-X", SubstrateEvent::Acquire);
|
||||||
|
s.fire_processing_event("W-X", SubstrateProcessingEvent::StartProcessing);
|
||||||
|
s.fire_processing_event("W-X", SubstrateProcessingEvent::EndProcessing);
|
||||||
|
s.fire_location_event("W-X", SubstrateEvent::Release);
|
||||||
|
CHECK(loc_log.size() == 2);
|
||||||
|
CHECK(proc_log.size() == 2);
|
||||||
|
CHECK(loc_log.back().second == SubstrateState::AtDestination);
|
||||||
|
CHECK(proc_log.back().second == SubstrateProcessingState::Processed);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user