Table now displays all 5 RPC types (Read, ReadBatch, Write, WriteBatch,
Subscribe) with dashes for zero-count operations instead of hiding the
table entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Subscription metrics (totalDelivered, totalDropped) now visible in
/api/status JSON and HTML dashboard. Card turns yellow if drops > 0.
Aggregated from per-client counters in SubscriptionManager.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Deviation #2: document three STA iterations (failed → Task.Run → StaComThread)
- Deviation #7: mark resolved — OnWriteComplete now works via STA message pump
- Deviation #8: note awaited subscription creation fixes flaky subscribe test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SubscriptionManager.Subscribe was fire-and-forgetting the MxAccess COM
subscription creation. The initial OnDataChange callback could fire
before the subscription was established, losing the first (and possibly
only) value update. Changed to async SubscribeAsync that awaits
CreateMxAccessSubscriptionsAsync before returning the channel reader.
Subscribe_ReceivesUpdates now passes 5/5 consecutive runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With StaComThread's GetMessage loop in place, OnWriteComplete callbacks
are now delivered properly. Write flow: dispatch Write() on STA thread,
await OnWriteComplete via TCS, clean up on STA thread. Falls back to
fire-and-forget on timeout as safety net. OnWriteComplete now resolves
or rejects the TCS with MxStatus error details.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multiple instances registering with the same name may cause MxAccess to
conflict on callback routing. ClientName is now configurable via
appsettings.json, defaulting to a GUID-suffixed name if not set.
Instances A and B use "LmxProxy-A" and "LmxProxy-B" respectively.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for the SubscriptionManager/MxAccessClient subscription pipeline:
1. Serialize Subscribe and UnsubscribeClient with a SemaphoreSlim gate to prevent
race where old-session unsubscribe removes new-session COM subscriptions.
CreateMxAccessSubscriptionsAsync is now awaited instead of fire-and-forget.
2. Fix dual VTQ delivery in MxAccessClient.OnDataChange — each update was delivered
twice (once via stored callback, once via OnTagValueChanged property). Now uses
stored callback as the single delivery path.
3. Store pending tag addresses when CreateMxAccessSubscriptionsAsync fails (MxAccess
down) and retry them on reconnect via NotifyReconnection/RetryPendingSubscriptionsAsync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added DatetimeArray message (repeated int64, UTC ticks) to proto and
code-first contracts. Host serializes DateTime[] → DatetimeArray.
Client deserializes DatetimeArray → DateTime[] (not raw long[]).
Client ExtractArrayValue now unpacks all array types including DateTime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DateTime[] from MxAccess was falling through to ToString() fallback,
producing "System.DateTime[]" instead of actual values. Now converts
each DateTime to UTC ticks and stores in Int64Array.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ArrayValue from LmxProxy client was showing as type name in debug views.
Added ValueFormatter utility and NormalizeValue in LmxProxyDataConnection
to convert arrays at the adapter boundary. DateTime arrays remain as
"System.DateTime[]" due to server-side v1 string serialization.
LmxFakeProxy is no longer needed now that two real LmxProxy v2 instances
are available for testing. Added remote test infra section to test_infra.md
documenting the windev instances. Removed tagsim (never committed).
Both instances share API keys and connect to the same AVEVA platform.
Verified: 17/17 integration tests pass against both instances.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AppEngine built-in tag is always present and constantly updating (~1s),
making it a more reliable probe than a user-deployed TestChildObject tag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gap 1: Active health probing verified — 60s recovery after platform restart.
Gap 2: Address-based subscription cleanup — no stale handles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tested aaBootstrap kill on windev — three gaps identified:
1. No active health probing (IsConnected stays true on dead connection)
2. Stale SubscriptionManager handles after reconnect cycle
3. AVEVA objects don't auto-start after platform crash (platform behavior)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MxStatusMapper: maps all 40+ MxStatusDetail codes, MxStatusCategory,
and MxStatusSource to human-readable names and client messages
- OnDataChange: checks MXSTATUS_PROXY.success and overrides quality with
specific OPC UA code when MxAccess reports a failure (e.g., CommFailure,
ConfigError, WaitingForInitialData)
- OnWriteComplete: uses MxStatusMapper.FormatStatus for structured logging
- Write errors: catches COMException separately with HRESULT in message
- Read errors: distinguishes COM, timeout, and generic failures in logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents when the full STA+Application.Run() approach is needed
(secured/verified writes), why our first attempt failed, the correct
pattern using Form.BeginInvoke(), and tradeoffs vs fire-and-forget.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MxAccess docs explicitly state OnWriteComplete always fires after Write().
The real cause is no Windows message pump in the headless service process to
marshal the COM callback. Fire-and-forget is safe for supervisory writes but
would miss secured/verified write rejections (errors 1012/1013).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SubscriptionManager tracked client-to-tag routing but never called
MxAccessClient.SubscribeAsync to create the actual COM subscriptions,
so OnDataChange never fired. Now creates MxAccess subscriptions for
new tags and disposes them when the last client unsubscribes.
All 17 integration tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replaced STA dispatch thread with Task.Run pattern for COM interop
- Fixed TypedValue oneof tracking with property-level _setCase field
- Added x-api-key DelegatingHandler for gRPC metadata authentication
- Fixed CheckApiKey RPC to validate request body key (not header)
- Integration tests: 15/17 pass (reads, subscribes, API keys, connections)
- 2 write tests pending (OnWriteComplete callback timing issue)
- v2 service deployed on windev port 50100
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace JoeAppEngine tags with TestChildObject tags (TestBool, TestInt, TestFloat,
TestDouble, TestString, TestDateTime, and array variants) in Phase 4 and Phase 7
plans. These tags cover all TypedValue oneof cases for comprehensive v2 testing.
Design doc covers architecture, v2 protocol (TypedValue/QualityCode), COM threading
model, session lifecycle, subscription semantics, error model, and guardrails.
Implementation plans are detailed enough for autonomous Claude Code execution.
Verified all dev tooling on windev (Grpc.Tools, protobuf-net.Grpc, Polly v8, xUnit).
Generate high-level requirements and 10 component documents derived from source code
and protocol specs. Uses lmxproxy_updates.md (v2 TypedValue/QualityCode) as the source
of truth, with v1 string-based encoding documented as legacy context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split LmxProxy Host and Client into a self-contained solution under lmxproxy/,
ported from the ScadaBridge monorepo with updated namespaces (ZB.MOM.WW.LmxProxy.*).
Client project (.NET 10) inlines Core/DataEngine dependencies and builds clean.
Host project (.NET Fx 4.8) retains ArchestrA.MXAccess for Windows deployment.
Added windev.md documenting the WW_DEV_VM development environment setup.