# Stop Drawing Screens: Why Hand-Crafted HMIs Always Drift from PLC Logic

Category: decisions
Date: 2026-08-28
Canonical: https://cellwright.ai/blog/stop-drawing-screens-hmis-generated-from-manifests
Derived from: 0013-generated-hmis.md (https://github.com/burnt-toast76/OCM/blob/main/docs/decisions/0013-generated-hmis.md), 0004-packml-mandatory.md (https://github.com/burnt-toast76/OCM/blob/main/docs/decisions/0004-packml-mandatory.md)

Hand-drawn HMIs inflate project quotes and drift from PLC logic. We generate operator and engineering interfaces directly from module manifests.

---
Every automation quote contains a substantial line item for HMI development. Integrators typically build two distinct interfaces from scratch: an operator interface for cycle starts, stops, and fault banners, and an engineering interface for jogging axes, forcing outputs, and testing individual station operations.

This workflow causes predictable problems. The screen layout is drawn in a proprietary software package, disconnected from the PLC tag database and robot controller logic. The moment an engineer changes a sensor mapping, adjusts a travel limit, or adds an interlock on the shop floor without updating the screen project, the HMI drifts. Maintenance teams end up troubleshooting phantom tags, while operators face vague alarm banners that say "Station Fault" with no indication of which precondition failed.

Under ADR-0013, we stopped drawing screens. Both operator and engineering HMIs are generated directly from the cell's resolved manifests. When a module is added to `cell.yaml`, its interface components render automatically.

### The Manifest as the UI Schema

The cell manifest contains the complete functional contract of the hardware: its PackML state definitions, commanded actions, diagnostic channels, and parameter bounds. The generator maps these schema definitions directly to interface components.

| Screen Element | Manifest Source |
|---|---|
| Cell state, start/stop/hold, andon | PackML roles (`packml_cmd`/`packml_state`) |
| Fault display, root-caused per channel | `fault_code` and per-channel diagnostics |
| Engineer op execution with bounds | `capabilities` parameter schemas |
| Live interlock explanations | `preconditions` evaluated against signal state |
| Maintenance due list | `maintenance.wear_items` intervals versus cycle counts |
| Part traceability | `results` definitions and coordinator JSON logs |

Because the manifest is the cells definition, we do not design manual entry dialogs. If an engineer needs to execute an op on a dispensing head or a screwdriving spindle, the input fields, units, and numeric bounds come directly from the module's capability definition.

### Two Audiences, Zero Hand-Drawn Panels

The generator creates two separate views tailored to operational roles:

1. **Operator HMI:** A sparse, high-contrast interface designed for production use. It displays current state, large glove-friendly start and stop buttons, active part counters, and an andon banner. When a fault occurs, it does not display an unmapped integer code. It displays the plain-language diagnostic string mapped to the specific channel reported by the manifest.

2. **Engineer HMI:** A diagnostic interface providing live signal inspection, joint jog controls, wear-item tracking, and direct operation execution. Every module declared in `cell.yaml` receives a dedicated faceplate automatically.

### Guardrails and Refusals in Engineering Mode

Manual engineering controls on traditional HMIs are dangerous because they often bypass station interlocks or allow out-of-range setpoints. Controls engineers write manual screens quickly during commissioning, frequently omitting the safety checks built into the automatic sequence.

In our architecture, manual op execution in the engineer HMI runs through the exact same refusal engine that gates automatic production. Putting the cell into `MANUAL` mode relaxes step sequencing so an engineer can trigger operations out of order, but it never relaxes preconditions or physical boundaries.

For example, if an engineer attempts to manually trigger a `drive_screw` operation from the module faceplate without a fastener loaded in the feed track, the command does not execute:

```
REFUSAL: OpExecutionBlocked
Module: spindle_01
Operation: drive_screw
Failed Precondition: part_present == true (actual: false)
Action: Command dropped. Preconditions are enforced in MANUAL mode.
```

Similarly, if an engineer attempts to input a seating torque outside the declared min-max bounds in the capability schema, the interface refuses the submission locally before sending the command over the network. The system refuses invalid actions rather than guessing or allowing an unverified drive cycle.

Hardwired safety circuits remain entirely outside of this software stack. Safety interlocks operate on dedicated hardware and are never bypassed by any software mode.

### Architectural Separation

Neither the operator HMI nor the engineer HMI connects directly to fieldbus networks or hardware registers. Both screens are clients of `ocm-api` and receive real-time state updates over a WebSocket stream from the cell coordinator's SignalBus.

Plant-specific layouts or company branding are applied as declarative configuration overlays on top of the generation pipeline. We never modify generated UI files by hand. If a project requires a modified station layout, that layout is declared in configuration; any manual edits to generated artifacts would be overwritten during the next build.

By generating screens directly from verified manifests, custom modules achieve complete plug-and-produce functionality right to the touchscreen panel. The user interface reflects the actual state of the machine code on every cycle.
