docs(historian): alerts-fed historization (scripted, Primary-gated, exactly-once) + config-gated Sqlite→Wonderware sink
This commit is contained in:
+45
-5
@@ -145,13 +145,53 @@ fresh backoff.
|
|||||||
|
|
||||||
## Runtime wiring
|
## Runtime wiring
|
||||||
|
|
||||||
Production routes alarm transitions through the Akka cluster. The
|
|
||||||
`HistorianAdapterActor`
|
`HistorianAdapterActor`
|
||||||
([`Runtime/Historian/HistorianAdapterActor.cs`](../src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs))
|
([`Runtime/Historian/HistorianAdapterActor.cs`](../src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs))
|
||||||
bridges messages from the scripted-alarm actor into the sink's `EnqueueAsync`,
|
subscribes to the cluster **`alerts` DPS topic** and translates each
|
||||||
fire-and-forget so the actor loop is never blocked on historian reachability.
|
`AlarmTransitionEvent` into an `AlarmHistorianEvent`, then calls
|
||||||
The `WonderwareHistorianClient` is the `IAlarmHistorianWriter` the drain worker
|
`IAlarmHistorianSink.EnqueueAsync` fire-and-forget so the actor loop is never
|
||||||
delegates to. See [ServiceHosting.md](ServiceHosting.md) for the sidecar setup.
|
blocked on historian reachability. The actor is **Primary-gated**: only the
|
||||||
|
node whose `RedundancyRole` is `Primary` historizes, giving exactly-once
|
||||||
|
writes across a redundant pair. `AlarmTransitionEvent` carries `AlarmTypeName`
|
||||||
|
(the Part 9 subtype string) and `Comment` (the operator comment from the
|
||||||
|
originating ack/shelve command) that populate the corresponding fields of
|
||||||
|
`AlarmHistorianEvent`. `WonderwareHistorianClient` is the `IAlarmHistorianWriter`
|
||||||
|
the drain worker delegates to. See [ServiceHosting.md](ServiceHosting.md) for
|
||||||
|
the sidecar setup.
|
||||||
|
|
||||||
|
**Scope:** scripted alarms only. Galaxy-native alarms historize via System
|
||||||
|
Platform's `HistorizeToAveva` toggle (not this actor); AB CIP ALMD is not on
|
||||||
|
the `alerts` topic (future).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The real sink is opt-in via the `AlarmHistorian` section of `appsettings.json`.
|
||||||
|
When `Enabled` is `false` (the default), `AddAlarmHistorian` registers
|
||||||
|
`NullAlarmHistorianSink` and the feature is dormant. When `Enabled` is `true`,
|
||||||
|
`AddAlarmHistorian` constructs `SqliteStoreAndForwardSink` and registers
|
||||||
|
`WonderwareHistorianClient` as the `IAlarmHistorianWriter`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"AlarmHistorian": {
|
||||||
|
"Enabled": true,
|
||||||
|
"DatabasePath": "C:\\ProgramData\\OtOpcUa\\alarmhistorian.db",
|
||||||
|
"PipeName": "\\\\.\\pipe\\wonderware-historian",
|
||||||
|
"SharedSecret": "<token from historian sidecar config>",
|
||||||
|
"BatchSize": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `Enabled` | bool | `false` | Enable the real SQLite + Wonderware sink. `false` → `NullAlarmHistorianSink`. |
|
||||||
|
| `DatabasePath` | string | — | Absolute path to the SQLite queue file. Created on first use (WAL mode). Required when `Enabled`. |
|
||||||
|
| `PipeName` | string | — | Named-pipe path for the Wonderware Historian sidecar IPC channel. Required when `Enabled`. |
|
||||||
|
| `SharedSecret` | string | — | Shared secret token the sidecar expects on every connection. Required when `Enabled`. |
|
||||||
|
| `BatchSize` | int | `100` | Max rows per drain cycle handed to `IAlarmHistorianWriter.WriteBatchAsync`. |
|
||||||
|
|
||||||
|
> Dev and docker-dev deployments leave `Enabled` unset (defaults to `false`) so alarm transitions historize to nowhere unless a historian sidecar is present.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -178,9 +178,12 @@ AB CIP ALMD) route to AVEVA Historian via the Wonderware sidecar:
|
|||||||
silent data loss without log scraping.
|
silent data loss without log scraping.
|
||||||
- `HistorianAdapterActor`
|
- `HistorianAdapterActor`
|
||||||
(`src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs`)
|
(`src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs`)
|
||||||
bridges Akka cluster messages from `ScriptedAlarmActor` into the
|
subscribes to the cluster `alerts` DPS topic, translates each
|
||||||
sink's `EnqueueAsync`; fire-and-forget so the actor loop is never
|
`AlarmTransitionEvent` → `AlarmHistorianEvent`, and calls
|
||||||
blocked on historian reachability.
|
`EnqueueAsync` fire-and-forget. Primary-gated — only the Primary node
|
||||||
|
historizes, giving exactly-once writes across a redundant pair. See
|
||||||
|
[AlarmHistorian.md §Configuration](AlarmHistorian.md#configuration)
|
||||||
|
for the `AlarmHistorian` appsettings section that enables the real sink.
|
||||||
|
|
||||||
Galaxy-native alarms with `$Alarm*` extensions reach AVEVA Historian
|
Galaxy-native alarms with `$Alarm*` extensions reach AVEVA Historian
|
||||||
directly via System Platform's `HistorizeToAveva` toggle on the
|
directly via System Platform's `HistorizeToAveva` toggle on the
|
||||||
|
|||||||
+1
-1
@@ -140,7 +140,7 @@ There is no operator-driven role swap during a partition. Failover is what the c
|
|||||||
Under warm/hot redundancy both cluster nodes run `ScriptedAlarmHostActor` and evaluate scripted alarms, keeping each node's address space and engine state warm for instant failover. However, to avoid duplicate rows on `/alerts` and duplicate historian writes, only the Primary node publishes externally:
|
Under warm/hot redundancy both cluster nodes run `ScriptedAlarmHostActor` and evaluate scripted alarms, keeping each node's address space and engine state warm for instant failover. However, to avoid duplicate rows on `/alerts` and duplicate historian writes, only the Primary node publishes externally:
|
||||||
|
|
||||||
- **`alerts` topic emission** — `ScriptedAlarmHostActor` subscribes to the `redundancy-state` DPS topic and caches the local node's `RedundancyRole`. Each alarm transition is published to the cluster `alerts` topic **only when the node's role is `Primary`**. The default behaviour before any `redundancy-state` message arrives is to emit, so single-node deployments and the boot window never drop transitions. The OPC UA condition-node write and inbound ack/shelve command processing remain **ungated** on both nodes so the secondary is always ready to serve clients after a failover.
|
- **`alerts` topic emission** — `ScriptedAlarmHostActor` subscribes to the `redundancy-state` DPS topic and caches the local node's `RedundancyRole`. Each alarm transition is published to the cluster `alerts` topic **only when the node's role is `Primary`**. The default behaviour before any `redundancy-state` message arrives is to emit, so single-node deployments and the boot window never drop transitions. The OPC UA condition-node write and inbound ack/shelve command processing remain **ungated** on both nodes so the secondary is always ready to serve clients after a failover.
|
||||||
- **`HistorianAdapterActor` historization** — likewise Primary-gated so alarm historization is exactly-once across all alarm sources. (This actor currently has no production feeder; the gate is a forward-looking guard.)
|
- **`HistorianAdapterActor` historization** — likewise Primary-gated so alarm historization is exactly-once across all alarm sources. The actor subscribes to the `alerts` DPS topic and translates each `AlarmTransitionEvent` → `AlarmHistorianEvent` before enqueuing it on the sink; scripted alarms therefore historize exactly once regardless of cluster size.
|
||||||
|
|
||||||
Net effect: each alarm transition appears **once** on `/alerts` and would historize once, not once per node.
|
Net effect: each alarm transition appears **once** on `/alerts` and would historize once, not once per node.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user