# Historian Implementation Gap Analysis Comparison of the current LmxOpcUa server historian implementation against the OPC UA Part 11 Historical Access specification requirements. ## Current Implementation Summary | Feature | Status | |---------|--------| | HistoryRead — ReadRawModifiedDetails | Implemented (raw only, no modified) | | HistoryRead — ReadProcessedDetails | Implemented (7 aggregates) | | Historizing attribute on nodes | Implemented | | AccessLevel.HistoryRead on nodes | Implemented | | Quality mapping (OPC DA → OPC UA) | Implemented | | Historian SDK (aahClientManaged) | Implemented (replaced direct SQL) | | Configurable enable/disable | Implemented | | SDK packet timeout | Implemented | | Max values per read | Implemented | | HistoryServerCapabilities node | Implemented | | AggregateFunctions folder | Implemented (7 functions) | | Continuation points for history reads | Implemented | ## Gaps ### 1. ~~HistoryServerCapabilities Node (Required)~~ — RESOLVED **Spec requirement:** All OPC UA servers supporting Historical Access SHALL include a `HistoryServerCapabilities` object under `ServerCapabilities`. This is mandatory, not optional. **Current state:** Implemented. The server populates all `HistoryServerCapabilities` variables at startup via `LmxOpcUaServer.ConfigureHistoryCapabilities()`. All boolean capabilities are set, and `MaxReturnDataValues` reflects the configured limit. **Required variables:** | Variable | Expected Value | Priority | |----------|---------------|----------| | `AccessHistoryDataCapability` | `true` | High | | `AccessHistoryEventsCapability` | `false` | High | | `MaxReturnDataValues` | configurable (e.g., 10000) | High | | `MaxReturnEventValues` | 0 | Medium | | `InsertDataCapability` | `false` | Medium | | `ReplaceDataCapability` | `false` | Medium | | `UpdateDataCapability` | `false` | Medium | | `DeleteRawCapability` | `false` | Medium | | `DeleteAtTimeCapability` | `false` | Medium | | `InsertAnnotationCapability` | `false` | Low | | `InsertEventCapability` | `false` | Low | | `ReplaceEventCapability` | `false` | Low | | `UpdateEventCapability` | `false` | Low | | `DeleteEventCapability` | `false` | Low | | `ServerTimestampSupported` | `true` | Medium | **Files to modify:** `LmxOpcUaServer.cs` or `LmxNodeManager.cs` — create and populate the `HistoryServerCapabilities` node in the server's address space during startup. ### 2. ~~AggregateFunctions Folder (Required)~~ — RESOLVED **Spec requirement:** The `HistoryServerCapabilities` object SHALL contain an `AggregateFunctions` folder listing all supported aggregate functions as child nodes. Clients browse this folder to discover available aggregates. **Current state:** Implemented. The `AggregateFunctions` folder under `HistoryServerCapabilities` is populated with references to all 7 supported aggregate function ObjectIds at startup. **Required:** Create `AggregateFunctions` folder under `HistoryServerCapabilities` with references to the 7 supported aggregate ObjectIds: - `AggregateFunction_Average` - `AggregateFunction_Minimum` - `AggregateFunction_Maximum` - `AggregateFunction_Count` - `AggregateFunction_Start` - `AggregateFunction_End` - `AggregateFunction_StandardDeviationPopulation` **Priority:** Medium ### 3. ~~Continuation Points for History Reads (Required)~~ — RESOLVED **Spec requirement:** When a HistoryRead result exceeds `MaxReturnDataValues` or the client's `NumValuesPerNode`, the server SHALL return a `ContinuationPoint` in the result. The client then issues follow-up HistoryRead calls with the continuation point to retrieve remaining data. The server must maintain state for active continuation points and release them when complete or on timeout. **Current state:** Implemented. `HistoryContinuationPointManager` stores remaining data keyed by GUID. Both `HistoryReadRawModified` and `HistoryReadProcessed` return a `ContinuationPoint` when results exceed `NumValuesPerNode`. Follow-up requests with the continuation point resume from stored state. Points expire after 5 minutes. Invalid or expired points return `BadContinuationPointInvalid`. **Priority:** High (resolved) ### 4. ~~ReadModified Support~~ — RESOLVED **Spec requirement:** `ReadRawModifiedDetails` has an `IsReadModified` flag. When `true`, the server should return the original value before modification along with the modification info (who modified, when, what the original value was). This is part of audit trail / data integrity use cases. **Current state:** Implemented. `HistoryReadRawModified` checks `details.IsReadModified` and returns `BadHistoryOperationUnsupported` when true, since the Wonderware Historian does not expose modification history. ### 5. ~~ReadAtTimeDetails~~ — RESOLVED **Spec requirement:** `ReadAtTimeDetails` allows a client to request interpolated values at specific timestamps (not raw samples). The server interpolates between the two nearest raw values for each requested timestamp. **Current state:** Implemented. `LmxNodeManager` overrides `HistoryReadAtTime`. `HistorianDataSource.ReadAtTimeAsync` uses the Historian SDK with `HistorianRetrievalMode.Interpolated` to query interpolated values at each requested timestamp. ### 6. ~~HistoryUpdate Service (Insert/Replace/Delete)~~ — RESOLVED (N/A) **Spec requirement:** The HistoryUpdate service allows clients to insert new values, replace existing values, update (insert or replace), and delete historical data. Each capability is separately advertised via the `HistoryServerCapabilities` node. **Current state:** Not applicable. The Historian is read-only. All write capability booleans (`InsertDataCapability`, `ReplaceDataCapability`, `UpdateDataCapability`, `DeleteRawCapability`, `DeleteAtTimeCapability`) are explicitly set to `false` in `ConfigureHistoryCapabilities()`. No `HistoryUpdate` override exists, which is correct. ### 7. ~~HistoryReadEventDetails (Historical Events)~~ — RESOLVED **Spec requirement:** Servers supporting historical event access implement `HistoryReadEventDetails` to retrieve past event notifications (e.g., alarm history). **Current state:** Implemented. `LmxNodeManager` overrides `HistoryReadEvents`. `HistorianDataSource.ReadEventsAsync` uses the Historian SDK with a separate `HistorianConnectionType.Event` connection and `EventQuery` to retrieve historical alarm/event records. Events are mapped to OPC UA `HistoryEventFieldList` entries with standard fields (EventId, EventType, SourceNode, SourceName, Time, ReceiveTime, Message, Severity). `AccessHistoryEventsCapability` is set to `true` when alarm tracking is enabled. ### 8. ~~HistoricalDataConfiguration Node~~ — RESOLVED **Spec requirement:** Each historized node SHOULD have a `HistoricalDataConfiguration` child object with properties describing how its history is stored: `Stepped` (interpolation type), `MinTimeInterval`, `MaxTimeInterval`, `ExceptionDeviation`, etc. **Current state:** Implemented. Historized variables receive a `HistoricalDataConfigurationState` child node with `Stepped = false` and `Definition = "Wonderware Historian"`. Recording parameters (intervals, deadbands) are not available from the Galaxy DB, so default values are used. ### 9. ~~AggregateConfiguration~~ — RESOLVED (N/A) **Spec requirement:** The `AggregateConfiguration` object (child of `HistoricalDataConfiguration` or `HistoryServerCapabilities`) defines default aggregate behavior: `TreatUncertainAsBad`, `PercentDataBad`, `PercentDataGood`, `UseSlopedExtrapolation`. **Current state:** Not applicable. The server delegates aggregation entirely to the Wonderware Historian's pre-computed summary tables, so these parameters are not actionable. Aggregate discovery is fully supported via the `AggregateFunctions` folder. ### 10. ~~ReturnBounds Parameter~~ — RESOLVED **Spec requirement:** When `ReturnBounds=true` in `ReadRawModifiedDetails`, the server should return bounding values at the start and end of the requested time range, even if no raw samples exist at those exact times. **Current state:** Implemented. When `ReturnBounds` is true, `AddBoundingValues` inserts boundary `DataValue` entries at `StartTime` and `EndTime` with `StatusCodes.BadBoundNotFound` if no sample exists at those exact times. ### 11. ~~Client-Side: StandardDeviation Aggregate~~ — RESOLVED **Current state:** Implemented. `AggregateType.StandardDeviation` added to enum, `AggregateTypeMapper`, CLI parser (aliases: `stddev`, `stdev`), and UI dropdown. Full end-to-end support from client to server for the `AggregateFunction_StandardDeviationPopulation` aggregate. ## Recommended Implementation Order | Priority | Gap | Effort | |----------|-----|--------| | ~~**High**~~ | ~~HistoryServerCapabilities node~~ | ~~RESOLVED~~ | | ~~**High**~~ | ~~Continuation points for history reads~~ | ~~RESOLVED~~ | | ~~**Medium**~~ | ~~AggregateFunctions folder~~ | ~~RESOLVED~~ | | ~~**Medium**~~ | ~~ReadAtTimeDetails (interpolation)~~ | ~~RESOLVED~~ | | ~~**Medium**~~ | ~~Advertise all capabilities as true/false~~ | ~~RESOLVED~~ | | ~~**Low**~~ | ~~Return BadHistoryOperationUnsupported for ReadModified~~ | ~~RESOLVED~~ | | ~~**Low**~~ | ~~HistoricalDataConfiguration per node~~ | ~~RESOLVED~~ | | ~~**Low**~~ | ~~Client StdDev aggregate support~~ | ~~RESOLVED~~ | | ~~**Low**~~ | ~~HistoryUpdate (write/delete)~~ | ~~RESOLVED (N/A)~~ | | ~~**Low**~~ | ~~Historical event access~~ | ~~RESOLVED~~ | | ~~**Low**~~ | ~~AggregateConfiguration~~ | ~~RESOLVED (N/A)~~ | | ~~**Low**~~ | ~~ReturnBounds~~ | ~~RESOLVED~~ | ## References - [OPC UA Part 11: Historical Access — Concepts](https://reference.opcfoundation.org/Core/Part11/v105/docs/4) - [OPC UA Part 11: Historical Access — HistoryServerCapabilitiesType](https://reference.opcfoundation.org/Core/Part11/v104/docs/5.4.2) - [OPC UA Part 11: Historical Access — Service Usage](https://reference.opcfoundation.org/Core/Part11/v104/docs/6) - [OPC UA Part 11: Historical Access — Data Architecture](https://reference.opcfoundation.org/Core/Part11/v104/docs/4.2) - [UA-.NETStandard Historical Access Overview](http://opcfoundation.github.io/UA-.NETStandard/help/historical_access_overview.htm) - [OPC Foundation Historical Data Access Wiki](http://wiki.opcfoundation.org/index.php?title=Historical_Data_Access)