9.3 KiB
Historian — server-side OPC UA HistoryRead (Phase C)
Phase C wires server-side OPC UA HistoryRead for authored equipment tags flagged
historized. The feature is driver-agnostic: any equipment tag (Galaxy, Modbus, OpcUaClient,
or any other driver) can be marked historized; the server dispatches all history reads to the
registered IHistorianDataSource — today, the Wonderware sidecar client
(WonderwareHistorianClient). No EF migration is required; the historian flag rides in the
existing schemaless TagConfig JSON blob alongside the Phase B alarm object.
Design reference: docs/plans/2026-06-14-galaxy-phase-c-historian-design.md.
Historized TagConfig schema
A tag is historized by adding fields to its TagConfig blob on the /uns equipment page
Tags tab (raw-JSON textarea). No separate UI control exists — Galaxy (the primary use case)
already uses the raw-JSON editor.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
isHistorized |
bool | yes | Marks the tag historized. Materialises the OPC UA node with Historizing=true and the HistoryRead AccessLevel bit. |
historianTagname |
string | no | Explicit historian tagname to query. When absent or empty, defaults to the tag's driver FullName (the TagConfig.FullName). |
Examples
Default historian tagname (uses FullName = TestMachine_002.TestFloat):
{"FullName":"TestMachine_002.TestFloat","isHistorized":true}
Explicit historian tagname override:
{"FullName":"TestMachine_002.TestFloat","isHistorized":true,"historianTagname":"Plant.Line1.Flow"}
A tag that is both historized and a native alarm:
{"FullName":"TestMachine_002.HiAlarm","isHistorized":true,"alarm":{"alarmType":"OffNormalAlarm","severity":700}}
ServerHistorian configuration
The ServerHistorian section in appsettings.json controls the historian read path.
Enabled defaults to false; when disabled, the server registers NullHistorianDataSource
and all HistoryRead calls on historized nodes return GoodNoData (empty, not an error).
{
"ServerHistorian": {
"Enabled": false,
"Host": "localhost",
"Port": 32569,
"UseTls": false,
"ServerCertThumbprint": "",
"SharedSecret": ""
}
}
| Key | Type | Default | Description |
|---|---|---|---|
Enabled |
bool | false |
Enable the live WonderwareHistorianClient. false → NullHistorianDataSource (empty reads). |
Host |
string | localhost |
DNS name or IP of the machine running the historian sidecar. |
Port |
int | 32569 |
TCP port the sidecar listens on (OTOPCUA_HISTORIAN_TCP_PORT). |
UseTls |
bool | false |
Wrap the TCP connection in TLS. |
ServerCertThumbprint |
string | — | Optional SHA-1 thumbprint to pin the sidecar's TLS certificate. Leave empty for CA-chain validation. |
SharedSecret |
string | — | Shared secret token the sidecar expects on every connection. Required when Enabled. |
Do not commit
SharedSecrettoappsettings.json. Set it via an environment variable, a secrets store, or a deployment-time overlay. The checked-in default is always empty.
The ServerHistorian section is independent of the AlarmHistorian section (the alarm
write path). They share the same Wonderware sidecar process but hold separate client
instances and separate SharedSecret values.
HistoryRead behavior
Read variants
The server supports all four OPC UA HistoryRead variants:
| Variant | Node type | CLI --aggregate |
|---|---|---|
| Raw | Historized variable | (omit --aggregate) |
| Processed | Historized variable | Average, Minimum, Maximum, Total, Count |
| AtTime | Historized variable | n/a (client supplies exact timestamps) |
| Events | Equipment folder (event notifier) | n/a |
Variable nodes (historized tags) serve Raw, Processed, and AtTime history.
Historizing=true and AccessLevels.HistoryRead are set at materialization so any compliant
OPC UA client can discover historized capability from the node's attributes.
Equipment-folder event-notifier nodes serve Event history. Every equipment folder that
owns at least one alarm condition is already an event notifier; the server registers a
sourceName (the equipment id) for each such folder and maps event history reads to the
Wonderware historian using that source. Event-field projection supports the standard
BaseEventType select clauses — EventId, SourceName, Time, ReceiveTime, Message,
and Severity; an unsupported select operand returns a null field (spec-conformant).
Graceful degradation
| Situation | HistoryRead status |
|---|---|
| Historized node, historian configured and reachable, results found | Good |
| Historized node, historian configured, time range empty | GoodNoData |
Historized node, historian NOT configured (Enabled=false / Null source) |
GoodNoData (empty) |
| Non-historized node | BadHistoryOperationUnsupported |
| Backend timeout or exception | BadHistoryOperationUnsupported per node; other nodes in the same batch are unaffected |
A historized node with no historian configured never returns an error status — it returns empty. This means a deployment can author and publish historized tags before the historian sidecar is provisioned, without producing error spikes in connected clients.
Known limitations
- No server-managed continuation points. Each HistoryRead call is single-shot. The server
honors the client's
NumValuesPerNodelimit but does not issue continuation points for large result sets. Paging across multiple calls is a documented follow-up. - No modified-value history (
HistoryReadModified). Requests for modified values returnBadHistoryOperationUnsupported. This is a follow-up.
Redundancy and authorization
History reads are served from any node — there is no Primary gate. Authorization is the
standard OPC UA HistoryRead permission enforced by the SDK through the AccessLevels.HistoryRead
bit set at materialization. A session without sufficient permissions receives
BadUserAccessDenied from the SDK before the dispatch reaches the historian.
Authoring workflow
- Open the equipment's Tags tab on
/uns/equipment/{id}. - Create or edit the tag. Because Galaxy uses the raw-JSON editor, add
"isHistorized":true(and optionally"historianTagname":"...") directly in the TagConfig textarea. - Save and publish. The server rebuilds its address space; the node materialises with
Historizing=trueand theHistoryReadAccessLevel bit. - Confirm with Client.CLI
readthat the node'sStatusisGoodand that the value is updating. Then issue ahistoryreadto verify the historian connection returns data.
Client.CLI historyread examples
The historyread command reads historical data from any node. Supply start and end times in
ISO 8601 UTC form. See docs/Client.CLI.md for the full flag reference.
# Raw history for a historized Galaxy tag (last 24 hours by default)
otopcua-cli historyread \
-u opc.tcp://localhost:4840/OtOpcUa \
-n "ns=2;s=EQ-55297329838d/GalaxyTestTag" \
--start "2026-06-13T00:00:00Z" --end "2026-06-14T00:00:00Z"
# Limit to 100 values
otopcua-cli historyread \
-u opc.tcp://localhost:4840/OtOpcUa \
-n "ns=2;s=EQ-55297329838d/GalaxyTestTag" \
--start "2026-06-13T00:00:00Z" --end "2026-06-14T00:00:00Z" \
--max 100
# 1-hour average aggregate
otopcua-cli historyread \
-u opc.tcp://localhost:4840/OtOpcUa \
-n "ns=2;s=EQ-55297329838d/GalaxyTestTag" \
--start "2026-06-13T00:00:00Z" --end "2026-06-14T00:00:00Z" \
--aggregate Average --interval 3600000
# Authenticated read (ReadOnly role or higher required)
otopcua-cli historyread \
-u opc.tcp://localhost:4840/OtOpcUa \
-n "ns=2;s=EQ-55297329838d/GalaxyTestTag" \
--start "2026-06-13T00:00:00Z" --end "2026-06-14T00:00:00Z" \
-U reader -P password
Supported --aggregate values: Average, Minimum, Maximum, Count, Start, End,
StandardDeviation (aliases: avg, min, max, stddev/stdev, first, last).
--interval is the processing interval in milliseconds (default 3600000 = 1 hour).
Live /run gate
The live read gate requires the Wonderware historian sidecar running on the WW Historian VM
(10.100.0.48) and AVEVA Historian healthy. Set ServerHistorian:Enabled=true with the
correct Host, Port, and SharedSecret in appsettings.json (or via environment
variables), then deploy and publish at least one historized Galaxy tag. The gate is
operator-driven — it is not part of the local docker-dev rig.
See AlarmHistorian.md for the historian sidecar setup and ServiceHosting.md for the sidecar service configuration.
See also
- docs/plans/2026-06-14-galaxy-phase-c-historian-design.md — full design and implementation notes
- AlarmHistorian.md — alarm write path; shares the same Wonderware sidecar
- AlarmTracking.md — OPC UA Part 9 alarm surface (event history source)
- Client.CLI.md — full
historyreadflag reference - ScriptedAlarms.md §"Native driver alarms" — the Phase B
alarmobject inTagConfig(parallel carrier)