a2ebbf7c65
Python client:
- eq.names.event.* / .alarm.* / .command.* / .var.* / .constant.* —
autocomplete-able, typo-safe name lookup backed by the Describe RPC
(lazy, cached; AttributeError on bad name with close-match hints)
- @eq.command decorator — binds a handler by function name, validated
against the equipment's real command set at decoration time
- eq.report_substrate() — E90 wafer milestone reporting
- eq.report_module() — E157 module state reporting (auto-create)
Daemon (C++ service):
- ReportSubstrate RPC — drives E90 location + processing FSMs
- ReportModule RPC — drives E157 module FSM (auto-create on first report)
- ack_from_outcome() helper — consistent Ack mapping for read_sync results
Proto: SubstrateReport, ModuleReport, EquipmentDescription,
SpoolFlushRequest, TerminalMessage; Describe, FlushSpool,
SendTerminalMessage RPCs
Tests: C++ FSM test (journey + ghost rejection + E157 illegal jump);
interop coverage for names API and E90/E157 round-trip
Docs: ch42 RPC table + Python example updated; ch16 daemon-path section added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
357 lines
14 KiB
Protocol Buffer
357 lines
14 KiB
Protocol Buffer
syntax = "proto3";
|
|
|
|
package secsgem.v1;
|
|
|
|
// =============================================================================
|
|
// SECS/GEM Equipment API
|
|
//
|
|
// The daemon *is* one piece of SEMI equipment: it owns the HSMS link to the
|
|
// host (MES) and speaks GEM on your behalf. Your tool software connects as a
|
|
// client and only ever does two things:
|
|
//
|
|
// • tell the equipment about itself — set variables, fire events, raise alarms
|
|
// • react to what the host asks for — receive commands/jobs, answer them
|
|
//
|
|
// Everything SECS lives inside the daemon: message framing, report definitions,
|
|
// the GEM state machines, timers, spooling. You need no SEMI knowledge to use
|
|
// this API. Items are addressed by the human names from your equipment config
|
|
// (e.g. "chamber_pressure"), never by numeric SVID / CEID / ALID.
|
|
//
|
|
// CAPABILITY TIERS — wire up only what your equipment is:
|
|
// • Universal — variables, events, alarms, control state, commands. Every tool.
|
|
// • Carriers — E87 carrier/load-port flows. Only carrier-based equipment.
|
|
// • Recipes — S7 process-program transfer. Only recipe-driven equipment.
|
|
// • Jobs — E40 process jobs. Only job-based process/front-end equipment.
|
|
// If a tier doesn't apply, you simply never receive its HostRequest variants and
|
|
// never call its report RPCs.
|
|
// =============================================================================
|
|
|
|
service Equipment {
|
|
// ---- Universal: report state to the host --------------------------------
|
|
|
|
// Update one or more status/data variables by name. The daemon remembers the
|
|
// values, so the host always sees the latest when it polls (S1F3).
|
|
rpc SetVariables(VariableUpdate) returns (Ack);
|
|
|
|
// Read back what the daemon currently holds (useful on tool restart/reconnect).
|
|
rpc GetVariables(VariableQuery) returns (VariableSnapshot);
|
|
|
|
// Fire a collection event by name. The daemon assembles the configured report
|
|
// and sends S6F11. Values in `data` override current values for this one event.
|
|
rpc FireEvent(Event) returns (Ack);
|
|
|
|
// Raise (S5F1 set) or clear an alarm by name.
|
|
rpc SetAlarm(Alarm) returns (Ack);
|
|
rpc ClearAlarm(Alarm) returns (Ack);
|
|
|
|
// ---- Universal: control state -------------------------------------------
|
|
|
|
// Current GEM control state (ONLINE/LOCAL/REMOTE/OFFLINE).
|
|
rpc GetControlState(Empty) returns (ControlState);
|
|
|
|
// Request a transition — e.g. an operator panel taking the tool OFFLINE for
|
|
// maintenance, or back ONLINE. The daemon applies E30 rules and may decline.
|
|
rpc RequestControlState(ControlStateRequest) returns (Ack);
|
|
|
|
// ---- Universal: react to the host ---------------------------------------
|
|
|
|
// Subscribe to everything the host asks of this equipment. The daemon streams
|
|
// HostRequest messages for as long as the call stays open.
|
|
//
|
|
// Delivery contract (v1):
|
|
// - firehose: every subscriber receives every host request;
|
|
// - NO buffering: a command arriving while no client is subscribed is
|
|
// answered with its declarative ack from the equipment config (the
|
|
// pre-daemon behaviour) and is NOT replayed on reconnect — the daemon
|
|
// never tells the host "will finish later" for work no tool will do;
|
|
// - when a Command does arrive here, the host has ALREADY been answered
|
|
// with S2F42 HCACK=4; report the real outcome via FireEvent/SetAlarm.
|
|
rpc Subscribe(SubscribeRequest) returns (stream HostRequest);
|
|
|
|
// Report the outcome of a Command delivered on the stream, quoting its `id`.
|
|
// NOTE the contract (SEMI-conformant, non-blocking): the daemon has ALREADY
|
|
// answered the host with S2F42 HCACK=4 ("accepted, will finish later") when
|
|
// it pushed the command onto the stream — the host's transaction is closed.
|
|
// CompleteCommand therefore correlates/audits the command lifecycle; the
|
|
// host learns the real outcome via the events/alarms you fire (FireEvent /
|
|
// SetAlarm), exactly as E30 intends. A synchronous gating mode (tool decides
|
|
// the HCACK before S2F42 goes out) is a possible v2 extension.
|
|
rpc CompleteCommand(CommandResult) returns (Ack);
|
|
|
|
// ---- Jobs / Carriers: report progress of work the host asked for --------
|
|
// Keyed by the durable id (job_id / carrier_id), not a per-message id — these
|
|
// are long-lived objects you report against as the physical work proceeds.
|
|
|
|
rpc ReportProcessJob(ProcessJobState) returns (Ack); // E40 — job-based tools
|
|
|
|
// E87 — carrier-based tools. WAITING announces a physically-arrived
|
|
// carrier (creates it; idempotent — re-announce updates the slot map);
|
|
// IN_ACCESS / COMPLETE drive the access FSM. The host's S3F17 decisions
|
|
// (ProceedWithCarrier / CancelCarrier) come back on the Subscribe stream
|
|
// as CarrierAction.
|
|
rpc ReportCarrier(CarrierState) returns (Ack);
|
|
|
|
// E90 — substrate (wafer) tracking. Report each milestone of a wafer's
|
|
// journey; the daemon drives the E90 FSMs and emits the standard CEIDs to
|
|
// the host. ARRIVED creates the substrate. Only for tools that track
|
|
// individual substrates.
|
|
rpc ReportSubstrate(SubstrateReport) returns (Ack);
|
|
|
|
// E157 — module process tracking. Report a module's execution state; the
|
|
// daemon drives the E157 FSM and emits the standard CEIDs. The module is
|
|
// auto-created on first report. Only for tools with tracked modules.
|
|
rpc ReportModule(ModuleReport) returns (Ack);
|
|
|
|
// ---- Diagnostics --------------------------------------------------------
|
|
|
|
// Live daemon/link status: distinguishes "host went offline" from "cable
|
|
// unplugged" from "spool filling up". Streams a snapshot on every change.
|
|
rpc WatchHealth(Empty) returns (stream Health);
|
|
|
|
// Everything this equipment is configured with, by name — for tooling,
|
|
// diagnostics, and client-side validation/autocomplete.
|
|
rpc Describe(Empty) returns (EquipmentDescription);
|
|
|
|
// Flush the spool: purge=true discards queued messages, purge=false drains
|
|
// them toward the host (requires a SELECTED session).
|
|
rpc FlushSpool(SpoolFlushRequest) returns (Ack);
|
|
|
|
// Equipment-initiated operator message to the host (S10F1). Fails with
|
|
// CANNOT_DO_NOW when no host is connected and stream 10 isn't spoolable.
|
|
rpc SendTerminalMessage(TerminalMessage) returns (Ack);
|
|
}
|
|
|
|
// ---- Values ----------------------------------------------------------------
|
|
|
|
// A SECS-II value in plain terms. The daemon converts it to the wire format
|
|
// declared for the target item in your config — you never choose U4/F4/ASCII.
|
|
message Value {
|
|
oneof kind {
|
|
string text = 1;
|
|
sint64 integer = 2;
|
|
double real = 3;
|
|
bool boolean = 4;
|
|
bytes binary = 5;
|
|
List list = 6; // SECS-II nested list
|
|
}
|
|
}
|
|
message List { repeated Value items = 1; }
|
|
|
|
message Empty {}
|
|
|
|
// ---- Universal: tool -> equipment ------------------------------------------
|
|
|
|
message VariableUpdate {
|
|
map<string, Value> values = 1; // name -> value
|
|
}
|
|
|
|
message VariableQuery {
|
|
repeated string names = 1; // empty = all configured variables
|
|
}
|
|
message VariableSnapshot {
|
|
map<string, Value> values = 1;
|
|
}
|
|
|
|
message Event {
|
|
string name = 1; // collection-event name
|
|
map<string, Value> data = 2; // optional per-fire variable values
|
|
}
|
|
|
|
message Alarm {
|
|
string name = 1; // alarm name
|
|
}
|
|
|
|
message SubscribeRequest {
|
|
string client = 1; // optional label for logging/diagnostics
|
|
}
|
|
|
|
// ---- Control state ---------------------------------------------------------
|
|
|
|
message ControlState {
|
|
State state = 1;
|
|
enum State {
|
|
EQUIPMENT_OFFLINE = 0;
|
|
ATTEMPT_ONLINE = 1;
|
|
HOST_OFFLINE = 2;
|
|
ONLINE_LOCAL = 3;
|
|
ONLINE_REMOTE = 4;
|
|
}
|
|
}
|
|
message ControlStateRequest {
|
|
ControlState.State desired = 1;
|
|
}
|
|
|
|
// ---- Host -> tool stream ---------------------------------------------------
|
|
|
|
// Everything the host initiates arrives here. Match on the populated variant;
|
|
// ignore the variants for tiers your equipment doesn't implement.
|
|
message HostRequest {
|
|
oneof request {
|
|
Command command = 1; // S2F41/F21/F49 — universal
|
|
ControlStateChange control_state = 2; // control state transitioned — universal
|
|
ConstantChange constant = 3; // host set an equipment constant — universal
|
|
ProcessProgram process_program = 4; // S7 recipe downloaded — recipe tools
|
|
CarrierAction carrier = 5; // E87 carrier at a port — carrier tools
|
|
ProcessJob process_job = 6; // E40 job to run/stop — job tools
|
|
}
|
|
}
|
|
|
|
// A remote command (S2F41 / S2F21 / S2F49). Answer with CompleteCommand.
|
|
message Command {
|
|
string id = 1; // correlation id — echo in CommandResult
|
|
string name = 2; // RCMD, e.g. "START"
|
|
map<string, Value> params = 3; // CPNAME -> CPVAL
|
|
}
|
|
|
|
// Control state changed (host went online/offline, operator toggled local/remote).
|
|
message ControlStateChange {
|
|
ControlState.State state = 1;
|
|
}
|
|
|
|
// The host wrote an equipment constant (S2F15). React if it tunes a process
|
|
// parameter you care about; the daemon already stored the new value.
|
|
message ConstantChange {
|
|
string name = 1;
|
|
Value value = 2;
|
|
}
|
|
|
|
// The host downloaded a recipe (S7F3). Load it into your process engine.
|
|
message ProcessProgram {
|
|
string ppid = 1; // recipe id
|
|
bytes body = 2; // recipe contents (opaque to the daemon)
|
|
}
|
|
|
|
// E87: a carrier needs attention at a load port. Reply with ReportCarrier.
|
|
message CarrierAction {
|
|
string carrier_id = 1; // CARRIERID
|
|
uint32 port = 2; // load-port number
|
|
Action action = 3;
|
|
enum Action {
|
|
VERIFY_ID = 0; // read & confirm the carrier's identity
|
|
PROCEED = 1; // begin access (open / map slots)
|
|
CANCEL = 2;
|
|
}
|
|
}
|
|
|
|
// E40: the host wants this process job run (or stopped). Report progress with
|
|
// ReportProcessJob. `recipe` + `carriers` tell you what to run and on what.
|
|
message ProcessJob {
|
|
string job_id = 1; // PRJobID
|
|
string recipe = 2; // PPID to run
|
|
Action action = 3;
|
|
repeated string carriers = 4; // material: carrier ids bound to this job
|
|
enum Action { START = 0; STOP = 1; PAUSE = 2; RESUME = 3; ABORT = 4; }
|
|
}
|
|
|
|
// ---- Tool -> equipment: replies & progress reports -------------------------
|
|
|
|
message CommandResult {
|
|
string id = 1; // the Command.id you are answering
|
|
Ack ack = 2; // outcome (maps to HCACK on the wire)
|
|
}
|
|
|
|
// Advance an E40 process job as the physical work proceeds; the daemon drives
|
|
// the E40 FSM and emits the matching S6F11 / S16F9 to the host.
|
|
message ProcessJobState {
|
|
string job_id = 1;
|
|
State state = 2;
|
|
enum State {
|
|
SETTING_UP = 0;
|
|
PROCESSING = 1;
|
|
COMPLETE = 2;
|
|
ABORTED = 3;
|
|
}
|
|
}
|
|
|
|
// Report a carrier's identity, slot map, and state as the tool reads them.
|
|
message CarrierState {
|
|
string carrier_id = 1;
|
|
uint32 port = 2;
|
|
State state = 3;
|
|
repeated bool slots = 4; // slot map: true = wafer present
|
|
enum State {
|
|
WAITING = 0;
|
|
IN_ACCESS = 1;
|
|
COMPLETE = 2;
|
|
}
|
|
}
|
|
|
|
// ---- Diagnostics -----------------------------------------------------------
|
|
|
|
message Health {
|
|
LinkState link = 1;
|
|
uint32 spool_depth = 2; // queued messages waiting for the host
|
|
ControlState.State control_state = 3;
|
|
enum LinkState {
|
|
DISCONNECTED = 0; // no TCP
|
|
CONNECTED = 1; // TCP up, not yet SELECTED
|
|
SELECTED = 2; // HSMS selected — actively talking to the host
|
|
}
|
|
}
|
|
|
|
// ---- Material tracking (E90 / E157) ----------------------------------------
|
|
|
|
message SubstrateReport {
|
|
string substrate_id = 1;
|
|
Milestone milestone = 2;
|
|
string carrier_id = 3; // optional; recorded on ARRIVED
|
|
uint32 slot = 4; // optional; 1-based slot within the carrier
|
|
enum Milestone {
|
|
ARRIVED = 0; // create; AtSource / NeedsProcessing
|
|
AT_WORK = 1; // picked up for processing
|
|
PROCESSING = 2; // processing started
|
|
PROCESSED = 3; // processing finished
|
|
AT_DESTINATION = 4; // returned / deposited
|
|
}
|
|
}
|
|
|
|
message ModuleReport {
|
|
string module_id = 1;
|
|
State state = 2;
|
|
enum State {
|
|
NOT_EXECUTING = 0;
|
|
GENERAL_EXECUTING = 1;
|
|
STEP_EXECUTING = 2;
|
|
STEP_COMPLETED = 3;
|
|
}
|
|
}
|
|
|
|
// ---- Diagnostics & operations -----------------------------------------------
|
|
|
|
message EquipmentDescription {
|
|
string model_name = 1;
|
|
string software_rev = 2;
|
|
repeated string variables = 3; // SVID + DVID names
|
|
repeated string events = 4; // collection-event names
|
|
repeated string alarms = 5; // alarm names (or stringified ALIDs)
|
|
repeated string commands = 6; // RCMDs the host may send
|
|
repeated string constants = 7; // equipment-constant names
|
|
}
|
|
|
|
message SpoolFlushRequest {
|
|
bool purge = 1; // true = discard, false = drain to host
|
|
}
|
|
|
|
message TerminalMessage {
|
|
uint32 tid = 1; // terminal id (0 = main)
|
|
string text = 2;
|
|
}
|
|
|
|
// ---- Acknowledgement -------------------------------------------------------
|
|
|
|
// Mirrors SEMI HCACK exactly. For non-command RPCs, only ACCEPT vs an error
|
|
// code matters; `message` carries human detail ("no variable named 'presure'").
|
|
message Ack {
|
|
Code code = 1;
|
|
string message = 2;
|
|
enum Code {
|
|
ACCEPT = 0; // HCACK 0
|
|
INVALID_COMMAND = 1; // HCACK 1
|
|
CANNOT_DO_NOW = 2; // HCACK 2
|
|
PARAMETER_INVALID = 3; // HCACK 3
|
|
ACCEPTED_WILL_FINISH_LATER = 4; // HCACK 4
|
|
REJECTED = 5; // HCACK 5
|
|
INVALID_OBJECT = 6; // HCACK 6
|
|
}
|
|
}
|