diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs index 22b9de16..66f4f1a6 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs @@ -143,7 +143,13 @@ public sealed class RealMxGatewayClient : IMxGatewayClient var orderedTags = new List(writes.Count); foreach (var (tag, value) in writes) { - var handle = await GetOrAddItemHandleAsync(tag, ct).ConfigureAwait(false); + // MXAccess addresses a WHOLE array attribute with a trailing "[]" on the + // item reference. A write to the bare reference is silently dropped — the + // COM Write returns success but the value never commits (reads work + // either way). This mirrors the AVEVA MES Camstar API, which registers + // array tags as ".[]". Scalars keep the bare reference. + var writeRef = IsArrayValue(value) ? tag + "[]" : tag; + var handle = await GetOrAddItemHandleAsync(writeRef, ct).ConfigureAwait(false); // MXAccess requires a supervisory advise on the item before it will // accept a write; without it the worker's synchronous COM Write blocks. // With no write-user context we advise supervisory by default (a @@ -155,13 +161,14 @@ public sealed class RealMxGatewayClient : IMxGatewayClient // rejected and the COM write blocks. Pad list values out to the live // array's slot count before encoding (callers signal the valid count // via a separate scalar, e.g. MoveInNumberWorkOrders). - var toWrite = await PadArrayToDeclaredSizeAsync(tag, value, ct).ConfigureAwait(false); + var toWrite = await PadArrayToDeclaredSizeAsync(writeRef, value, ct).ConfigureAwait(false); entries.Add(new WriteBulkEntry { ItemHandle = handle, Value = ToMxValue(toWrite), UserId = _writeUserId, }); + // Map the per-handle result back to the ORIGINAL caller tag (not writeRef). orderedTags.Add(tag); } @@ -387,6 +394,26 @@ public sealed class RealMxGatewayClient : IMxGatewayClient } } + /// + /// Whether a write value is a multi-element (array) attribute value — i.e. one + /// encodes as an MXAccess array. Such attributes must be + /// addressed with a trailing "[]" on the item reference (see WriteAsync). + /// The set matches the array cases in / + /// ; scalars (string, int, bool, …) are false. + /// + private static bool IsArrayValue(object? value) => value switch + { + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + _ => false, + }; + private async Task> PadAsync(string tag, IReadOnlyList values, T pad, CancellationToken ct) { var size = await GetArraySlotCountAsync(tag, ct).ConfigureAwait(false);