@@ -166,6 +166,35 @@ Beyond that:
|
||||
3. **Dedicated historian integration lab** — only path for
|
||||
historian-specific coverage.
|
||||
|
||||
## HistoryRead aggregate coverage
|
||||
|
||||
PR-13 (issue #285) extended `HistoryAggregateType` from 5 to ~30 values
|
||||
matching the OPC UA Part 13 §5 catalog. The mapping itself
|
||||
(`OpcUaClientDriver.MapAggregateToNodeId`) is unit-tested via
|
||||
`OpcUaClientAggregateMappingTests`:
|
||||
|
||||
- The full enum is swept with `Enum.GetValues<HistoryAggregateType>()` —
|
||||
every value must resolve to a non-null namespace-0 numeric `NodeId`.
|
||||
- The 25 new aggregates each assert against a reflection-resolved
|
||||
`Opc.Ua.ObjectIds.AggregateFunction_*` field by name, so a future SDK
|
||||
upgrade that renames a constant trips the test loudly.
|
||||
- The original 5 ordinals stay pinned to their pre-PR-13 NodeIds so existing
|
||||
config files / persisted enums keep working.
|
||||
|
||||
This is **the well-known-NodeId test path** — the standard Part 13 NodeIds
|
||||
are stable across SDK versions; round-tripping each one against a live
|
||||
upstream is the integration suite's job and doesn't add coverage to the
|
||||
mapping table itself.
|
||||
|
||||
`OpcUaClientAggregateSweepTests` is the integration counterpart. It loops
|
||||
every enum value against a real opc-plc upstream and asserts the wire path
|
||||
doesn't crash even when the simulator returns
|
||||
`BadAggregateNotSupported` for an aggregate it doesn't honour. opc-plc's
|
||||
default profile doesn't enable HistoryRead on the well-known nodes, so the
|
||||
test currently `Assert.Skip`s — re-enables when the fixture image is
|
||||
upgraded to a history-sim profile (`--useslowtypes --ut=10` or similar) and
|
||||
a known-good historized NodeId is wired into `OpcPlcProfile`.
|
||||
|
||||
## Key fixture / config files
|
||||
|
||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/` — unit tests with
|
||||
@@ -175,3 +204,7 @@ Beyond that:
|
||||
- `tests/ZB.MOM.WW.OtOpcUa.Server.Tests/OpcUaServerIntegrationTests.cs` —
|
||||
the server-side integration harness a future loopback client test could
|
||||
piggyback on
|
||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientAggregateMappingTests.cs`
|
||||
— Part 13 aggregate enum-to-NodeId mapping coverage (PR-13)
|
||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcUaClientAggregateSweepTests.cs`
|
||||
— wire-side aggregate sweep against opc-plc (build-only scaffold; PR-13)
|
||||
|
||||
@@ -193,3 +193,68 @@ HistoryRead facade is responsible for round-tripping these so a paged event
|
||||
read against a chatty upstream completes incrementally. The driver itself
|
||||
doesn't track them — every `ReadEventsAsync` call issues a fresh
|
||||
`HistoryReadAsync`.
|
||||
|
||||
## HistoryRead Aggregates (Part 13 catalog)
|
||||
|
||||
`IHistoryProvider.ReadProcessedAsync` takes a `HistoryAggregateType` and the
|
||||
driver maps it to the standard `Opc.Ua.ObjectIds.AggregateFunction_*` NodeId
|
||||
in `MapAggregateToNodeId`. PR-13 (issue #285) extended the enum from the
|
||||
original 5 values (Average / Minimum / Maximum / Total / Count) to the full
|
||||
OPC UA Part 13 §5 catalog — ~30 aggregates.
|
||||
|
||||
The mapping is best-effort: not every upstream OPC UA server implements every
|
||||
aggregate. Aggregates the upstream rejects come back with
|
||||
`StatusCode=BadAggregateNotSupported` on the per-row HistoryRead result; the
|
||||
driver passes that through verbatim (cascading-quality rule, Part 11 §8) — it
|
||||
does not throw. Servers advertise the aggregates they support via the
|
||||
`AggregateConfiguration` object on the `Server` node; clients can probe it at
|
||||
runtime.
|
||||
|
||||
### Catalog
|
||||
|
||||
| Enum value | SDK NodeId field | Part 13 § | Server-side support | Typical use |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `Average` | `AggregateFunction_Average` | §5.4 | almost always | smoothing |
|
||||
| `Minimum` | `AggregateFunction_Minimum` | §5.5 | almost always | low watermark |
|
||||
| `Maximum` | `AggregateFunction_Maximum` | §5.6 | almost always | high watermark |
|
||||
| `Total` | `AggregateFunction_Total` | §5.10 | usually | totalisation |
|
||||
| `Count` | `AggregateFunction_Count` | §5.18 | almost always | sample count |
|
||||
| `TimeAverage` | `AggregateFunction_TimeAverage` | §5.4.2 | usually | time-weighted mean |
|
||||
| `TimeAverage2` | `AggregateFunction_TimeAverage2` | §5.4.3 | sometimes | bounded time-weighted mean |
|
||||
| `Interpolative` | `AggregateFunction_Interpolative` | §5.3 | usually | trend snapshot |
|
||||
| `MinimumActualTime` | `AggregateFunction_MinimumActualTime` | §5.5.4 | sometimes | when low occurred |
|
||||
| `MaximumActualTime` | `AggregateFunction_MaximumActualTime` | §5.6.4 | sometimes | when high occurred |
|
||||
| `Range` | `AggregateFunction_Range` | §5.7 | usually | spread |
|
||||
| `Range2` | `AggregateFunction_Range2` | §5.7 | sometimes | bounded spread |
|
||||
| `AnnotationCount` | `AggregateFunction_AnnotationCount` | §5.21 | rarely | operator notes |
|
||||
| `DurationGood` | `AggregateFunction_DurationGood` | §5.16 | sometimes | quality coverage |
|
||||
| `DurationBad` | `AggregateFunction_DurationBad` | §5.16 | sometimes | gap accounting |
|
||||
| `PercentGood` | `AggregateFunction_PercentGood` | §5.17 | sometimes | quality % |
|
||||
| `PercentBad` | `AggregateFunction_PercentBad` | §5.17 | sometimes | gap % |
|
||||
| `WorstQuality` | `AggregateFunction_WorstQuality` | §5.20 | sometimes | worst seen |
|
||||
| `WorstQuality2` | `AggregateFunction_WorstQuality2` | §5.20 | rarely | bounded worst |
|
||||
| `StandardDeviationSample` | `AggregateFunction_StandardDeviationSample` | §5.13 | sometimes | n-1 stddev |
|
||||
| `StandardDeviationPopulation` | `AggregateFunction_StandardDeviationPopulation` | §5.13 | sometimes | n stddev |
|
||||
| `VarianceSample` | `AggregateFunction_VarianceSample` | §5.13 | sometimes | n-1 variance |
|
||||
| `VariancePopulation` | `AggregateFunction_VariancePopulation` | §5.13 | sometimes | n variance |
|
||||
| `NumberOfTransitions` | `AggregateFunction_NumberOfTransitions` | §5.12 | sometimes | event count |
|
||||
| `DurationInStateZero` | `AggregateFunction_DurationInStateZero` | §5.19 | sometimes | OFF time |
|
||||
| `DurationInStateNonZero` | `AggregateFunction_DurationInStateNonZero` | §5.19 | sometimes | ON time |
|
||||
| `Start` | `AggregateFunction_Start` | §5.8 | usually | first sample |
|
||||
| `End` | `AggregateFunction_End` | §5.9 | usually | last sample |
|
||||
| `Delta` | `AggregateFunction_Delta` | §5.11 | usually | end-start |
|
||||
| `StartBound` | `AggregateFunction_StartBound` | §5.8 | sometimes | extrapolated start |
|
||||
| `EndBound` | `AggregateFunction_EndBound` | §5.9 | sometimes | extrapolated end |
|
||||
|
||||
"Server-side support" is heuristic — see your upstream's `AggregateConfiguration`
|
||||
node for the authoritative list. AVEVA Historian, KEPServerEX, Prosys, and
|
||||
opc-plc each implement different subsets.
|
||||
|
||||
### Driver-side validation
|
||||
|
||||
The mapping itself is unit-tested over the full enum
|
||||
(`OpcUaClientAggregateMappingTests`) — every value resolves to a non-null
|
||||
namespace-0 NodeId, and the original 5 ordinals stay pinned. Wire-side
|
||||
behaviour against a live server is exercised by
|
||||
`OpcUaClientAggregateSweepTests` (build-only scaffold pending an opc-plc
|
||||
history-sim profile).
|
||||
|
||||
Reference in New Issue
Block a user