Make HMI Commands Prove They Worked, Not Just That You Clicked
How to drive HMI state from feedback instead of command bits, set timeouts from real actuator travel, and show operators why a command was rejected.
The motor symbol is green and the shaft is dead still
The pump symbol on the overview screen is green. The operator swears it started. Walk down to the MCC and the starter contactor is open — no current, shaft not turning. The HMI turned green off the start command bit the instant the button was pressed. It was showing the click, not the machine.
This is the single most common command-confirmation defect I find on commissioning, and it comes from one shortcut: coloring the symbol from what the operator asked for instead of what the equipment proved. A click is a request. Nothing is complete until feedback comes back from the PLC, drive, actuator, or vendor controller.
Draw the full path for every operator command and be honest about which link you're actually displaying:
- Operator presses the HMI control.
- HMI writes a command bit, command word, or setpoint.
- PLC accepts or rejects based on mode, permissives, and interlocks.
- Field device moves or changes state.
- Feedback proves the commanded result.
- HMI shows success, timeout, reject reason, or uncertain.
If your symbol changes at step 2, you have a display that lies whenever steps 3–5 fail. ISA-101 (ANSI/ISA-101.01) makes this point in its state-indication guidance: the graphic should represent actual process state, and command state and feedback state are not the same thing.
Drive the symbol from feedback
Running should come from running feedback. Open should come from an open limit switch or actuator position — not from the fact that someone clicked Open two seconds ago. Recipe-load-complete should come from the controller's sequence state, not a screen timer.
For anything important, show command state and feedback state separately. That one habit is what lets a night-shift operator tell the difference between "the PLC never got my command" and "the PLC got it but the valve didn't move" — two problems with completely different fixes.
The tag groups I set up per device class:
| Device | Command tags | Feedback tags | Diagnostic tags |
|---|---|---|---|
| Motor | Start, stop, reset | Running, stopped, faulted | Local mode, overload, permissive missing |
| Valve | Open, close | Opened, closed, traveling | Travel timeout, air pressure low, manual override |
| Drive | Run, stop, speed setpoint | Running, actual speed, fault | Not ready, interlock, speed reference source |
| Package skid | Start seq, stop seq | Sequence state, complete, aborted | Step number, reject code, active hold |
| Robot / tool | Remote command, recipe select | Busy, ready, selected recipe | Host mode, alarm code, command reject |
Note the valve row has both Opened and Closed feedback. A valve that reads "not open" is not the same as a valve that reads "closed." If you only wire the open limit and infer closed from its absence, a traveling valve, a broken limit switch, and a genuinely closed valve all paint the same picture.
Momentary vs. maintained — pick one and let the PLC own the edge
A momentary command is a pulse; a maintained command stays true until something changes it. Mixing the two inside one faceplate produces the faults nobody can reproduce.
My rule: the PLC owns one-shot generation. Use an R_TRIG (IEC 61131-3) on the command bit inside the controller rather than trusting the HMI to paint a clean pulse — HMI scan and network update rates (often 250 ms–1 s) will eat a short pulse and the operator will never know their press was dropped. For slow equipment, use an explicit request/acknowledge handshake and clear the request when the PLC accepts it, not after an arbitrary screen delay.
Here's the classic maintained-command trap. A start bit stays true after the motor is already running. Later a fault trips, the operator resets, permissives return — and the motor restarts on its own because the old command was still latched. Nobody touched a button. Decide in the PLC whether a latched command may re-fire after a trip, document that decision, and never let the HMI hide it.
Set timeouts from actual travel, not the template default
Timeout belongs to the device, not to the faceplate library. A small solenoid proves position in well under a second. A 24-inch pneumatic damper might need 20–40 seconds of stroke. A pump needs time to build discharge pressure after the motor feedback already went true, so a "running" timeout and a "pressure reached" timeout are two different clocks.
Do not paste the same 10-second timeout on every object because that's what the template shipped with. Parameterize it per device class. For each command, pin down: the expected feedback condition, the normal response time, the maximum allowed time, what message fires on timeout, whether the operator may retry, and whether the PLC must drive a safe state when the clock expires. A travel timeout is usually worth raising as an alarm — treat it under your ISA-18.2 rationalization like any other, with priority and operator response text, not as a silent status bit.
"Command failed" is not a diagnosis
A command can fail four different ways: the HMI write never landed, the PLC refused it, the device didn't move, or the feedback is bad quality. Collapsing all four into "command failed" wastes everyone's time. Show the actual reason:
- Not in remote/automatic mode
- Required permissive missing
- Active interlock or trip present
- Already in the requested state
- Command source is local panel / MCC / vendor controller / MES
- Communication quality bad
- Safety system not reset
- Another sequence owns the equipment
"Valve not remote" gets an operator moving in seconds; "open failed" gets you a phone call. Surface the PLC's active reject bit right on the faceplate — the person troubleshooting at 2 a.m. shouldn't have to go online with the PLC to find out why.
Setpoint writes are not display changes
A setpoint write is a control action, and it deserves the same rigor as a start command. Before the write commits:
- HMI scale and PLC scale match (get this wrong and 50% becomes 5000)
- Units are visible on entry and on the display
- Min, max, and decimal precision are enforced client-side before the write
- The value read back from the controller matches what you sent
- The controller can reject the value and say why
- Where required, the audit log captures old value, new value, operator, timestamp, and station
Read-back matters more than people think: a PLC that clamps 150 down to its 100 limit and says nothing leaves the operator believing the loop is at 150. For recipe or batch values, attach a command ID or transaction number so a genuine new command can be told apart from a screen refresh or a late acknowledgement.
When the quality is bad, say so — don't guess
If feedback quality is bad, the HMI cannot honestly confirm anything. If command-write quality is bad, you don't even know the request reached the controller. Both cases need a defined behavior, and OPC UA already gives you the vocabulary — a StatusCode is Good, Uncertain, or Bad, and stale feedback should paint as Uncertain, not as a comfortable green "stopped."
What I settle on for most equipment:
- Bad control-path quality disables start, open, and mode commands.
- Stop or safe commands stay allowed if the write path is independently known good — locking every button on a comms hiccup can be the less safe choice.
- Stale feedback shows uncertain, never a valid stopped/closed state.
- A timeout does not silently clear just because feedback went bad mid-stroke.
- Comms failures get logged separately from device-movement failures.
The point isn't a universal rule; it's an explicit one that you actually tested.
Commissioning: break it on purpose
Test with real feedback whenever you can. Simulation is fine for logic checks but it hides wiring faults and actuator behavior that only show up on live steel.
- Press once, confirm the PLC sees exactly one request — not a burst of writes.
- Confirm the symbol changes only after real feedback changes.
- Pull a permissive and check the reject reason text.
- Force feedback to bad quality and watch the displayed state.
- Block the actuator (or simulate no movement) and confirm timeout behavior end to end.
- Switch to local mode and verify remote commands are blocked or clearly rejected.
- Restart the HMI client and the alarm server while a command is pending.
- Read the audit and event records afterward.
For sequences, test abort, pause, reset, and retry. Almost every command defect lives outside the happy path — which is exactly the path most factory acceptance tests spend their time on.
A good command faceplate answers three questions without the operator asking: what was requested, what the equipment actually proved, and — if it didn't follow — why. Keep the command contract, the per-class timeout values, the feedback definitions, and the reject-code list next to your HMI standard. That's the document that gets a stuck valve moving before anyone has to call you.