Why Is This Button Greyed Out? Making HMI Disable Reasons Answer That
How to split HMI button logic into authorization, mode, permissives, interlocks and quality — and show operators the actual reason a command is blocked.
Night shift calls the on-call engineer because the START button on a transfer pump is grey. Twenty minutes later it turns out the downstream tank was above its high-level permissive. The information was in the PLC the whole time. It just never made it onto the screen the operator was looking at.
That call is the failure. Not the interlock — the interlock did its job. The screen did not.
One enable bit hides five different questions
Most faceplates end up with a single expression called something like ButtonEnabled, and it collapses five independent decisions into one boolean:
- Authorization — is this logged-in user allowed to do this at all?
- Mode — is the equipment in remote, auto, manual, maintenance, or local?
- Permissives — do process conditions currently allow the action?
- Interlocks — is a protective condition actively blocking it?
- Quality — can the HMI actually read and write the tags involved?
Keep the final bit if your template needs one. But carry the five inputs alongside it, because the operator's question is never "is it enabled" — it's "which of these five is stopping me."
The cheapest version of this costs one extra integer per command: a reason code, evaluated in the PLC, that says which condition failed first. Everything else in this article is downstream of that decision.
Visible, enabled, confirm, and reject are four different mechanisms
The common mistake is treating "hidden" as "secure." Hiding a command button removes clutter. It does not stop anything — a scripting client, an OPC UA session, or an engineering workstation writes the same tag with the button never rendered. IEC 62443-3-3 SR 2.1 puts authorization enforcement on the system, not on the drawing.
| Decision | Driven by | What breaks if you misuse it |
|---|---|---|
| Visible | Equipment option installed, object type, screen role | Operator never learns the function exists |
| Enabled | Process state, permissives, tag quality | Operator presses a command that gets rejected |
| Confirmation | High-impact action, restart after trip, abnormal state | Routine work slows down, or dangerous work gets too easy |
| PLC reject | Mode, interlock, command source — final authority | HMI misconfiguration bypasses protection |
A drain valve is a decent worked example: visible to every operator, enabled only in manual mode with downstream capacity available, confirmation required if the line still contains product, and rejected outright by the PLC if command source is local. Four layers, four different reasons, and only the last one is safety.
Write the disable reason in plant language
An operator should never open a watch table to learn why a button is grey. Put the first active blocking reason in a status line under the control or in the faceplate header.
Disabled: pump is in local modeDisabled: discharge valve not openDisabled: trip must be reset at MCC-3Disabled: your role cannot change recipeDisabled: PLC communication bad
Compare those against what usually ships: not available, invalid state, permission denied. Those three strings are the reason the 2 a.m. call happens. If the logic knows which condition failed — and it does, that's how it computed the enable bit — then throwing that knowledge away before it reaches the screen is a choice.
When several conditions block at once, lead with the one the operator can act on. "Discharge valve not open" beats "communication degraded on a secondary tag" even if both are true, because one of them has a next step and the other has a phone call.
Roles drift; actions don't
One site runs Operator, Lead, Maintenance, Engineer. The next runs shift supervisor, line owner, process engineer, OEM technician. Role names are local vocabulary and they will not survive the next reorganization. Document authorization per action and map roles onto it:
| Action | Typical roles | Extra condition |
|---|---|---|
| Start / stop equipment | Operator, lead | Remote mode, permissives healthy |
| Reset alarm | Operator, lead | Alarm is HMI-resettable |
| Force manual override | Maintenance, engineer | Maintenance mode, reason recorded |
| Change recipe parameter | Lead, engineer | Recipe unlocked, value in range |
| Bypass interlock for test | Engineer only | Time-limited and logged — if permitted at all |
That last row deserves a hard opinion: an interlock bypass with no expiry is a permanent bypass. Someone sets it during commissioning, and it is still set three years later because nothing on the screen ever mentions it again. Give it a timer and an active-bypass indicator on the overview screen, or don't build it.
Commissioning cases worth actually running
Simulated tags will pass a test that real equipment fails. Force the states:
- Right role, right mode, permissives healthy — command enables and the equipment moves.
- Right role, wrong mode — disabled, and the mode reason shows.
- Wrong role — hidden or disabled per your screen standard, and a forced write from a client tool still gets rejected.
- Pull the network cable — the button must not sit there enabled on cached values.
- Interlock active with a deliberately wrong HMI expression — the PLC still blocks it.
- Change role while the faceplate is open — enable state updates without a logout.
Case 4 is the one that gets skipped and the one that bites. A permissive read from cache with stale quality looks identical to a healthy true. Treat bad quality on any required tag as a disable condition unless the command was explicitly designed for degraded operation — and if it was, say so on the screen.
Case 6 matters more on web HMIs than on old thick clients. Open the same faceplate in two browser sessions and change the role in one. A logout in session A that leaves session B holding engineer rights is not a display bug.
Where these designs usually rot
HMI and PLC evaluate different permissive logic. The screen says go, the controller says no. Structurally that's correct — the PLC should be authoritative — but if it happens weekly, operators stop believing the screen and start mashing buttons to see what sticks. Surface the PLC's reject code so the two agree on the story.
Role escalation with no audit trail. A supervisor logs in, enables something, and walks away. The next operator inherits it. Session lock and remote session termination are separate requirements in IEC 62443-3-3 (SR 2.5 and SR 2.6) for exactly this reason. Inactivity timeout on the operator station, plus a record of who held which role when a command fired.
Reason codes that get renumbered. The codes end up in logs, in support calls, in someone's spreadsheet. Keep them stable across revisions and translate them at display time, not at the source.
The fields worth exposing on the faceplate
Structured, named, per-command:
CanStartCannotStartReasonCodeCommandSourceAllowedOperatorRoleLevelPermissiveSummaryOkInterlockActiveWriteQualityGood
If the command path runs over OPC UA, don't discard the service result either — a Bad_UserAccessDenied coming back from a write tells you the server refused, which is a very different problem from a permissive that never came true. Log both.
Test the whole thing with one question: an operator points at a grey button and asks why. If answering requires an engineer, the design isn't finished.