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:
Features:Rpc:Enabled— the feature slice as a whole.- 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
| Status | Code | Meaning |
|---|---|---|
200 | — | Invoked; response carries returnValue / outParameters |
400 | TYPE_MISMATCH | A parameters entry is a JSON object or array — see Scalar-only parameters |
403 | RPC_INVOKE_DENIED | Method not on the allowlist for this PLC (or the PLC has no allowlist configured at all) |
404 | SYMBOL_NOT_FOUND | methodPath 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 |
501 | RPC_RESULT_NOT_SERIALIZABLE | The 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.
"**" 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.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." }
}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 } } }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).