Files
wwtools/mxaccesscli/docs/api-notes.md
Joseph Doherty ab202a1fa1 mxaccesscli: read/write/subscribe System Platform tags via MxAccess
New tool wrapping ArchestrA.MxAccess.LMXProxyServerClass (the same COM
proxy aaObjectViewer / WindowViewer use) as a CliFx CLI for LLM-driven
debugging.

Commands:
- mxa info      — loaded MxAccess assembly identity, supported value
                   types, MxStatusCategory enum.
- mxa read      — fetch one or more tag values; subscribes briefly,
                   captures first OnDataChange per tag, tears down.
- mxa write     — write a value with optional --type coercion; advises
                   first to resolve the attribute type, then waits for
                   OnWriteComplete with a per-call timeout.
- mxa subscribe — stream OnDataChange events for --seconds; JSON Lines
                   under --llm-json for piped agent consumption.
- mxa diag      — minimal smoke test on a private STA thread; bypasses
                   the CliFx pipeline for diagnosing apartment / pump
                   issues.

Implementation notes documented in docs/api-notes.md (reverse-engineered
because AVEVA does not publish a single canonical MxAccess reference):

- Net48 / x86 / [STAThread] are non-negotiable. The CLI runs the entire
  CliFx pipeline on a dedicated STA thread.
- COM events are dispatched as Win32 messages; AutoResetEvent.WaitOne
  alone does not pump them on this configuration. MxSession.WaitForUpdate
  loops Application.DoEvents() + drain + Sleep(20ms) instead.
- Write requires the target attribute's type to be resolved first.
  WriteCommand advises and waits for the initial OnDataChange before
  calling LMXProxyServerClass.Write to avoid ArgumentException
  "Value does not fall within the expected range".
- Errors carry the full MXSTATUS_PROXY[] from MxAccess (Success,
  Category, DetectedBy, Detail) so an agent can tell exactly which
  layer rejected a request.

Verified against the live ZB galaxy with a writeable tag identified
via grdb (TestChildObject.TestInt, mx_attribute_category=10):
  read:      99 (q=192, MxCategoryOk)
  write 7:   round-tripped — read returned 7 — written back to 99
  write str: TestChildObject.TestString round-tripped a timestamp
  subscribe: captured initial value plus subsequent change from a
             separate process

The vendored ArchestrA.MxAccess.dll is gitignored — it is copied from
C:\Program Files (x86)\ArchestrA\Framework\Bin\ on any System Platform
install per the README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:02:51 -04:00

8.8 KiB

MxAccess API notes

Reverse-engineered from the shipped ArchestrA.MxAccess.dll (assembly version 3.2.0.0, file at C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll) plus its sibling type libraries MXAccess.tlb / MXAccess20.tlb / MXAccess32.tlb. AVEVA does not publish a single canonical online reference for this assembly, which is why this file exists.

The assembly is registered in the GAC at C:\Windows\assembly\GAC_MSIL\ArchestrA.MxAccess\3.2.0.0__23106a86e706d0ae\. Strong name: PublicKeyToken=23106a86e706d0ae.

Threading model

LMXProxyServerClass is a COM proxy. All calls — and the events the proxy fires — run on the apartment that called Register. The CLI's Program.Main is [STAThread] for this reason; if you call into MxAccess from a non-STA thread you'll observe:

  • Register() returns a handle but events never fire.
  • Write() queues but OnWriteComplete never reaches your handler.

A deeper STA pump is unnecessary for one-shot CLI invocations because CliFx's ICommand.ExecuteAsync runs on the entry thread synchronously, and the COM marshaller pumps the calling apartment while waiting on WaitOne.

Type catalog

The full public surface from reflection:

class  LMXProxyServerClass    -- the entry point (newable)
class  LMXProxyServer         -- alias / synonym
iface  ILMXProxyServer        -- v1 method set (Register, AddItem, Advise, Write, WriteSecured, AuthenticateUser)
iface  ILMXProxyServer2       -- adds  ArchestrAUserToId
iface  ILMXProxyServer3       -- adds  AddItem2
iface  ILMXProxyServer4       -- adds  Write2, WriteSecured2, Suspend, Activate, AdviseSupervisory
iface  ILMXProxyServer5       -- adds  AddBufferedItem, SetBufferedUpdateInterval

iface  _ILMXProxyServerEvents          -- OnDataChange, OnWriteComplete, OperationComplete
iface  _ILMXProxyServerEvents2         -- OnBufferedDataChange

struct MxStatus              -- success:int16, category:MxStatusCategory, detectedBy:MxStatusSource, detail:int16
struct MXSTATUS_PROXY        -- same fields; passed to event handlers as an array
enum   MxStatusCategory      -- 10 values; see `mxa info`
enum   MxStatusSource        -- 7 values; "who detected this status"
enum   MxDataType            -- MxNoData / MxBoolean / MxInteger / MxFloat / MxDouble / MxString / MxTime / MxElapsedTime / MxReferenceType / MxStatusType / MxDataTypeEnum / MxSecurityClassificationEnum / MxDataQualityType / MxQualifiedEnum / MxQualifiedStruct / MxInternationalizedString / MxBigString / MxDataTypeEND / MxDataTypeUnknown

Lifecycle methods

int  Register(string clientName);           // returns hServer
void Unregister(int hServer);

int  AddItem(int hServer, string itemRef);  // returns hItem
int  AddItem2(int hServer, string itemRef, string itemContext);
int  AddBufferedItem(int hServer, string itemRef, string itemContext);
void RemoveItem(int hServer, int hItem);

