Skip to content

RPC

Invoke a method on a PLC function block or program instance over ADS. Requires feature flag Rpc enabled — disabled by default, and answers 501 rather than 404 when off (see Error Codes).

Two independent switches must both be on, and neither implies the other:

  1. Features:Rpc:Enabled — the feature slice as a whole.
  2. A per-PLC allowlist under PlcTargets:{plcId}:Rpc:Invokable — see Configuration below.

Enabling the feature flag with no allowlist entries leaves every method call denied; there is no PLC on which anything is invokable until it is named.

Invoke a Method

POST /api/plcs/{plcId}/methods/{methodPath}

Policy: WriteAccess

methodPath is the full dotted path including the method name, e.g. MAIN.fbErrorHandler.AcknowledgeAlarm — not just the instance path.

curl -X POST http://localhost:5000/api/plcs/Line1/methods/MAIN.fbErrorHandler.AcknowledgeAlarm \
  -H "Content-Type: application/json" \
  -d '{"parameters": ["Line1_Err_60"]}'
{
  "data": { "returnValue": true, "outParameters": [] },
  "error": null
}

parameters are positional, in the method’s declaration order. returnValue and each entry of outParameters must be scalar — see Scalar-only results. Each element of parameters must be scalar too — see Scalar-only parameters below.

Status Codes

StatusCodeMeaning
200Invoked; response carries returnValue / outParameters
400TYPE_MISMATCHA parameters entry is a JSON object or array — see Scalar-only parameters
403RPC_INVOKE_DENIEDMethod not on the allowlist for this PLC (or the PLC has no allowlist configured at all)
404SYMBOL_NOT_FOUNDmethodPath has no instance segment (e.g. just AcknowledgeAlarm, no dot) — a malformed path is treated the same as every other symbol-path endpoint, not 400
501RPC_RESULT_NOT_SERIALIZABLEThe method returned a struct or array — see below

The default-deny allowlist

Nothing is invokable until it is named. An unconfigured PLC and a PLC with an empty Invokable list both deny every call — this is the inverse of SymbolAccess, where writing a variable defaults to permitted. Invoking a function block is unbounded in effect compared to writing one value, so the default is inverted deliberately.

{
  "PlcTargets": {
    "Line1": {
      "Rpc": {
        "Invokable": [ "MAIN.fbErrorHandler.AcknowledgeAlarm" ]
      }
    }
  }
}

Invokable supports the same * and ** segment wildcards as SymbolAccess’s Writable / Denied lists — MAIN.fbErrorHandler.* matches any single method on that instance, MAIN.fbErrorHandler.** matches any depth beneath it.

A bare "**" entry grants blanket invocation of every method on that PLC. This is the single highest-leverage misconfiguration the allowlist allows: one entry turns a default-deny gate into “invoke anything.” Treat it the way you would a wildcard sudo rule — not as a convenient shortcut while setting up an allowlist, even temporarily.
Matching is case-insensitive, for both the plcId and every path segment. An allowlist entry of MAIN.fbErrorHandler.AcknowledgeAlarm also matches a request for main.fberrorhandler.acknowledgealarm. Don’t rely on case to distinguish two different methods, or to informally narrow an entry’s scope — it won’t.

TcRpcEnable

The PLC method must carry {attribute 'TcRpcEnable'} in its declaration. Without it, TwinCAT does not expose the method over ADS at all, and the call fails as an unknown method regardless of what the allowlist or the request path say — this is a PLC-program-side requirement, not something adsify’s configuration can work around.

Scalar-only results

Only scalar return values and output parameters are supported — booleans, numbers, strings, DateTime, TimeSpan, and similar primitives. A struct or array return refuses with 501 RPC_RESULT_NOT_SERIALIZABLE rather than attempting a best-effort translation: a struct or array arrives from the underlying ADS library as a dynamic value object that this endpoint does not have the type metadata to decode faithfully, and handing it to the JSON serializer as-is would emit whatever public properties the wrapper object happens to expose — output that looks plausible and is not the PLC’s actual value. Read the symbol directly instead (see Variables), which does have that decoding.

Scalar-only parameters

The mirror image applies inbound: every element of parameters must be a JSON string, number, boolean, or null. A JSON object or array as a parameter refuses with 400 TYPE_MISMATCH, naming the offending index, rather than being forwarded — the underlying ADS layer cannot marshal one either, so sending it would either throw a 500 deep in the connection or, worse, be silently coerced into something that isn’t what was sent.

curl -X POST http://localhost:5000/api/plcs/Line1/methods/MAIN.fbErrorHandler.AcknowledgeAlarm \
  -H "Content-Type: application/json" \
  -d '{"parameters": [{"nested": "object"}]}'
{
  "data": null,
  "error": { "code": "TYPE_MISMATCH", "message": "Parameter 0 of method '...' is a JSON object or array. PLC method arguments must be scalar — a string, number, boolean, or null." }
}
Numeric widening is a best-effort guess, not an authoritative mapping. This endpoint deliberately does not fetch the target method’s type metadata, so it has no way to know whether a PLC parameter is declared INT, DINT, or LREAL. A JSON number with no fraction/exponent converts to a 64-bit integer; anything else converts to a 64-bit float. Getting the PLC’s actual argument type right from there relies entirely on the underlying Beckhoff library’s own marshalling — this conversion cannot verify it, because it isn’t told what the method expects.

Rate Limiting

RateLimiting:RpcInvocations — default 10 requests/second, per user per PLC.

Configuration

See The default-deny allowlist above for PlcTargets:{plcId}:Rpc:Invokable, and enable the slice with:

{ "Features": { "Rpc": { "Enabled": true } } }
RPC is not enabled in the Simulation profile. A simulated ADS connection has no handler for a method until one is seeded programmatically via SetRpcHandler — there is no appsettings.*.json equivalent, unlike InitialValues for variables. An unseeded call throws rather than returning something plausible, by the same design that makes a simulated alarm acknowledgement throw (see Alarms).