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:
@@ -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
|
||||
Reference in New Issue
Block a user