using System.Collections.Generic; using System.IO; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian; /// /// Binds the AlarmHistorian configuration section that gates the durable /// store-and-forward alarm sink. When is true, /// AddAlarmHistorian registers a SqliteStoreAndForwardSink (draining to the /// Wonderware named-pipe writer supplied by the Host) in place of the /// NullAlarmHistorianSink default; otherwise the Null default survives. /// public sealed class AlarmHistorianOptions { /// The configuration section name this options class binds. public const string SectionName = "AlarmHistorian"; /// /// When true, the durable SQLite store-and-forward sink is registered; when /// false (the default) the no-op NullAlarmHistorianSink stays in place. /// public bool Enabled { get; init; } /// Filesystem path to the local SQLite store-and-forward queue database. public string DatabasePath { get; init; } = "alarm-historian.db"; /// Named-pipe name the Wonderware historian sidecar listens on. public string PipeName { get; init; } = "OtOpcUaHistorian"; /// TCP hostname or IP address the Wonderware historian sidecar listens on. public string Host { get; init; } = "localhost"; /// TCP port the Wonderware historian sidecar listens on. public int Port { get; init; } = 32569; /// When true, the client connects over TLS. public bool UseTls { get; init; } /// Expected TLS server certificate thumbprint (hex, no spaces). Null or empty disables pinning. public string? ServerCertThumbprint { get; init; } /// Per-process shared secret the sidecar verifies in the Hello frame. public string SharedSecret { get; init; } = ""; /// Maximum number of queued rows the drain worker forwards in a single batch. public int BatchSize { get; init; } = 100; /// Seconds between drain-worker ticks. Defaults to 5. public int DrainIntervalSeconds { get; init; } = 5; /// Maximum queued rows before the sink evicts the oldest. Defaults to 1,000,000 /// (matches SqliteStoreAndForwardSink's DefaultCapacity). public long Capacity { get; init; } = SqliteStoreAndForwardSink.DefaultCapacity; /// Days to retain dead-lettered rows before purge. Defaults to 30. public int DeadLetterRetentionDays { get; init; } = 30; /// Returns operator-facing misconfiguration warnings for an Enabled historian /// (empty when disabled or correctly configured). Pure — the registration logs each entry. /// Zero or more human-readable warning messages. public IReadOnlyList Validate() { var warnings = new List(); if (!Enabled) return warnings; if (string.IsNullOrWhiteSpace(SharedSecret)) warnings.Add("AlarmHistorian:SharedSecret is empty while the historian is enabled — the Wonderware sidecar Hello frame will carry an empty secret."); if (!Path.IsPathRooted(DatabasePath)) warnings.Add($"AlarmHistorian:DatabasePath '{DatabasePath}' is relative — it resolves against the process working directory (e.g. System32 for a Windows service). Set an absolute path."); if (DrainIntervalSeconds <= 0) warnings.Add($"AlarmHistorian:DrainIntervalSeconds is {DrainIntervalSeconds} — must be > 0; the drain timer will throw or spin at startup."); if (Capacity <= 0) warnings.Add($"AlarmHistorian:Capacity is {Capacity} — must be > 0; the sink constructor will throw at startup."); if (DeadLetterRetentionDays <= 0) warnings.Add($"AlarmHistorian:DeadLetterRetentionDays is {DeadLetterRetentionDays} — must be > 0; dead-lettered rows would be purged on every drain tick."); return warnings; } }