← Articles
OPC UA/8 min read/ views

OPC UA Method Calls From the HMI: Why 'Good' Doesn't Mean the Command Ran

An OPC UA method call can return Good while the machine never moves. Wiring HMI calls so operators see acceptance, execution and a real failure reason.

OPC UAHMISCADATagsTroubleshooting

An operator presses Load Recipe, the message line blinks OK, and the machine sits there. Pull the trace and the Call service returned Good. The recipe never reached the controller. Nobody is lying — the HMI told the truth about what it knew, which was only that the server accepted the request. That gap between "accepted" and "ran" is the thing you have to design around, and it's the first place teams get burned when they wire a button to an OPC UA method instead of a plain tag write.

Methods are the right tool for actions that don't fit a single writable tag: starting a recipe download, acknowledging a machine message, kicking off a calibration step, clearing a fault queue, releasing a carrier. The Call service (OPC UA Part 4, §5.11) hands the server a set of input arguments, runs application code, and returns output arguments plus a status. That's genuinely different from writing StartRequest = 1 and watching a feedback bit.

But the method itself buys you nothing if the HMI treats it as a magic button. It's a command transaction — input, validation, a return status, and behavior behind it. Use one when the action actually needs that structure:

  • Several input values have to be submitted together, atomically.
  • The server should validate the request before it accepts anything.
  • You need a reason code, a message, or an assigned job id back.
  • It's a one-shot action, not a persistent setpoint.

If none of those apply, a tag write and a feedback bit is less to commission and less to get wrong. Don't reach for a method just because it looks tidier in the address space.

Model the call like a small API, because that's what it is

The reliable interface is explicit about what the caller provides and what the server promises to return. Write it down — argument names, types, and order — because a server update that reorders arguments will silently break every screen that calls it.

Input arguments worth carrying:

ArgumentExampleField note
Equipment areaLine3.Filler.InfeedPass the target explicitly. A method that acts on hidden server-side "current selection" state is a landmine.
Command nameLoadRecipe, ClearFaultQueueControlled list, never free text from a screen field.
Recipe or job idRCP-2041-AValidate existence and approved version before executing.
Operator / session idop_1452For the audit record and for figuring out who did what at 2am.
Client request idUUID or sequence numberThe hook that makes retries idempotent — see below.
Expected stateStopped, Manual, NoLotLoadedLets the server reject a command issued from a stale screen.

On the way back, don't return a bare boolean. Return an accept/reject result, an application reason code, a short diagnostic string, the server's command or job id, and a coarse execution state (Queued, Running, Completed, Failed). Keep the diagnostic short enough to fit the HMI message line; the full server log lives elsewhere.

Good is a transport verdict, not a process one

Here's the trap from the opening, stated plainly: a Good status on the Call service means the server received a well-formed request and returned valid output arguments. It says nothing about whether the equipment did the thing.

A recipe-download method often returns in a few hundred milliseconds — it accepted the request. The download can still fail seconds later because the controller rejects a parameter, the machine changed mode, or the target station dropped off the network. So build two layers of feedback and keep them separate:

  1. Call result — did the server accept the request and hand back valid outputs? This is the StatusCode plus your application reason code.
  2. Execution result — did the equipment finish the commanded action inside the expected window?

The execution result rides on normal tags or events, not on the method return:

  • CommandActive, CommandDone, CommandFailed
  • LastCommandId, LastCommandResultCode
  • equipment state, mode, and permissive status

The second layer is the one operators actually watch. A green service status with no machine motion is a failed command from the control room, no matter what the return code says.

Permissions belong at the server, not just the button

A method can trigger a cascade of internal writes or start a whole sequence, so it needs at least as much permission thinking as a tag write. OPC UA gives you the hooks directly: every Method node carries Executable and UserExecutable attributes (Part 3, §5.7), and a call that isn't allowed comes back as Bad_NotExecutable or Bad_UserAccessDenied — not Good. Make sure the HMI reads those and shows them, instead of collapsing everything into "method failed."

At commissioning, walk both layers:

  • OPC UA identity — anonymous, username/password, certificate, or token.
  • Server role mapping — which roles may call this method.
  • Node attributes — is the method visible but UserExecutable = false for the runtime user?
  • HMI role — operator, maintenance, engineer, admin.
  • Equipment mode — auto, manual, maintenance, local, remote.
  • Process permissives — guards closed, no active interlock, no batch running, safe speed.

Hiding the button is not access control. If a test client, a script, or a forgotten engineering workstation can reach the same node, the server has to be the one that says no.

Assume the network will lie about whether the call arrived

The nastiest case isn't a rejection — it's a timeout. The HMI fires the Call, the response never comes back, and now nobody knows whether the server got it. The operator presses the button again, and material transfers twice.

This is why the client request id earns its place. For any command with side effects, define the server's duplicate rule up front:

  • Same request id, same arguments → return the previous result or the current command state. Idempotent, safe to retry.
  • Same request id, different arguments → reject as a client error. Something is confused; don't guess.
  • New request id while a conflicting command runs → reject as busy / invalid state.

Material transfer, label print, carrier release, recipe download, lot start — for these, a duplicate isn't a doubled screen message, it's a doubled production record. That's the difference between a UI annoyance and a traceability problem you explain to a customer.

One timeout for everything is wrong

A validation query and a sequence-start don't wait the same amount of time, so don't give them the same timeout:

Call typeHMI service timeoutWhy
Read-like validation1–3 sShould return fast or fail visibly.
Command acceptance3–10 sRoom for server-side validation, not full machine motion.
Recipe download request10–30 sScales with recipe size and controller interface.
Equipment executionseparate watchdogTracked through status tags, never the Call timeout.

Set the service timeout too long and the HMI looks frozen. Too short and operators see false failures while the command quietly runs on in the background — which then invites the double-fire from the previous section. Tie the timeout to the acceptance, and let a status-tag watchdog own the execution.

The failures you'll actually see at FAT and SAT

Almost none of these are OPC UA transport problems. They're broken interface contracts:

  • Method returns Good, HMI ignores the output argument carrying an application rejection.
  • HMI calls the method on the wrong object instance after a browse path changed.
  • Argument order shifted after a server update; the screen script didn't.
  • Numeric argument in the wrong engineering units, or an enum value off by one.
  • Works for the engineering login, Bad_UserAccessDenied for the operator runtime login.
  • HMI times out, retries, double-books the command.
  • Server accepts the command while the equipment is in local mode.
  • Operator sees "method failed" instead of "recipe not approved" or "station not empty."

Before it goes into production, test the command path like a control function, not a screen: confirm the exact node, object context, argument names and types; force every rejection (wrong mode, missing permissive, bad recipe, unauthorized user, busy equipment); pull the network cable mid-call and watch the HMI; replay the same request id and confirm the duplicate rule holds; verify the audit record captures user, time, client, arguments, and result; then repeat the whole thing after a server restart, an HMI restart, and a certificate renewal.

If you get one thing from this, get the two-layer feedback right. Everything else is cleanup; a command that reports acceptance and execution as separate facts is one an operator can trust.