E40 Process Jobs + E94 Control Jobs + E30 communication state
GEM300 layer: SEMI E40-0705 Process Job and E94-0705 Control Job state machines, plus the E30 §6.1 communication-state machine that sits between HSMS SELECT and full GEM communication. Data-driven via data/process_job_state.yaml and data/control_job_state.yaml, mirroring the existing control_state.yaml pattern. Wire coverage: S14F9/F10 CreateObject (CJ) host -> equipment S14F11/F12 DeleteObject (CJ) host -> equipment S16F5/F6 PRJobCommand host -> equipment S16F9 PRJobAlert equipment -> host S16F11/F12 PRJobCreate (simplified body) host -> equipment S16F13/F14 PRJobDequeue host -> equipment S16F27/F28 CJobCommand host -> equipment Process Job FSM exposes 8 states matching PRJOBSTATE bytes (E40 §10.3.2); HOQ is reorder-aware (move-to-head against an insertion-order vector); Stop/Abort on a Queued PJ routes through ABORTING so the host observes PRJOBSTATE=7 on the wire (§6.3); alert_enabled is settable per-PJ for PRALERT control; FSM dispatches through ProcessJobStore::on_change_ dynamically so a late set_state_change_handler() reaches existing PJs. Hardening: loader rejects NoState (sentinel) as initial/from/to and rejects `on: created` rows; static_asserts pin enum values to wire bytes; ProcessJobStore is non-movable to keep the per-PJ this-capture safe. Server simulator cascades the full CJ -> PJ lifecycle on CJSTART so the wire trace exercises every legal state. CEIDs 400/401 fire on CJ state changes via the existing event-report pipeline. Tests: 60+ new assertions across test_process_jobs, test_control_jobs, test_communication_state, test_hsms_connection, plus loader and messages round-trip coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+74
-2
@@ -40,6 +40,8 @@ constexpr uint32_t kDataIdReports = 7;
|
||||
constexpr uint32_t kRptidStatus = 1000;
|
||||
constexpr uint32_t kCeidProcessStarted = 300;
|
||||
constexpr uint32_t kCeidAlarmSetEvent = 200;
|
||||
constexpr uint32_t kCeidCJExecuting = 400;
|
||||
constexpr uint32_t kCeidCJCompleted = 401;
|
||||
constexpr uint32_t kAlarmChiller = 1;
|
||||
|
||||
struct Sequence : std::enable_shared_from_this<Sequence> {
|
||||
@@ -102,6 +104,16 @@ int main(int argc, char** argv) {
|
||||
logfn("SPOOL READY: " + std::to_string(n.value_or(0)) + " queued messages");
|
||||
return gem::s6f26_spool_data_ready_ack(gem::EventReportAck::Accept);
|
||||
}
|
||||
// S16F9: E40 PRJob alert (one-way, E->H).
|
||||
if (msg.stream == 16 && msg.function == 9) {
|
||||
auto a = gem::parse_s16f9(msg);
|
||||
if (a) {
|
||||
logfn("PJ ALERT " + a->prjobid + " state=" +
|
||||
gem::process_job_state_name(a->prjobstate));
|
||||
}
|
||||
if (msg.reply_expected) return s2::Message(16, 0, false);
|
||||
return std::nullopt;
|
||||
}
|
||||
// S5F1: alarm send from equipment.
|
||||
if (msg.stream == 5 && msg.function == 1) {
|
||||
auto a = gem::parse_s5f1(msg);
|
||||
@@ -241,7 +253,9 @@ int main(int argc, char** argv) {
|
||||
conn->send_request(
|
||||
gem::s2f35_link_event_report(kDataIdReports,
|
||||
{{kCeidProcessStarted, {kRptidStatus}},
|
||||
{kCeidAlarmSetEvent, {kRptidStatus}}}),
|
||||
{kCeidAlarmSetEvent, {kRptidStatus}},
|
||||
{kCeidCJExecuting, {kRptidStatus}},
|
||||
{kCeidCJCompleted, {kRptidStatus}}}),
|
||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||
if (ec) { fail("S2F35", ec); return; }
|
||||
auto a = gem::ack_byte(reply);
|
||||
@@ -253,7 +267,8 @@ int main(int argc, char** argv) {
|
||||
// 8. Enable the linked CEIDs.
|
||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||
conn->send_request(
|
||||
gem::s2f37_enable_event(true, {kCeidProcessStarted, kCeidAlarmSetEvent}),
|
||||
gem::s2f37_enable_event(true, {kCeidProcessStarted, kCeidAlarmSetEvent,
|
||||
kCeidCJExecuting, kCeidCJCompleted}),
|
||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||
if (ec) { fail("S2F37", ec); return; }
|
||||
auto a = gem::ack_byte(reply);
|
||||
@@ -391,6 +406,63 @@ int main(int argc, char** argv) {
|
||||
});
|
||||
});
|
||||
|
||||
// ---- E40/E94: create a PJ, wrap it in a CJ, start the CJ ----------
|
||||
|
||||
// 14a. S16F11 PRJobCreate PJ-1 with recipe RECIPE-A and 2 wafers.
|
||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||
conn->send_request(
|
||||
gem::s16f11_pr_job_create("PJ-1", "RECIPE-A", {"WFR-1", "WFR-2"}),
|
||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||
if (ec) { fail("S16F11", ec); return; }
|
||||
auto a = gem::ack_byte(reply);
|
||||
logfn("S16F12 HCACK=" + (a ? std::to_string(*a) : "?"));
|
||||
next();
|
||||
});
|
||||
});
|
||||
// 14b. S14F9 CreateControlJob CJ-1 containing [PJ-1].
|
||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||
conn->send_request(
|
||||
gem::s14f9_create_control_job("CJ-1", {"PJ-1"}),
|
||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||
if (ec) { fail("S14F9", ec); return; }
|
||||
auto r = gem::parse_s14f10(reply);
|
||||
if (r)
|
||||
logfn("S14F10 CJ=" + r->ctljobid + " OBJACK=" +
|
||||
std::to_string(static_cast<int>(r->ack)));
|
||||
next();
|
||||
});
|
||||
});
|
||||
// 14c. S16F27 CJSTART CJ-1 -> equipment cascades the PJ through every
|
||||
// state, host sees a burst of S16F9 alerts + S6F11(CEID=400/401).
|
||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||
conn->send_request(
|
||||
gem::s16f27_cj_command("CJ-1", "CJSTART"),
|
||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||
if (ec) { fail("S16F27", ec); return; }
|
||||
auto a = gem::ack_byte(reply);
|
||||
logfn("S16F28 HCACK=" + (a ? std::to_string(*a) : "?"));
|
||||
next();
|
||||
});
|
||||
});
|
||||
// 14d. Pace 200ms so the asynchronous S16F9 / S6F11 alerts arrive
|
||||
// before we move on to terminal display.
|
||||
seq->steps.push_back([pause_then](auto next) {
|
||||
pause_then(std::chrono::milliseconds(200), [next] { next(); });
|
||||
});
|
||||
// 14e. Tidy up: delete the CJ via S14F11. The contained PJs are now
|
||||
// ProcessComplete; the equipment leaves them in the store until
|
||||
// explicitly dequeued (which can be wired similarly via S16F13).
|
||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||
conn->send_request(
|
||||
gem::s14f11_delete_control_job("CJ-1"),
|
||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||
if (ec) { fail("S14F11", ec); return; }
|
||||
auto a = gem::ack_byte(reply);
|
||||
logfn("S14F12 OBJACK=" + (a ? std::to_string(*a) : "?"));
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// 15. Send a terminal display to the equipment.
|
||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||
conn->send_request(gem::s10f1_terminal_display_single(0, "Hello equipment!"),
|
||||
|
||||
Reference in New Issue
Block a user