Coerce scalar OPC UA writes to the node DataType — String Variants are rejected with BadTypeMismatch #58
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Register row 120 (deferred-work register).
InstanceActor.HandleSetDataAttribute/HandleWriteAttributeBatchdecode only List values before the DCL write, so codec-encoded scalar strings ("True","42.5") reachRealOpcUaClient.WriteValuesAsync, which wraps them innew Variant(value)— a String variant to a Boolean/Double node →0x80740000. Found live 2026-09-10 against opc-plc during the #36 rig gate. MxGateway path unaffected in practice.Revisit trigger (from register): Before any OPC UA-bound template relies on script writes to non-String nodes.
Current state: Not fixed; no OPC UA-bound template relies on script writes to non-String nodes yet.
Preferred fix (b): adapter-local coercion in
RealOpcUaClientto the node DataType (it already resolvesDataTypeIdsinEnrichVariableTypeInfoAsync), leaving the MxGateway wire untouched; pin with a Boolean-scalar test. Related: #36.Source:
docs/plans/2026-07-08-deferred-work-register.mdrow 120Fixed in
a5c3de63(pushed tomain).Design: adapter-local coercion in
RealOpcUaClient(option b; MxGateway wire untouched).WriteValuesAsyncreads theDataTypeattribute of each written node once per session (batched,ConcurrentDictionary<NodeId,BuiltInType>, cleared on connect/disconnect; derived types and enumerations resolved viaTypeInfo.GetBuiltInTypeAsyncover the session TypeTree), then converts non-null scalars via the newOpcUaWriteCoerciontable: Boolean (true/false/1/0), SByte..UInt64, Float, Double, String, DateTime (ISO 8601, offset-less = UTC), Guid — all culture-invariant. Overflow, fractional-to-integer and unparseable strings are refused client-side withBadTypeMismatch+ a message naming the value and target type (never rounded). Arrays, null, unmapped types and nodes whose DataType read fails pass through unchanged.WriteValueAsyncnow throws with the outcome status code instead of alwaysBadNodeIdUnknown.Component-DataConnectionLayer.mdupdated.Evidence:
OpcUaWriteCoercionTests(unit): 67 cases incl. overflow per integer width, invalid strings,42,5/1,000refused, de-DE current culture ignored.OpcUaWriteCoercionLiveTests(RequiresOpcUa) against infra opc-plc: String\"1234.5\"/\"True\"/\"17\"written to Motor.Speed (Double) / Motor.Running (Boolean) / Motor.FaultCode (UInt32) read back as typed double/bool/uint (two value rounds);\"-1\"→UInt32 and\"abc\"→Double refused with BadTypeMismatch and node values unchanged. Positive control: with coercion disabled the same test fails with 2155085824 (0x80740000) for all three writes.ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests: 483 passed / 0 failed / 0 skipped (opc-plc up).dotnet build ZB.MOM.WW.ScadaBridge.slnx: 0 errors withNU1903demoted — the NU1903 (Microsoft.DotNet.ILCompiler 10.0.5 advisory on DelmiaNotifier) is pre-existing on origin/main and unrelated.Not deployed.