04 · 历史库与报警引擎 —— Hmi.Historian / Hmi.Alarms

两个 RTDB 订阅者:Hmi.Historian 把点值按规则存进 SQLite 时序库,Hmi.Alarms 对点值跑限值/开关量检测走四态机。二者都只依赖 Hmi.Core,可单独取用。


Hmi.Historian —— 时序历史库

职责:SQLite 存储抽象 + 订阅 RTDB 的记录器(死区/定时规则)+ 保留清理。依赖 Hmi.Core;Microsoft.Data.Sqlite 8.0.10。

存储契约 IHistorian

src/Hmi.Historian/IHistorian.cs:

方法 作用
void Store(int tagId, TagValue v, long tsUtc, Quality q, TagDataType t) 写一点
IReadOnlyList<HistPoint> Query(int, DateTime from, DateTime to, int? maxPoints=null) 查询(服务端抽稀)
double Aggregate(int, from, to, AggFunc) 聚合(Avg/Max/Min/Sum/Count)
int Purge(DateTime olderThanUtc) 整表清理
int PurgeByTag(IReadOnlyDictionary<int,int> retentionDaysByTag, DateTime nowUtc) 按点保留天数清理

DTO:readonly record struct HistPoint(long TsUtc, double Value, Quality Quality)。实现 SqliteHistorian(批量异步写)、清理器 RetentionSweeper。

记录器 HistorianRecorder : ITagSubscriber

src/Hmi.Historian/HistorianRecorder.cs(public sealed class)。构造 (IHistorian, IReadOnlyDictionary<int,TagDataType>);SetRule(TagHistoryRule)、bool IsRecorded(int)、OnTagChanged(...)。在分发线程上按规则(OnChange/Timed/Both + 死区)决定存不存,重活(落盘)交给历史库自己的写线程。


Hmi.Alarms —— 报警引擎

职责:订阅 RTDB,把限值/开关量检测经死区/延时送进四态机,带 SQLite 记录。依赖 Hmi.Core;Microsoft.Data.Sqlite 8.0.10。

引擎 AlarmEngine : ITagSubscriber

src/Hmi.Alarms/Engine/AlarmEngine.cs(public sealed partial class,另有 .Detection / .Scripting 分部)。构造 (IEnumerable<TagAlarmConfig>? configs = null, IClock? clock = null, IAlarmStore? store = null):

  • event Action<AlarmRecord>? AlarmChanged —— 产生/确认/恢复/清除时触发。
  • IReadOnlyList<AlarmRecord> Active() —— 当前活动报警。

只评估变化了的点(不全表轮询);_armedTags 集合驱动周期延时 Tick,把持锁时间压到极小。检测与状态机在 .Detection,脚本触发在 .Scripting。IAlarmStore 是报警记录落点;AlarmEngineService(在 ScriptRuntime 里)把它适配给脚本。


小结

  • 单独做历史:实现或用 SqliteHistorian,套 HistorianRecorder 订到 RtdbCore。
  • 单独做报警:new AlarmEngine(configs) 订到 RtdbCore,听 AlarmChanged。
  • 实战中一般不手接 —— 交给第 03 章 RuntimeDataServices.Compose 一次性组好(它内部就是建这两个 + 订到 Lossless 车道)。