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 车道)。