监控逻辑

This commit is contained in:
hsc
2026-07-10 11:57:56 +08:00
parent 52d486e2b1
commit db8baed9c7
9 changed files with 435 additions and 97 deletions

View File

@@ -1,6 +1,8 @@
using Model.Models;
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIShare.PubEvent;
using ZLGUSBCANFD;
@@ -8,78 +10,108 @@ using ZLGUSBCANFD;
namespace UIShare.GlobalVariable
{
/// <summary>
/// CAN 信号广播器:订阅 ZLGCANFD.OnDbcMessageDecoded 事件,
/// 将解码后的 DBC 信号值通过 HardwareDataReportedEvent 广播给监控系统。
/// DeviceManager 创建和管理,生命周期与 CAN 设备绑定。
/// CAN 信号广播器(全局单例):
/// 统一订阅 GlobalInfo.CanPool 中所有已实例化的 ZLGCANFD 设备,
/// DBC 解码后的信号值广播给所有引用该 CAN 设备的作用域,
/// 并同步检查各作用域 ValueLimitList 的超限报警。
/// </summary>
public class CANSignalBroadcaster : IDisposable
{
private readonly IEventAggregator _eventAggregator;
private readonly string _scopeName;
private ZLGCANFD? _canfd;
private readonly GlobalInfo _globalInfo;
private bool _disposed;
public CANSignalBroadcaster(IEventAggregator eventAggregator, string scopeName)
/// <summary>已订阅解码事件的 CANFD 实例 → 委托 映射</summary>
private readonly Dictionary<ZLGCANFD, Action<uint, ZDBC.DBCMessage>> _handlers = new();
public CANSignalBroadcaster(GlobalInfo globalInfo, IEventAggregator eventAggregator)
{
_globalInfo = globalInfo;
_eventAggregator = eventAggregator;
_scopeName = scopeName;
}
/// <summary>
/// 绑定 CANFD 实例并订阅解码事件。
/// 扫描 GlobalInfo.CanPool 中已创建的 CANFD 实例并订阅解码事件。
/// 幂等:已订阅的实例不会重复订阅。
/// </summary>
public void Bind(ZLGCANFD canfd)
public void Discover()
{
Unbind();
_canfd = canfd;
_canfd.OnDbcMessageDecoded += OnDbcMessageDecoded;
}
if (_disposed) return;
/// <summary>
/// 取消订阅。
/// </summary>
public void Unbind()
{
if (_canfd != null)
foreach (var kvp in _globalInfo.CanPool)
{
_canfd.OnDbcMessageDecoded -= OnDbcMessageDecoded;
_canfd = null;
string fingerprint = kvp.Key;
var lazy = kvp.Value;
if (!lazy.IsValueCreated || lazy.Value == null) continue;
var canfd = lazy.Value;
if (_handlers.ContainsKey(canfd)) continue;
// 使用闭包捕获指纹,回调时即可区分不同 CAN 卡
Action<uint, ZDBC.DBCMessage> handler = (channel, msg) => OnDbcMessageDecoded(fingerprint, channel, msg);
canfd.OnDbcMessageDecoded += handler;
_handlers[canfd] = handler;
}
}
/// <summary>
/// DBC 解码回调:提取所有信号并广播
/// 启动广播器(目前 Discover 已完成订阅,此处保留以兼容 HardwareDataBroadcaster 的使用模式)
/// </summary>
private void OnDbcMessageDecoded(uint channel, ZDBC.DBCMessage msg)
public void Start()
{
Discover();
}
/// <summary>
/// DBC 解码回调:提取所有信号,向引用该 CAN 设备的作用域广播,并检查报警。
/// </summary>
private void OnDbcMessageDecoded(string canFingerprint, uint channel, ZDBC.DBCMessage msg)
{
if (_disposed) return;
string fingerprint = $"CAN:{channel}";
string signalFingerprint = BuildFingerprint(channel);
var now = DateTime.Now;
// 获取引用该 CAN 设备的所有作用域
var scopes = GetScopesForFingerprint(canFingerprint);
if (scopes.Count == 0) return;
for (int i = 0; i < msg.nSignalCount; i++)
{
var signal = msg.vSignals[i];
string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (string.IsNullOrEmpty(signalName)) continue;
// 计算物理值physical = raw * factor + offset
double physicalValue = signal.nRawvalue * signal.nFactor + signal.nOffset;
string methodName = BuildMethodName(msg.nID, signalName);
// MethodName 格式:"{MessageId:X}.{SignalName}"
string methodName = $"{msg.nID:X}.{signalName}";
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
foreach (var scope in scopes)
{
Scope = _scopeName,
HardwareFingerprint = fingerprint,
MethodName = methodName,
Value = physicalValue,
Time = now
});
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
{
Scope = scope,
HardwareFingerprint = signalFingerprint,
MethodName = methodName,
Value = physicalValue,
Time = now
});
ValueLimitAlarmHelper.CheckAlarm(scope, signalFingerprint, methodName, physicalValue, _globalInfo);
}
}
}
/// <summary>获取指定硬件指纹当前被哪些作用域引用</summary>
private List<string> GetScopesForFingerprint(string fingerprint)
{
if (_globalInfo.DeviceAndScopeDic.TryGetValue(fingerprint, out var lazy))
{
var scopeList = lazy.Value;
lock (scopeList) return scopeList.ToList();
}
return new List<string>();
}
/// <summary>
/// 生成 CAN 信号的 DisplayName 格式:"{MessageName}.{SignalName}"
/// </summary>
@@ -108,7 +140,13 @@ namespace UIShare.GlobalVariable
{
if (_disposed) return;
_disposed = true;
Unbind();
foreach (var kvp in _handlers)
{
if (kvp.Key != null)
kvp.Key.OnDbcMessageDecoded -= kvp.Value;
}
_handlers.Clear();
}
}
}

