Phase 3 PR 38 — DriverNodeManager HistoryRead override (LMX #1 finish) #37
Reference in New Issue
Block a user
Delete Branch "phase-3-pr38-historyread-servicehandler"
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?
Last missing piece of LMX #1. Wires the OPC UA HistoryRead service through
CustomNodeManager2's four protected per-kind hooks so clients can actually reach theIHistoryProvidercapability we exposed in PR 35.What changes
Overrides
HistoryReadRawModified/HistoryReadProcessed/HistoryReadAtTime/HistoryReadEventsonDriverNodeManager. Each hook:nodesToProcess(the filtered subset the stack assigned to this manager).handle.NodeId.Identifier→ driver-side full reference.IHistoryProvidermethod.results[handle.Index]ANDerrors[handle.Index]via aWriteResulthelper.Hard-won gotchas (documented inline)
handle.Index, not the loop counter —nodesToProcessis filtered fromnodesToRead. Writing toresults[n]lands in the wrong slot for mixed-manager batches.results[i]ANDerrors[i]must be set —MasterNodeManagermerges them, and leavingerrors[i]at its default (BadHistoryOperationUnsupported) overrides a Goodresults[i]on the wire. Cost most of the debug budget. HelpersWriteResult/WriteUnsupported/WriteInternalError/WriteNodeIdUnknownalways touch both.AccessLevels.HistoryReadon variables — the stack's early-gate check (variable.AccessLevel & HistoryRead != 0) rejects requests before our override runs. Historized variables now set the bit inAccessLevel+UserAccessLevel.EventNotifiers.HistoryRead | SubscribeToEventson the driver root — required forHistoryReadEventsto target the folder (the conventional pattern for alarm-history browse).OpcHistoryReadResultalias —Opc.Ua.HistoryReadResult(service-layer) collides withCore.Abstractions.HistoryReadResult(driver-side) by type name; explicit alias keeps both overrides and test stubs unambiguous.Aggregate + event shape
MapAggregatetranslatesObjectIds.AggregateFunction_{Average|Minimum|Maximum|Total|Count}to the driver enum; returns null for anything else so the handler surfacesBadAggregateNotSupportedat the batch level (per Part 13 —ReadProcessedDetailscarries one aggregate for all nodes).BuildHistoryDatawraps driver samples asOpc.Ua.HistoryDatain anExtensionObject.BuildHistoryEventemits aHistoryEventwith the canonical BaseEventType field list (EventId / SourceName / Message / Severity / Time / ReceiveTime) — the order clients with defaultSimpleAttributeOperands expect. Per-SelectClauseevaluation is a follow-up.Tests
DriverNodeManagerHistoryMappingTests— 12 unit cases pinningMapAggregate,BuildHistoryData,BuildHistoryEvent,ToDataValue. Reflection-backed theory for aggregates guards against the stack renamingAggregateFunction_*. Null-preservation +DateTimeKind.Utc+ canonical field-list ordering pinned as regression guards.HistoryReadIntegrationTests— 5 end-to-end cases drive a realSession.HistoryReadagainst a fakeIHistoryProviderdriver through the running stack:Averageaggregate (captures driver-received aggregate + interval).TimeAverage→BadAggregateNotSupported).Test posture
Deferred (explicit in
lmx-followups.md)Session.Save/RestoreHistoryContinuationPoint. Driver returns null continuations today so pass-through works.SelectClauseevaluation forHistoryReadEvents. Clients with custom field selections currently get the canonical layout.