Auto: opcuaclient-12 — IHistoryProvider.ReadEventsAsync EventFilter spec + impl
Adds a filter-aware overload of IHistoryProvider.ReadEventsAsync that carries EventFilter SelectClauses + WhereClause, and implements it on the OPC UA Client driver via Session.HistoryReadAsync + ReadEventDetails. The change is additive (default-impl returns NotSupportedException) so the existing Galaxy.Proxy.GalaxyProxyDriver implementation keeps compiling against the fixed-field overload — no cross-driver refactor required. * Core.Abstractions: new EventHistoryRequest / SimpleAttributeSpec / ContentFilterSpec records mirror the OPC UA wire shape transport-neutrally. HistoricalEventBatch / HistoricalEventRow carry an open-ended Fields bag keyed by SimpleAttributeSpec.FieldName so server-side dispatch can re-align with the client's wire-side SelectClause order. * OpcUaClient driver: new ReadEventsAsync(fullReference, EventHistoryRequest, ct) builds an EventFilter, calls Session.HistoryReadAsync, and unwraps HistoryEvent.Events into HistoricalEventBatch rows. Default SelectClause set matches BuildHistoryEvent on the server side. ContentFilter bytes are decoded through the live session's MessageContext (passthrough — the driver does not evaluate filters). * Unit tests: 7 new tests cover SelectClause translation, default-clause fallback, malformed where-clause swallowing, uninitialized-driver guard, null-request guard, and IHistoryProvider default fallback. * Integration scaffold: build-only [Fact] gated on opc-plc --alm; flips to green when the fixture image is upgraded. * Docs: HistoryRead Events section in docs/drivers/OpcUaClient.md plus a cross-link from Client.CLI.md historyread page. * E2E: -HistoryEvents switch on scripts/e2e/test-opcuaclient.ps1 confirms the gateway round-trips HistoryReadEvents without BadHistoryOperationUnsupported (gated; defaults to skip). Closes #284
This commit is contained in:
@@ -126,3 +126,70 @@ expose a "ReverseConnect.Endpoint" config knob).
|
||||
- Public-internet OPC UA: reverse-connect is a network-policy workaround,
|
||||
not a security primitive. Always pair with `Sign` or `SignAndEncrypt`
|
||||
+ a vetted user-token policy.
|
||||
|
||||
## HistoryRead Events
|
||||
|
||||
The driver passes through OPC UA `HistoryReadEvents` to the upstream server.
|
||||
HistoryRead Raw / Processed / AtTime ship in the same code path
|
||||
(`ExecuteHistoryReadAsync`); event history takes a slightly different shape
|
||||
because the client sends an `EventFilter` (SelectClauses + WhereClause) rather
|
||||
than a plain numeric / time-based detail block.
|
||||
|
||||
### Wire path
|
||||
|
||||
`IHistoryProvider.ReadEventsAsync(fullReference, EventHistoryRequest, ct)`
|
||||
translates to:
|
||||
|
||||
```
|
||||
new ReadEventDetails {
|
||||
StartTime,
|
||||
EndTime,
|
||||
NumValuesPerNode,
|
||||
Filter = EventFilter { SelectClauses, WhereClause }
|
||||
}
|
||||
```
|
||||
|
||||
…and is sent through `Session.HistoryReadAsync` to the upstream server. The
|
||||
returned `HistoryEvent.Events` collection (one `HistoryEventFieldList` per
|
||||
historical event) is unwrapped into `HistoricalEventBatch.Events`, where each
|
||||
`HistoricalEventRow.Fields` dictionary is keyed by the
|
||||
`SimpleAttributeSpec.FieldName` the caller supplied. The server-side history
|
||||
dispatcher uses those keys to align fields with the wire-side SelectClause
|
||||
order — drivers don't have to honour the entire OPC UA `EventFilter` shape
|
||||
verbatim.
|
||||
|
||||
### SelectClauses
|
||||
|
||||
When `EventHistoryRequest.SelectClauses` is `null` the driver falls back to a
|
||||
default set that matches `BuildHistoryEvent` on the server side:
|
||||
|
||||
| Field | Browse path | Notes |
|
||||
| --- | --- | --- |
|
||||
| `EventId` | `EventId` | BaseEventType — stable unique id. |
|
||||
| `SourceName` | `SourceName` | Source-object name. |
|
||||
| `Time` | `Time` | Process-side event timestamp. Used for `OccurrenceTime`. |
|
||||
| `Message` | `Message` | LocalizedText payload. |
|
||||
| `Severity` | `Severity` | OPC UA 1-1000 scale. |
|
||||
| `ReceiveTime` | `ReceiveTime` | Server-side ingest timestamp. |
|
||||
|
||||
Custom SelectClauses are supported — pass any
|
||||
`IReadOnlyList<SimpleAttributeSpec>`. Each entry's `TypeDefinitionId`
|
||||
defaults to `BaseEventType` when `null`; pass an explicit NodeId text (e.g.
|
||||
`"i=2782"` for `ConditionType`) to reach typed-condition fields.
|
||||
|
||||
### WhereClause
|
||||
|
||||
`ContentFilterSpec.EncodedOperands` carries the binary-encoded
|
||||
`ContentFilter` from the wire. The driver decodes it into the SDK
|
||||
`ContentFilter` and attaches it to the outgoing `EventFilter` verbatim — the
|
||||
OPC UA Client driver is a passthrough for filter semantics, it does not
|
||||
evaluate them. A malformed filter is dropped silently; the SelectClause
|
||||
projection still goes out.
|
||||
|
||||
### Continuation points
|
||||
|
||||
Returned in `HistoricalEventBatch.ContinuationPoint`. The server-side
|
||||
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`.
|
||||
|
||||
Reference in New Issue
Block a user