No driver's Bad/Uncertain status sub-code reaches an OPC UA client (3-state OpcUaQuality collapse) #519
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?
Found during the MTConnect live gate (PR #506, 2026-07-27) and confirmed by direct code inspection. Not MTConnect-specific and out of scope for that plan.
Symptom
MTConnect maps the Agent's
UNAVAILABLEsentinel toBadNoCommunication(0x80310000) atMTConnectObservationIndex.cs:255. An OPC UA client reading that node gets0x80000000(genericStatusCodes.Bad).Verified live:
ns=2;s=mtc-1/vmc/fixture_asset_changedread0x80000000while carrying the Agent's exact source timestamp — which proves the publish landed and that only the status differs.Cause — two hops
DriverInstanceActor.QualityFromStatus(src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs:1032-1041) projects the driver'suintstatus onto the 3-stateOpcUaQualityenum using only the top two severity bits:Every
Badsub-code collapses toOpcUaQuality.Bad.OtOpcUaNodeManager.StatusFromQuality(src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs:3318-3322) re-expandsBad→StatusCodes.Bad=0x80000000.Every message type between the two is already enum-typed (
AttributeValuePublished→AttributeValueUpdate→IOpcUaAddressSpaceSink.WriteValue), so nothing in between could have preserved the sub-code.Not a null-value artifact — there is no null/Variant special-casing on the path. A
Badstatus carrying a non-null value collapses identically.It is deliberate — but the consequence looks undocumented
OpcUaQuality's own doc comment (src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/IOpcUaAddressSpaceSink.cs:164) says: "Real SDK has finer-grained codes; the engine actors only need this 3-state classification."So the design decision is intentional. What appears unwritten anywhere is the client-visible consequence: no driver's
Bad/Uncertainsub-code ever reaches an OPC UA client.That interacts with recent work. #497 corrected 16 wrong status constants across 6 drivers, and
StatusCodeParityTestsnow guards them by reflection — both valuable for internal consistency — but a client still cannot distinguishBadNoCommunicationfromBadTypeMismatchfromBadNotSupported. Diagnostic intent survives in logs only.One sub-code does survive:
BadWaitingForInitialData(0x80320000), because it is written directly at the node (OtOpcUaNodeManager.cs:1806/:1901) rather than routed through the quality projection.Options
docs/that driver status sub-codes are log-only, so driver authors don't assume clients can act on them. Cheap and honest.uintthroughAttributeValuePublished→AttributeValueUpdate→IOpcUaAddressSpaceSink.WriteValue→WriteValue, touching the sink interface and its three implementations (Sdk,Deferred,Null). The real fix if clients need to act on sub-codes.Worth an explicit decision either way, since right now the constants are maintained as if they mattered end-to-end.