# ACME-PVD-3000 — worked vendor example A fictional Physical Vapor Deposition tool, end-to-end. This is what a real tool integrator's deployment looks like: - `equipment.yaml` — the tool's data dictionary (29 SVIDs, 5 DVIDs, 7 ECIDs, 21 CEIDs, 12 alarms, 3 recipes, 9 host commands) - `main.cpp` — the vendor application: sensor simulator, recipe runner, alarm threshold monitor, EPT cycling, metrics exporter, Router handlers wiring it all to the wire. If you're starting a real integration, **fork these two files** and customize. They're written to be a template, not an abstract demo. ## What it demonstrates | Section in main.cpp | What it shows you how to do | |-------------------------------|----------------------------------------------------------------------| | §1 Helpers + constants | The few `kSvidX / kCeidX` constants worth pinning at file scope | | §2 Sensor simulator | Multi-cadence sensor poll loops (10 Hz pressure, 1 Hz temps), with the `asio::post`-onto-strand thread-safety pattern | | §3 Recipe runner | Driving a PJ through SettingUp → Processing → ProcessComplete by walking the recipe body, with per-step CEID emission | | §4 Alarm threshold monitor | Continuous threshold-based alarm logic (chamber pressure, cleaning interval) with set/clear emission | | §5 EPT cycling | E116 state transitions driven by PJ state + safety alarms | | §6 main() | `EquipmentRuntime` + `register_default_handlers` (all 56 GEM handlers in one call) + `commands.set_handler` for the START behaviour | | §7 main() | Loading YAML → validating → composing → running, including the Prometheus exporter on `:9090` (§7.3) | ## Running it From repo root: ```bash # Validate the configs (this is what your CI should do). docker compose run --rm builder /app/build/secs_server --validate-config \ --config /app/examples/pvd_tool/equipment.yaml \ --state-table /app/data/control_state.yaml \ --pj-state-table /app/data/process_job_state.yaml \ --cj-state-table /app/data/control_job_state.yaml # Start the tool. docker compose run --rm builder /app/build/pvd_tool \ /app/examples/pvd_tool/equipment.yaml \ /app/data/control_state.yaml \ 5000 \ 9090 # In another shell, drive it with the conformance harness or a real host. docker compose run --rm builder /app/build/secs_conformance \ --host 127.0.0.1 --port 5000 --device 0 # 47 / 47 checks passed ``` Or via Docker Compose if you'd rather wire it as a service. ## What the host sees Once a host connects and SELECTs: 1. **S1F1 → S1F2** returns `MDLN="ACME-PVD-3000"`, `SOFTREV="1.4.2"`. 2. **S1F3** on the 32 SVIDs returns live sensor readings — chamber pressure tracks the simulator's target (default 1e-7 Torr in idle), wafer counter increments per processed PJ, EPT state gauge says `Standby`. 3. **S2F33/F35/F37** binds dynamic event reports; CEIDs 300 / 301 / 310 / 311 fire on real PJ activity. 4. **S2F41 RCMD=START** kicks the recipe runner: any PJ in WaitingForStart transitions to Processing and the simulator starts tracking the recipe's step targets. Sensor values change in real time. CEID 300 (ProcessStarted) emits, then per-step CEID 310/311, then CEID 301 (ProcessCompleted) on completion. 5. **S2F41 RCMD=FAULT** sets alarm 4 → S5F1 emitted (if enabled via S5F3 first). 6. **S7F19** lists the 3 recipes; **S7F5** returns the body (multi-line STEP definitions). 7. **S16F11** (PJ create) + **S14F9** (CJ create) + **S16F27** (CJSTART) drives the full E40/E94 lifecycle. ## What's the same as the secs-gem demo server `apps/secs_server.cpp` (used by `docker compose up server`) is the canonical fully-loaded server. This example is structurally a slimmer fork: - Same Router pattern (`gem::Router` + `router.on(s, f, [...])`) - Same event/alarm emission helpers (`deliver_or_spool`, `emit_event`, `emit_alarm_set`) - Same control-state-change handler wiring What this example adds that the demo doesn't: - **Sensor simulator** with multi-cadence poll loops. The demo's SVID values stay at their YAML defaults; PVD's drift toward recipe-step targets. - **Recipe runner** that parses the recipe body and drives the PJ FSM step-by-step. The demo's RCMD=START just emits the linked CEID; PVD actually walks the recipe. - **Alarm threshold monitor** — continuous evaluation of sensor values against ECID setpoints. The demo only fires alarms when RCMD=FAULT is sent. - **EPT cycling** — automatic Standby↔Productive↔UnscheduledDowntime based on PJ + alarm state. The demo doesn't cycle EPT. - **Prometheus metrics exporter** on a second port. The demo logs but doesn't export. If you want one of these patterns in your own tool, lift the code from `main.cpp` directly — each section is independently usable. ## What's not here - **Persistence.** The demo server's `--spool-dir` flag is the pattern to copy. Add `model->spool.enable_persistence(...)` etc. at startup before binding the port. See [`docs/INTEGRATION.md`](../../docs/INTEGRATION.md) §5. - **E84 handshake timers.** No load-port AMHS wiring; see [`docs/INTEGRATION.md`](../../docs/INTEGRATION.md) §4.6 for the `E84AsioTimers` adapter. - **Real I/O bridges.** Sensor values come from a random-walk simulator. A real PVD tool would have a PLC/serial driver module-bridge feeding `model->svids.set_value(...)` from real hardware. - **Production deployment hardening** — [`docs/SECURITY.md`](../../docs/SECURITY.md) (nftables, stunnel, minisign signing) and [`docs/INTEGRATION.md`](../../docs/INTEGRATION.md) §7 (HA pattern). ## What you'd change for *your* tool 1. **Replace `equipment.yaml`** with your tool's actual SVIDs, ECIDs, alarms, recipes. Run `secs_server --validate-config` after every edit. 2. **Replace the sensor simulator** (`pvd::Simulator`) with calls into your real hardware driver. Keep the `asio::post` pattern for cross-thread updates. 3. **Replace the recipe runner** with your real recipe engine integration. The shape — fire `Start`, walk steps, fire `ProcessComplete` — is the contract; the implementation is yours. 4. **Replace the alarm threshold monitor** with your real alarm sources (sensor interrupts, watchdog timers, hardware fault lines). Same `emit_alarm_set / emit_alarm_clear` API. 5. **Keep most of the Router handler section** — those are spec- defined and you'll need them all in production. That's it. No framework, no DI container, no abstract base classes. ~1,100 lines of vendor code on top of the library. ## Cross-references - [`docs/INTEGRATION.md`](../../docs/INTEGRATION.md) — the conceptual tutorial this example concretizes - [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md) — how stores compose, how to extend - [`docs/BENCHMARKS.md`](../../docs/BENCHMARKS.md) — what the throughput envelope looks like - [`docs/SECURITY.md`](../../docs/SECURITY.md) — production hardening configs - [`apps/secs_server.cpp`](../../apps/secs_server.cpp) — the demo server's fully-loaded Router (every handler PVD inherits + a few more)