void Advise(int hServer, int hItem);
void UnAdvise(int hServer, int hItem);
void AdviseSupervisory(int hServer, int hItem);    // higher-rate / mxsupv updates
void SetBufferedUpdateInterval(int hServer, int intervalMs);

Suspend / Activate toggle update delivery on a per-item basis without losing the AddItem handle.

Write methods

void Write   (int hServer, int hItem, object value, int userId);
void Write2  (int hServer, int hItem, object value, object timestamp, int userId);
void WriteSecured  (int hServer, int hItem, int currentUserId, int verifierUserId, object value);
void WriteSecured2 (int hServer, int hItem, int currentUserId, int verifierUserId, object value, object timestamp);

userId = 0 is unauthenticated. For attributes with Operate, Tune, Configure, View Only, Free Access, Secured Write, or Verified Write classifications (see ../../aot/dev-guide/appendix-e-security-classifications.md):

  • Free AccessWrite succeeds with userId = 0.
  • Operate / Tune / ConfigureWrite requires a real userId from AuthenticateUser.
  • Secured Write — must call WriteSecured with the same user as both currentUserId and verifierUserId (or use Write after AuthenticateUser if Galaxy security allows).
  • Verified Write — must call WriteSecured with two distinct authenticated user ids (operator + verifier).

Authentication helpers

int AuthenticateUser(int hServer, string verifyUser, string verifyUserPwd);
int ArchestrAUserToId(int hServer, string userIdGuid);

AuthenticateUser returns a non-zero user id when credentials match the Galaxy security configuration, or 0 on failure. ArchestrAUserToId resolves a Galaxy user GUID to its numeric id.

Events

All events fire on the registering apartment. The CLI bridges them to a ConcurrentQueue<MxUpdate> plus an AutoResetEvent so command code uses WaitForUpdate(predicate, timeout, out update) rather than dealing with the COM event signature directly.

event OnDataChange(int hServer, int hItem,
                   object value, int quality, object timestamp,
                   ref MXSTATUS_PROXY[] statuses);

event OnWriteComplete(int hServer, int hItem, ref MXSTATUS_PROXY[] statuses);

event OperationComplete(int hServer, int hItem, ref MXSTATUS_PROXY[] statuses);
                                              // fires on AddItem completion etc.

event OnBufferedDataChange(int hServer, int hItem, MxDataType dataType,
                           object value, object quality, object timestamp,
                           ref MXSTATUS_PROXY[] statuses);
                                              // only when AddBufferedItem was used

timestamp arrives as a Win32 FILETIME boxed inside an object (decimal/Int64-shaped). The CLI converts via DateTime.FromFileTimeUtc(Convert.ToInt64(...)) and exposes it as a local-time DateTime?; if conversion fails the field is null.

quality on OnDataChange is the legacy 16-bit OPC quality value (e.g. 192 = 0xC0 for "Good"). The richer state lives in the statuses[] array, especially Category and DetectedBy.

Status semantics

MxStatusCategory values, in roughly ascending severity:

Category Meaning
MxCategoryOk Operation succeeded.
MxCategoryPending Operation accepted, working on it; not an error.
MxCategoryWarning Soft issue (e.g. value clamped); operation proceeded.
MxCategoryCommunicationError LMX/NMX / engine reach failure.
MxCategoryConfigurationError Reference unresolved, attribute missing, type mismatch.
MxCategoryOperationalError Object not OnScan, attribute read-only, etc.
MxCategorySecurityError Secured / Verified attribute and credentials insufficient.
MxCategorySoftwareError Bug in the LMX runtime — usually rare.
MxCategoryOtherError Catch-all.
MxStatusCategoryUnknown Couldn't classify.

MxStatusSource tells you who set the category — useful when the same code (e.g. MxCategoryConfigurationError) can come from the requesting LMX (your client side) versus the responding automation object (the server side):

MxSourceRequestingLmx           - this CLI's local LMX
MxSourceRespondingLmx           - the LMX hosting the target
MxSourceRequestingNmx           - cross-node messaging on this side
MxSourceRespondingNmx           - cross-node messaging on the target side
MxSourceRequestingAutomationObject
MxSourceRespondingAutomationObject
MxSourceUnknown

A statuses array often has multiple entries — one per layer that touched the request. The CLI emits all of them so an agent can pinpoint which layer rejected the call.

Buffered items

AddBufferedItem returns updates batched at SetBufferedUpdateInterval (milliseconds) intervals via OnBufferedDataChange. Useful for high-rate tags where you want time-bucketed snapshots instead of every tick. The CLI does not expose this surface yet — adding a subscribe --buffered <ms> option is straightforward.

Type libraries

File Use
MXAccess.tlb v1 — minimal interface, legacy clients.
MXAccess20.tlb adds events.
MXAccess32.tlb full v3.x surface — what ArchestrA.MxAccess.dll 3.2.0.0 projects.

If you need to consume MxAccess from a different language (C++, Python via comtypes, etc.), import MXAccess32.tlb rather than the older versions — they expose subsets.

What the CLI does not (yet) cover

  • Suspend / Activate — easy to add as a sub-command.
  • AddItem2 with explicit context — the CLI passes a single tag reference; if your environment needs context (cross-galaxy, role-based) wrap it.
  • WriteSecured / WriteSecured2 — adding a --verifier-id <int> option to WriteCommand is the natural extension.
  • AdviseSupervisory and AddBufferedItem — opt-in performance modes.