WriteCommand now accepts multiple positional values when the tag reference ends with '[]', bundling them into a strongly-typed array (string[], int[], bool[], etc.) before passing to MxAccess.Write. The CLR marshals the array to a COM SAFEARRAY of the matching VARTYPE, which is the shape MxAccess expects for an array attribute. Verified live on a 50-slot String[] (MESReceiver_001.MoveInPartNumbers): write 50 distinct strings A1..A50 -> ok, MxCategoryOk read [] -> ['A1','A2', ..., 'A50'] Plus a guardrail: passing multiple values without the '[]' suffix exits 2 with a clear error so a typo can't accidentally write only the first element of an indexed reference. Critical finding documented in docs/usage.md: **a bulk write resizes the array to the count provided.** Writing 25 values into a 50-slot array leaves the array at 25 elements; the trailing 25 are deallocated, not zero-filled. Verified by 50 -> 25 -> 50 round-trip on the same attribute. Discover the runtime length via 'mxa read <attr>[]' or the configured length via grdb's attributes.sql array_dimension column. Type matrix in docs/usage.md updated: - Bulk array via '[]' - read ✅ + write ✅ - Bare reference (no brackets) - read ❌ + write ❌ - Element via '[N]' - unchanged ValueCoercion.cs: adds CoerceArray(IReadOnlyList<string>, typeHint) that produces strongly-typed arrays. Default element type is inferred from the first value when --type is unspecified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
mxa — usage
Read, write, and subscribe to AVEVA System Platform tags via MxAccess. The CLI runs in-process: each invocation registers an LMXProxyServer, executes, and unregisters cleanly. Errors carry the underlying MxStatusCategory so an agent can decide whether the failure is transient (Pending), configurational, or operational.
Common notes
- Tag references are full attribute paths:
<ObjectName>.<AttributeName>(e.g.TestMachine_001.Speed). ForGalaxy:references, follow the convention used in InTouch / Object Viewer. --client <name>sets the client name passed to MxAccessRegister(). Defaults tomxa. Most install logs key on this string.- Timeouts are per-call. They control how long the CLI waits for a
OnDataChange(read) orOnWriteComplete(write). The default is 5 seconds. - First-event latency. LMX has to resolve the reference and bind to the hosting engine on each fresh client connection. Empirically the first
OnDataChangearrives 3-8 seconds afterAdvise(). Set timeouts andsubscribe --secondsaccordingly: a 3-secondreadmay legitimately time out on first contact, then succeed on the next try because LMX has cached the binding. - Subsequent events are fast. Once a tag is bound, value-change updates propagate within ~100 ms.
- Exit codes:
0on success,1if any operation timed out or returned a non-Ok / non-PendingMxStatusCategory,2on argument-validation errors.
mxa info
Print the loaded ArchestrA.MxAccess assembly identity, supported --type values, and the full MxStatusCategory enum. No tag access.
mxa info
mxa read <tag> [<tag>...]
Reads one or more tags by briefly subscribing and capturing the first OnDataChange per tag.
| Option | Default | Notes |
|---|---|---|
-t, --timeout <seconds> |
5 |
Per-tag timeout. Tags that don't deliver a DataChange within the window are reported with error: timeout. |
--client <name> |
mxa |
Passed to Register(). |
--llm-json |
off | Emit the JSON envelope. |
Examples:
mxa read TestMachine_001.Speed
mxa read TestMachine_001.Speed Reactor1.Level -t 3
mxa read TestMachine_001.Speed Reactor1.Level --llm-json
LLM-JSON envelope:
{
"query": { "command": "read", "tags": ["TestMachine_001.Speed"], "timeout_s": 5.0, "client": "mxa" },
"ok": true,
"results": [
{
"tag": "TestMachine_001.Speed",
"ok": true,
"value": 1234.5,
"quality": 192,
"timestamp": "2026-05-03T19:42:18.001",
"statuses": [
{ "Success": 0, "Category": "MxCategoryOk", "DetectedBy": "MxSourceRespondingAutomationObject", "Detail": 0 }
]
}
]
}
mxa write <tag> <value>
Writes one value to one tag and waits for OnWriteComplete.
| Option | Default | Notes |
|---|---|---|
--type <kind> |
inferred | Force the .NET type used for the boxed value. One of bool, byte, short, int, long, float, double, string, datetime. |
-t, --timeout <seconds> |
5 |
How long to wait for OnWriteComplete. |
--user-id <int> |
0 |
Authenticated user id. 0 is unauthenticated; secured attributes will reject. |
--client <name> |
mxa |
Passed to Register(). |
--llm-json |
off | Emit the JSON envelope. |
Type inference rules (when --type is not set): true/false/yes/no/on/off/1/0 → bool; pure integer → int (then long); decimals → double; everything else → string.
Examples:
mxa write TestMachine_001.Setpoint 42.5 --type double
mxa write TestMachine_001.RunFlag true
mxa write TestMachine_001.Label "Hello world"
mxa write Reactor1.Setpoint 100 --type int -t 10 --llm-json
The same JSON envelope shape as read, with results[0] containing { tag, ok, error?, statuses }. No value/quality/timestamp on the write result — consult a follow-up mxa read to confirm.
mxa subscribe <tag> [<tag>...]
Streams OnDataChange events for a duration.
| Option | Default | Notes |
|---|---|---|
-s, --seconds <seconds> |
10 |
Wall-clock duration of the subscription. |
--max <int> |
1000 |
Hard cap on emitted events. |
--client <name> |
mxa |
Passed to Register(). |
--llm-json |
off | JSON Lines mode — one JSON object per line, no outer envelope. |
Human output:
[INFO] Subscribed to 1 tag(s). Streaming for 30.0s. Ctrl-C to stop early.
[19:42:18.001] [OK ] TestMachine_001.Speed = 1234.5 (q=192)
[19:42:19.002] [OK ] TestMachine_001.Speed = 1245.7 (q=192)
...
[INFO] 30 event(s) emitted; subscription closed.
LLM-JSON output (one event per line, no surrounding [ ... ]):
{"tag":"TestMachine_001.Speed","ok":true,"value":1234.5,"quality":192,"timestamp":"2026-05-03T19:42:18.001","statuses":[{...}]}
{"tag":"TestMachine_001.Speed","ok":true,"value":1245.7,"quality":192,"timestamp":"2026-05-03T19:42:19.002","statuses":[{...}]}
JSON Lines lets a downstream consumer parse events incrementally rather than buffering the whole stream — the right shape for indefinite or long-running subscriptions.
Type support matrix
Verified end-to-end against the live ZB galaxy (System Platform 2017 Express, MxAccess 3.2.0.0). Each row records what the wire shape looks like in the JSON envelope.
MxDataType |
Read | Write | JSON shape | Notes |
|---|---|---|---|---|
MxBoolean |
✅ | ✅ | JSON true / false |
--type bool accepts true/false/1/0/yes/no/on/off. |
MxInteger (Int32) |
✅ | ✅ | JSON number | --type int. Up-cast to long if it overflows int.MaxValue. |
MxFloat (single) |
✅ | ⚠️ | JSON number | Read verified on DevPlatform.CPULoad family. Write requires a writeable Float UDA — none in the test galaxy, but --type float is wired. |
MxDouble |
❓ | ❓ | JSON number | No accessible Double instance in the test galaxy. Wiring is identical to Float; expected to work. |
MxString |
✅ | ✅ | JSON string | Default inferred type for non-numeric values. |
MxTime (DateTime) |
✅ | ⚠️ | JSON string "YYYY-MM-DDTHH:mm:ss" |
Read verified on DevPlatform.SystemStartupTime. Write via --type datetime accepts ISO-8601. |
MxElapsedTime |
❓ | ❓ | JSON number (seconds) | No accessible instance in the test galaxy. |
MxReferenceType |
✅ | – | JSON string (target object's Tagname) |
E.g. TestChildObject.Container → "DevTestObject". Writing references is not exposed by the CLI. |
MxQualifiedEnum (13) |
❓ | – | (likely string) | No accessible instance. |
MxQualifiedStruct (14) |
– | – | – | Access via dotted member names: <obj>.<struct>.<field>. |
MxInternationalizedString (15) |
❓ | ❓ | (likely string) | No accessible instance. |
MxBigString (16) |
❓ | ❓ | JSON string | No accessible instance. |
Array (any type), bulk read/write via [] |
✅ | ✅ | JSON array of element type | Reference syntax <obj>.<arrayAttr>[] — empty square brackets. Read returns the entire array as a single value. Write takes one positional value per element (mxa write '<obj>.<arr>[]' v1 v2 v3 ...). A bulk write resizes the array to the count provided (verified: 50 → 25 → 50 round-trip on MoveInPartNumbers). |
| Array (bare reference) | ❌ | ❌ | — | The plain <obj>.<arrayAttr> (no brackets) returns MxCategoryCommunicationError, Detail=1003. Always use [] for bulk operations. |
| Array element by index | ✅ | ✅ | scalar of element type | Reference syntax <obj>.<arrayAttr>[<n>]. 1-based, runs from [1] to [NumElements]. [0] is invalid. |
Legend: ✅ verified live, ⚠️ wiring present but no live instance to write, ❓ wiring present but no live instance found, ❌ not supported by MxAccess at this layer, – not applicable.
To test write support for a type, use ../../grdb/ to find a deployed instance whose dynamic_attribute.mx_data_type matches and whose mx_attribute_category is in (2-11, 24) (Writeable_* family).
Errors and statuses
Every result carries a statuses array — the elements of the COM MXSTATUS_PROXY[] MxAccess passes back. Field names match the C# struct exactly:
| Field | Type | Meaning |
|---|---|---|
Success |
int16 | 0 = Ok, non-zero = error code |
Category |
enum | MxCategoryOk, MxCategoryPending, MxCategoryWarning, MxCategoryCommunicationError, MxCategoryConfigurationError, MxCategoryOperationalError, MxCategorySecurityError, MxCategorySoftwareError, MxCategoryOtherError, MxStatusCategoryUnknown |
DetectedBy |
enum | MxSourceRequestingLmx, MxSourceRespondingLmx, MxSourceRequestingNmx, MxSourceRespondingNmx, MxSourceRequestingAutomationObject, MxSourceRespondingAutomationObject, MxSourceUnknown |
Detail |
int16 | Additional error-code detail |
A result is considered ok only if every statuses element has Category in (MxCategoryOk, MxCategoryPending).
Common failure shapes:
Category: MxCategoryConfigurationError— usually a typo'd reference or the attribute doesn't exist on the deployed instance. Sanity-check viagraccesscli object snapshot.Category: MxCategoryCommunicationError— engine isn't running, object is OffScan, or LMX can't reach the platform hosting the object.Category: MxCategorySecurityError— secured attribute,--user-id 0. UseWriteSecuredsemantics (not yet exposed by this CLI) or target aWriteable_USC_*attribute.- Timeout — most likely the tag is genuinely silent (no value updates) or the reference is wrong. With
--llm-jsonyou'll see"error": "timeout"and an emptystatuses.
Reading arrays
MxAccess accepts two reference forms for arrays — pick by what you need:
Whole array — <obj>.<arrayAttr>[] (empty brackets)
mxa read 'MESReceiver_001.MoveInPartNumbers[]' --llm-json
Returns the full array as a single JSON value:
{
"tag": "MESReceiver_001.MoveInPartNumbers[]",
"ok": true,
"value": ["", "11111", "", "", /* ... 50 elements total ... */],
"quality": 192,
"statuses": [{"Success":-1,"Category":"MxCategoryOk", ...}]
}
The array is fixed-length (sized at deploy time per the template's array_dimension). Empty string elements are unset slots, not gaps.
Single element — <obj>.<arrayAttr>[N]
mxa read 'MESReceiver_001.MoveInPartNumbers[2]' --llm-json
Indices are 1-based: [1] is the first element, [NumElements] is the last. [0] is invalid. Single-element reads are also writeable: mxa write '<obj>.<attr>[N]' <value>.
Whole array write — also via []
Pass one positional value per element after the tag. The CLI bundles them into a strongly-typed array (string[], int[], bool[], …) before writing.
# Write a 50-element string array
mxa write 'MESReceiver_001.MoveInPartNumbers[]' \
"" "11111" "" "" "" "" "" "" "" "" \
"" "" "" "" "" "15" "" "" "" "" \
"" "" "" "" "" "" "" "" "" "" \
"" "" "" "" "" "" "" "" "" "" \
"" "" "" "" "" "" "" "" "" ""
# Write a typed array
mxa write 'SomeObj.SomeFloats[]' 1.0 2.5 3.14 --type float
⚠️ A bulk write resizes the array to the count provided. If the configured
array_dimensionis 50 and you supply 25 values, after the writemxa read '...[]'returns 25 elements, not 50. The trailing slots are deallocated, not zero-filled. Always supply the full element count when you want to preserve the array's logical size — fetch the current count viamxa read '...[]' --llm-jsonfirst, or read it fromarray_dimensionin../../grdb/queries/attributes.sql.Mixing scalar / array forms is guarded: passing multiple values without
[]exits 2 with a clear error message.
What does not work
mxa read 'MESReceiver_001.MoveInPartNumbers' # bare ref, no brackets
# → MxCategoryCommunicationError, Detail=1003
The plain reference (no [], no [N]) is rejected by the proxy on both read and write. Always include the brackets — empty for whole-array, indexed for element.
Discovering array length
The CLI doesn't (yet) auto-discover element count. Two ways to find it:
- Read with
[]and count the returned values (this is the runtime length, which may have been resized by a previous bulk write). - Query the Galaxy Repository's
../../grdb/queries/attributes.sql— thearray_dimensioncolumn reports the configured size from the template at deploy time.
Picking a tag for a smoke test
If the live galaxy is not familiar:
- Connect to the Galaxy Repository SQL — see
../../grdb/connectioninfo.md. - Find a deployed instance with a writeable UDA —
../../grdb/queries/attributes.sqllists user-defined attributes with their data type. Filter on aWriteable_*security classification (see../../aot/dev-guide/appendix-e-security-classifications.md). - The reference for MxAccess is
<InstanceName>.<AttributeName>.