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:
2026-06-07 21:00:32 +02:00
parent 1f67aad985
commit 90c177b7ce
33 changed files with 3122 additions and 62 deletions
+48
View File
@@ -0,0 +1,48 @@
# E94 §6 Control Job state model — encoded as a transition table.
#
# CJ governs the batch — it owns an ordered list of PRJOBIDs and
# proceeds Queued -> Selected -> WaitingForStart -> Executing ->
# Completed; from any of those a Stop or Abort diverts to Stopping or
# Aborting (then Completed).
#
# State values are this project's encoding (see control_job_state.hpp).
# Host-initiated events (start / pause / resume / stop / abort) arrive
# via S16F27; internal events (select / setup_complete /
# all_jobs_complete / abort_complete) fire from the equipment's
# application logic as the contained PJs progress.
initial: Queued
transitions:
# --- QUEUED ----------------------------------------------------------
- {from: Queued, on: select, to: Selected}
- {from: Queued, on: stop, to: Completed}
- {from: Queued, on: abort, to: Completed}
# --- SELECTED --------------------------------------------------------
- {from: Selected, on: setup_complete, to: WaitingForStart}
- {from: Selected, on: stop, to: Stopping}
- {from: Selected, on: abort, to: Aborting}
# --- WAITING-FOR-START -----------------------------------------------
- {from: WaitingForStart, on: start, to: Executing}
- {from: WaitingForStart, on: stop, to: Stopping}
- {from: WaitingForStart, on: abort, to: Aborting}
# --- EXECUTING -------------------------------------------------------
- {from: Executing, on: pause, to: Paused}
- {from: Executing, on: stop, to: Stopping}
- {from: Executing, on: abort, to: Aborting}
- {from: Executing, on: all_jobs_complete, to: Completed}
# --- PAUSED ----------------------------------------------------------
- {from: Paused, on: resume, to: Executing}
- {from: Paused, on: stop, to: Stopping}
- {from: Paused, on: abort, to: Aborting}
# --- STOPPING --------------------------------------------------------
- {from: Stopping, on: all_jobs_complete, to: Completed}
- {from: Stopping, on: abort, to: Aborting}
# --- ABORTING --------------------------------------------------------
- {from: Aborting, on: abort_complete, to: Completed}
+5
View File
@@ -26,6 +26,9 @@ capabilities:
- {code: 12, name: "Clock"}
- {code: 14, name: "Spooling (partial; S2F43/F44 + S6F23/F24)"}
- {code: 15, name: "Control (operator initiated)"}
# GEM300 extensions (declared so the host can probe with S1F19).
- {code: 40, name: "E40 Process Job Management"}
- {code: 94, name: "E94 Control Job Management"}
# Reported on S1F3 (values) and S1F11 (namelist). `value` is the initial.
# `type` controls the SECS-II Item format.
@@ -50,6 +53,8 @@ ceids:
- {id: 100, name: ControlStateChanged}
- {id: 200, name: AlarmSetEvent}
- {id: 300, name: ProcessStarted}
- {id: 400, name: ControlJobExecuting} # E94 CJ entered Executing
- {id: 401, name: ControlJobCompleted} # E94 CJ entered Completed
# Reported on S5F5 / S5F1. `category` is the lower-7 of ALCD.
alarms:
+230
View File
@@ -251,6 +251,24 @@ messages:
parser: parse_s2f18
body: {kind: scalar, item_type: ASCII, param: time_str}
# S2F25 / S2F26 — Loopback Diagnostic. Host sends an arbitrary byte
# blob; equipment echoes it back unchanged in S2F26. Useful for round-
# trip diagnostics and as a "keep-alive at the application layer."
- id: S2F25
stream: 2
function: 25
w: true
builder: s2f25_loopback_diagnostic_request
parser: parse_s2f25
body: {kind: scalar, item_type: BINARY, param: payload}
- id: S2F26
stream: 2
function: 26
builder: s2f26_loopback_diagnostic_data
parser: parse_s2f26
body: {kind: scalar, item_type: BINARY, param: payload}
- id: S2F29
stream: 2
function: 29
@@ -660,6 +678,53 @@ messages:
- {name: alid, shape: {kind: scalar, item_type: U4}}
- {name: altx, shape: {kind: scalar, item_type: ASCII}}
# S5F9 / S5F10 — Exception Post Notify / Confirm (E5).
# Equipment posts a recoverable exception; the body lists the allowed
# recovery actions that S5F13 may then send. Host acks via S5F10.
- id: S5F9
stream: 5
function: 9
w: true
builder: s5f9_exception_post_notify
parser: parse_s5f9
body:
kind: list
struct_name: ExceptionPost
fields:
- {name: exid, shape: {kind: scalar, item_type: U4}}
- {name: extype, shape: {kind: scalar, item_type: ASCII}}
- {name: exmessage, shape: {kind: scalar, item_type: ASCII}}
- name: exrecvra
shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}}
- id: S5F10
stream: 5
function: 10
builder: s5f10_exception_post_confirm
body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack}
# S5F11 / S5F12 — Exception Clear Notify / Confirm. Equipment signals
# that a previously-posted exception has cleared.
- id: S5F11
stream: 5
function: 11
w: true
builder: s5f11_exception_clear_notify
parser: parse_s5f11
body:
kind: list
struct_name: ExceptionClear
fields:
- {name: exid, shape: {kind: scalar, item_type: U4}}
- {name: extype, shape: {kind: scalar, item_type: ASCII}}
- {name: exmessage, shape: {kind: scalar, item_type: ASCII}}
- id: S5F12
stream: 5
function: 12
builder: s5f12_exception_clear_confirm
body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack}
# =====================================================================
# S6 — Data collection
# =====================================================================
@@ -909,3 +974,168 @@ messages:
function: 6
builder: s10f6_terminal_display_multi_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: TerminalAck, param: ack}
# =====================================================================
# S14 — Object services (E94 ControlJob create/delete).
#
# Real E14 ObjectService is a fully generic generic-attribute carrier;
# for the E94 demo we use a simplified shape that names the object
# type ("ControlJob") and carries the CTLJOBID + contained PRJOBIDs
# directly. The wire shape is a defensible subset of E14F9 with the
# generic ATTRIBUTES list specialized.
# =====================================================================
# S14F9 / F10 — CreateObject (CJ). Host asks the equipment to bring
# a new ControlJob into existence and registers the list of PJs it
# contains; the CJ starts in CJ-QUEUED.
- id: S14F9
stream: 14
function: 9
w: true
builder: s14f9_create_control_job
parser: parse_s14f9
body:
kind: list
struct_name: CreateControlJobRequest
fields:
- {name: ctljobid, shape: {kind: scalar, item_type: ASCII}}
- name: prjobids
shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}}
- id: S14F10
stream: 14
function: 10
builder: s14f10_create_control_job_ack
parser: parse_s14f10
body:
kind: list
struct_name: CreateControlJobAck
fields:
- {name: ctljobid, shape: {kind: scalar, item_type: ASCII}}
- {name: ack, shape: {kind: scalar, item_type: BINARY_BYTE, enum: ObjectAck}}
# S14F11 / F12 — DeleteObject (CJ).
- id: S14F11
stream: 14
function: 11
w: true
builder: s14f11_delete_control_job
parser: parse_s14f11
body: {kind: scalar, item_type: ASCII, param: ctljobid}
- id: S14F12
stream: 14
function: 12
builder: s14f12_delete_control_job_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: ObjectAck, param: ack}
# =====================================================================
# S16 — Process Management (E40 Process Jobs, E94 Control Jobs).
#
# Wire shapes are E40-0705 / E94-0705 with a couple of deliberate
# simplifications documented inline. The PJ/CJ state set and the legal
# transitions live in data/process_job_state.yaml and
# data/control_job_state.yaml respectively (data-driven, same pattern
# as data/control_state.yaml).
# =====================================================================
# S16F5 / F6 — PRJobCommand. Host issues Start / Pause / Resume /
# Stop / Abort / HOQ against an existing process job.
- id: S16F5
stream: 16
function: 5
w: true
builder: s16f5_pr_job_command
parser: parse_s16f5
body:
kind: list
struct_name: PRJobCommandRequest
fields:
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
- {name: prcmd, shape: {kind: scalar, item_type: ASCII}}
- id: S16F6
stream: 16
function: 6
builder: s16f6_pr_job_command_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
# S16F9 — PRJobAlert. E->H one-way alert that a PJ entered a new
# state. PRJOBSTATE is the E40 PJ state code (0..7). Equipment
# emits one per PJ state transition when alerts are enabled.
- id: S16F9
stream: 16
function: 9
builder: s16f9_pr_job_alert
parser: parse_s16f9
body:
kind: list
struct_name: PRJobAlert
fields:
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
- {name: prjobstate, shape: {kind: scalar, item_type: BINARY_BYTE, enum: ProcessJobState}}
# S16F11 / F12 — PRJobCreate.
#
# Real E40-0705 S16F11 body is <L,5 PRJOBID MF PRRECIPEMETHOD
# RCPSPEC L MTRLOUTSPEC L PRPROCESSPARAMS>. We simplify to the
# three pieces that actually drive the demo state machine:
# PRJOBID, recipe (PPID), and the list of material identifiers. MF
# / PRRECIPEMETHOD / PRPROCESSPARAMS are tool-specific; layering
# them in is a YAML edit + builder overload, not surgery.
- id: S16F11
stream: 16
function: 11
w: true
builder: s16f11_pr_job_create
parser: parse_s16f11
body:
kind: list
struct_name: PRJobCreateRequest
fields:
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
- {name: ppid, shape: {kind: scalar, item_type: ASCII}}
- name: mtrloutspec
shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}}
- id: S16F12
stream: 16
function: 12
builder: s16f12_pr_job_create_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
# S16F13 / F14 — PRJobDequeue. Host removes a queued PJ.
- id: S16F13
stream: 16
function: 13
w: true
builder: s16f13_pr_job_dequeue
parser: parse_s16f13
body: {kind: scalar, item_type: ASCII, param: prjobid}
- id: S16F14
stream: 16
function: 14
builder: s16f14_pr_job_dequeue_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
# S16F27 / F28 — CJobCommand (E94). Host issues Start / Pause /
# Resume / Stop / Abort against an existing CJ.
- id: S16F27
stream: 16
function: 27
w: true
builder: s16f27_cj_command
parser: parse_s16f27
body:
kind: list
struct_name: CJobCommandRequest
fields:
- {name: ctljobid, shape: {kind: scalar, item_type: ASCII}}
- {name: ctljobcmd, shape: {kind: scalar, item_type: ASCII}}
- id: S16F28
stream: 16
function: 28
builder: s16f28_cj_command_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
+57
View File
@@ -0,0 +1,57 @@
# E40 §6 Process Job state model — encoded as a transition table.
#
# Each row is a (from, on) -> (to [, ack]) tuple, mirroring the encoding
# in data/control_state.yaml. Events are the lower-snake-case verbs the
# loader maps to ProcessJobEvent: start / pause / resume / stop / abort /
# hoq / select / setup_complete / process_complete / abort_complete /
# created. Host-initiated events (start, pause, resume, stop, abort,
# hoq) carry an `ack` when the row should override the default
# HostCmdAck::Accept (e.g. a row that only validates an event without
# changing state).
#
# Wire reference: S16F9 PRJOBSTATE byte (E40-0705 §10.3.2):
# 0 Queued, 1 SettingUp, 2 WaitingForStart, 3 Processing,
# 4 ProcessComplete, 5 Paused, 6 Stopping, 7 Aborting.
#
# Process-Complete is terminal — deletion happens out-of-band via the
# ProcessJobStore (CJ cleanup or explicit S16F13 while still QUEUED).
initial: Queued
transitions:
# --- QUEUED ----------------------------------------------------------
# PJSTOP/PJABORT on a Queued PJ routes through Aborting so the host
# observes PRJOBSTATE=7 on the wire (E40-0705 §6.3). PJDELETE / S16F13
# is the dequeue-without-state-change path.
- {from: Queued, on: select, to: SettingUp}
- {from: Queued, on: hoq} # reorder only, no state change
- {from: Queued, on: stop, to: Aborting}
- {from: Queued, on: abort, to: Aborting}
# --- SETTING-UP -------------------------------------------------------
- {from: SettingUp, on: setup_complete, to: WaitingForStart}
- {from: SettingUp, on: stop, to: Stopping}
- {from: SettingUp, on: abort, to: Aborting}
# --- WAITING-FOR-START -----------------------------------------------
- {from: WaitingForStart, on: start, to: Processing}
- {from: WaitingForStart, on: stop, to: Stopping}
- {from: WaitingForStart, on: abort, to: Aborting}
# --- PROCESSING -------------------------------------------------------
- {from: Processing, on: pause, to: Paused}
- {from: Processing, on: stop, to: Stopping}
- {from: Processing, on: abort, to: Aborting}
- {from: Processing, on: process_complete, to: ProcessComplete}
# --- PAUSED ----------------------------------------------------------
- {from: Paused, on: resume, to: Processing}
- {from: Paused, on: stop, to: Stopping}
- {from: Paused, on: abort, to: Aborting}
# --- STOPPING --------------------------------------------------------
- {from: Stopping, on: process_complete, to: ProcessComplete}
- {from: Stopping, on: abort, to: Aborting}
# --- ABORTING --------------------------------------------------------
- {from: Aborting, on: abort_complete, to: ProcessComplete}