View File

@@ -27,9 +27,6 @@ namespace UIShare.GlobalVariable
private readonly string _scopeName;
private readonly IEventAggregator _eventAggregator;
/// <summary>CAN 信号广播器:将 DBC 解码后的信号值广播给监控系统</summary>
private CANSignalBroadcaster? _canSignalBroadcaster;
/// <summary>按 DeviceName 索引的设备字典,便于业务层按名取实例。</summary>
public IDictionary<string, IBaseInterface> DeviceMap { get; private set; }
= new Dictionary<string, IBaseInterface>(StringComparer.OrdinalIgnoreCase);
@@ -439,10 +436,6 @@ namespace UIShare.GlobalVariable
bool ok = await Task.Run(() =>
{
if (!CANFD.()) return false;
// 启动 CAN 信号广播器
_canSignalBroadcaster = new CANSignalBroadcaster(_eventAggregator, _scopeName);
_canSignalBroadcaster.Bind(CANFD);
// 自动加载 DBC 文件
if (_systemConfig?.DBCAutoLoadList != null)
{
@@ -528,10 +521,6 @@ namespace UIShare.GlobalVariable
return;
}
// 先释放 CAN 信号广播器,取消事件订阅,防止关闭过程中仍触发回调
_canSignalBroadcaster?.Dispose();
_canSignalBroadcaster = null;
await Task.Run(() => CANFD.CAN卡设备());
LoggerHelper.Info($"CAN 设备 [{name}] 已成功关闭连接。");
}
@@ -591,10 +580,6 @@ namespace UIShare.GlobalVariable
public void Dispose()
{
// 释放 CAN 信号广播器
_canSignalBroadcaster?.Dispose();
_canSignalBroadcaster = null;
if (string.IsNullOrEmpty(_scopeName)) return;
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用

View File

@@ -179,6 +179,9 @@ namespace UIShare.GlobalVariable
Value = value,
Time = now
});
// 同步检查该作用域 ValueLimitList 是否超限
ValueLimitAlarmHelper.CheckAlarm(scope, entry.Fingerprint, entry.MethodName, value, _globalInfo);
}
}
catch

View File

@@ -0,0 +1,56 @@
using System.Linq;
using UIShare.UIViewModel;
namespace UIShare.GlobalVariable
{
/// <summary>
/// 值限制报警检查辅助类:供各广播器在采样到信号值时统一判断是否超限。
/// </summary>
public static class ValueLimitAlarmHelper
{
/// <summary>
/// 根据指定作用域的 ValueLimitList 检查当前值是否超限,并更新报警状态。
/// </summary>
/// <param name="scope">作用域名称</param>
/// <param name="fingerprint">硬件指纹</param>
/// <param name="methodName">方法名/信号标识</param>
/// <param name="value">当前采样值</param>
/// <param name="globalInfo">全局信息</param>
public static void CheckAlarm(string scope, string fingerprint, string methodName, double value, GlobalInfo globalInfo)
{
if (globalInfo?.ConfigDic == null) return;
if (!globalInfo.ConfigDic.TryGetValue(scope, out var systemConfig)) return;
if (systemConfig.ValueLimitList == null) return;
var limit = systemConfig.ValueLimitList.FirstOrDefault(x =>
x.Fingerprint == fingerprint && x.MethodName == methodName);
if (limit == null) return;
if (value > limit.Upper)
{
limit.IsAlarm = true;
limit.AlarmSatus = AlarmStatus.;
}
else if (value < limit.Lower)
{
limit.IsAlarm = true;
limit.AlarmSatus = AlarmStatus.;
}
else if(value > limit.UpperExtreme)
{
limit.IsAlarm = true;
limit.AlarmSatus = AlarmStatus.;
}
else if (value < limit.LowerExtreme)
{
limit.IsAlarm = true;
limit.AlarmSatus = AlarmStatus.;
}
else
{
limit.IsAlarm = false;
limit.AlarmSatus = AlarmStatus.;
}
}
}
}