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
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include "secsgem/gem/store/host_commands.hpp" // HostCmdAck
namespace secsgem::gem {
// E94 §6 Control Job state model.
//
// E94 governs the *batch* of work — a CJ owns an ordered list of PRJOBIDs
// (process jobs) and a processing policy. States below match E94-0705 §6
// (the values are this project's own opaque encoding — E94 does not pin a
// PRJOBSTATE-style wire enum for CJ; we surface state via S6F11 CEIDs in
// the demo).
enum class ControlJobState : uint8_t {
Queued = 0,
Selected = 1,
WaitingForStart = 2,
Executing = 3,
Paused = 4,
Completed = 5, // terminal: all PJs done, awaiting deletion
Stopping = 6,
Aborting = 7,
NoState = 255,
};
const char* control_job_state_name(ControlJobState s);
std::optional<ControlJobState> parse_control_job_state(const std::string& s);
enum class ControlJobEvent {
Created, // -> Queued
Select, // Queued -> Selected
SetupComplete, // Selected -> WaitingForStart
Start, // host: CJSTART -> Executing
Pause, // host: CJPAUSE -> Paused
Resume, // host: CJRESUME -> Executing
Stop, // host: CJSTOP -> Stopping
Abort, // host: CJABORT -> Aborting
AllJobsComplete, // internal: Executing/Stopping -> Completed
AbortComplete, // internal: Aborting -> Completed
};
const char* control_job_event_name(ControlJobEvent e);
std::optional<ControlJobEvent> parse_control_job_event(const std::string& s);
// Wire-name -> event mapping for the CTLJOBCMD string on S16F27.
std::optional<ControlJobEvent> ctl_cmd_to_event(const std::string& cmd);
struct ControlJobTransition {
ControlJobState from;
ControlJobEvent on;
std::optional<ControlJobState> to;
std::optional<uint8_t> ack_code; // HostCmdAck when applicable
};
class ControlJobTransitionTable {
public:
void add(ControlJobTransition row);
const ControlJobTransition* find(ControlJobState from,
ControlJobEvent on) const;
std::size_t size() const { return rows_.size(); }
const std::vector<ControlJobTransition>& rows() const { return rows_; }
static ControlJobTransitionTable default_table();
private:
std::vector<ControlJobTransition> rows_;
};
class ControlJobStateMachine {
public:
using StateChangeHandler =
std::function<void(ControlJobState from, ControlJobState to,
ControlJobEvent trigger)>;
ControlJobStateMachine();
explicit ControlJobStateMachine(ControlJobTransitionTable table,
ControlJobState initial = ControlJobState::Queued);
ControlJobState state() const { return state_; }
void set_state_change_handler(StateChangeHandler h) { on_change_ = std::move(h); }
HostCmdAck on_host_command(ControlJobEvent event);
bool on_internal(ControlJobEvent event);
private:
const ControlJobTransition* fire(ControlJobEvent on);
void transition(ControlJobState next, ControlJobEvent trigger);
ControlJobTransitionTable table_;
ControlJobState state_;
StateChangeHandler on_change_;
};
// S14F10 / F12 OBJACK — generic ObjectService ack. Used by the
// CreateControlJob / DeleteControlJob handlers.
enum class ObjectAck : uint8_t {
Success = 0,
Error = 1,
Denied_UnknownObject = 2,
Denied_AlreadyExists = 3,
Denied_InvalidAttribute = 4,
Denied_BadState = 5,
};
} // namespace secsgem::gem