监控逻辑
This commit is contained in:
@@ -85,6 +85,7 @@ namespace ADP
|
||||
containerRegistry.RegisterScoped<DeviceManager>();
|
||||
containerRegistry.RegisterSingleton<GlobalInfo>();
|
||||
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
|
||||
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
|
||||
//注册AutoMapper
|
||||
var config = new MapperConfiguration(
|
||||
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using System;
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.ViewModelBase;
|
||||
using ZLGUSBCANFD;
|
||||
|
||||
namespace MonitorModule.ViewModels.Dialogs
|
||||
{
|
||||
@@ -37,41 +39,48 @@ namespace MonitorModule.ViewModels.Dialogs
|
||||
get => _ValueLimitList;
|
||||
set => SetProperty(ref _ValueLimitList, value);
|
||||
}
|
||||
private ObservableCollection<string> _DeviceSingleList = new();
|
||||
//private ObservableCollection<string> _DeviceSingleList = new();
|
||||
|
||||
public ObservableCollection<string> DeviceSingleList
|
||||
{
|
||||
get => _DeviceSingleList;
|
||||
set => SetProperty(ref _DeviceSingleList, value);
|
||||
}
|
||||
private string _SelectCurve;
|
||||
//public ObservableCollection<string> DeviceSingleList
|
||||
//{
|
||||
// get => _DeviceSingleList;
|
||||
// set => SetProperty(ref _DeviceSingleList, value);
|
||||
//}
|
||||
//private string _SelectCurve;
|
||||
|
||||
public string SelectCurve
|
||||
{
|
||||
get => _SelectCurve;
|
||||
set => SetProperty(ref _SelectCurve, value);
|
||||
}
|
||||
//public string SelectCurve
|
||||
//{
|
||||
// get => _SelectCurve;
|
||||
// set => SetProperty(ref _SelectCurve, value);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
#region 命令
|
||||
public ICommand SaveCommand { get; set; }
|
||||
public ICommand CloseCommand { get; set; }
|
||||
public ICommand SelectSignalCommand { get; set; }
|
||||
//public ICommand SelectSignalCommand { get; set; }
|
||||
public ICommand DeleteCommand { get; set; }
|
||||
|
||||
#endregion
|
||||
private GlobalInfo _globalInfo { get; set; }
|
||||
private SystemConfig _systemConfig { get; set; }
|
||||
private IScopedProvider _scope { get; set; }
|
||||
private DeviceManager? _deviceManager;
|
||||
|
||||
/// <summary>SignalName → CAN信号标识,用于DBC加载/卸载时清理</summary>
|
||||
private readonly Dictionary<string, (string Fingerprint, string MethodName)> _canSignalMap = new();
|
||||
|
||||
private SubscriptionToken? _dbcLoadedToken;
|
||||
private SubscriptionToken? _dbcUnloadedToken;
|
||||
|
||||
public ValueLimitViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
SaveCommand = new DelegateCommand(OnSave);
|
||||
CloseCommand = new DelegateCommand(OnClose);
|
||||
SelectSignalCommand = new DelegateCommand(SelectSignal);
|
||||
//SelectSignalCommand = new DelegateCommand(SelectSignal);
|
||||
DeleteCommand = new DelegateCommand(Deletel);
|
||||
InitData();
|
||||
}
|
||||
#region 命令处理
|
||||
private void Deletel()
|
||||
@@ -90,35 +99,138 @@ namespace MonitorModule.ViewModels.Dialogs
|
||||
|
||||
private void InitData()
|
||||
{
|
||||
//ValueLimitList = new(_systemConfig.ValueLimitList);
|
||||
//foreach (var _PollingRead in _devices.PollingReadDic)
|
||||
//{
|
||||
// string deviceName = _PollingRead.Key;
|
||||
// PollingRead pollingRead = _PollingRead.Value;
|
||||
ValueLimitList = new ObservableCollection<ValueLimitVM>();
|
||||
//DeviceSingleList = new ObservableCollection<string>();
|
||||
_canSignalMap.Clear();
|
||||
|
||||
// var props = pollingRead.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
// .Where(p => p.PropertyType == typeof(double));
|
||||
// 1. 加载已保存的 ValueLimitList(保留用户设置)
|
||||
if (_systemConfig?.ValueLimitList != null)
|
||||
{
|
||||
foreach (var item in _systemConfig.ValueLimitList)
|
||||
ValueLimitList.Add(item);
|
||||
}
|
||||
|
||||
// foreach (var prop in props)
|
||||
// {
|
||||
// DeviceSingleList.Add(deviceName + "." + prop.Name);
|
||||
// }
|
||||
//}
|
||||
//foreach (var Signal in _systemConfig.ConfigurationList)
|
||||
//{
|
||||
// DeviceSingleList.Add($"{Signal.Channel}/{Signal.MessageName}/{Signal.SignalName}");
|
||||
//}
|
||||
// 2. 发现设备方法信号并加入 ValueLimitList / DeviceSingleList
|
||||
foreach (var signal in DiscoverDeviceSignals())
|
||||
{
|
||||
//DeviceSingleList.Add(signal);
|
||||
EnsureValueLimit(signal);
|
||||
}
|
||||
|
||||
// 3. 发现 CAN 信号并加入 ValueLimitList / DeviceSingleList
|
||||
foreach (var (displayName, fingerprint, methodName) in DiscoverCanSignals())
|
||||
{
|
||||
//DeviceSingleList.Add(displayName);
|
||||
_canSignalMap[displayName] = (fingerprint, methodName);
|
||||
EnsureValueLimit(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectSignal()
|
||||
/// <summary>
|
||||
/// 发现当前作用域所有可监测的设备方法信号(与 MonitorViewModel 逻辑一致)。
|
||||
/// 返回 DisplayName 列表。
|
||||
/// </summary>
|
||||
private IEnumerable<string> DiscoverDeviceSignals()
|
||||
{
|
||||
if (ValueLimitList.Where(x => x.SignalName == SelectCurve).Count() > 0)
|
||||
if (_deviceManager?.DeviceMap == null) yield break;
|
||||
|
||||
var instanceToFp = BuildInstanceToFingerprint();
|
||||
foreach (var kvp in _deviceManager.DeviceMap)
|
||||
{
|
||||
return;
|
||||
string deviceName = kvp.Key;
|
||||
var device = kvp.Value;
|
||||
|
||||
if (!instanceToFp.TryGetValue(device, out string? fingerprint))
|
||||
continue;
|
||||
|
||||
var methods = device.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m =>
|
||||
{
|
||||
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
|
||||
if (m.ReturnType != typeof(Task<string>)) return false;
|
||||
var parms = m.GetParameters();
|
||||
return parms.Length == 0 ||
|
||||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
|
||||
});
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var attr = method.GetCustomAttribute<MonitorableAttribute>();
|
||||
string displayName = !string.IsNullOrEmpty(attr?.Description)
|
||||
? $"{deviceName}.{attr.Description}"
|
||||
: $"{deviceName}.{method.Name}";
|
||||
yield return displayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建设备实例 → 硬件指纹的反向查找表。
|
||||
/// </summary>
|
||||
private Dictionary<object, string> BuildInstanceToFingerprint()
|
||||
{
|
||||
var map = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
|
||||
if (_globalInfo?.HardwarePool == null) return map;
|
||||
|
||||
foreach (var poolEntry in _globalInfo.HardwarePool)
|
||||
{
|
||||
var lazy = poolEntry.Value;
|
||||
if (lazy.IsValueCreated && lazy.Value != null)
|
||||
map[lazy.Value] = poolEntry.Key;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发现当前已加载 DBC 中的 CAN 信号(以 ConfigurationList 为白名单)。
|
||||
/// 返回 (DisplayName, Fingerprint, MethodName) 列表。
|
||||
/// </summary>
|
||||
private IEnumerable<(string DisplayName, string Fingerprint, string MethodName)> DiscoverCanSignals()
|
||||
{
|
||||
if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) yield break;
|
||||
if (_systemConfig?.ConfigurationList == null) yield break;
|
||||
|
||||
var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
|
||||
foreach (var cfg in _systemConfig.ConfigurationList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cfg.SignalName)) continue;
|
||||
if (cfg.Channel < 0 || cfg.Channel >= msgDb.Count) continue;
|
||||
|
||||
string fingerprint = CANSignalBroadcaster.BuildFingerprint((uint)cfg.Channel);
|
||||
string methodName = CANSignalBroadcaster.BuildMethodName((uint)cfg.MessageID, cfg.SignalName);
|
||||
string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
|
||||
|
||||
if (ExistsInDbc(methodName, msgDb[cfg.Channel]))
|
||||
yield return (displayName, fingerprint, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定的 MethodName 是否存在于该通道的 DBC 消息数据库中。
|
||||
/// </summary>
|
||||
private static bool ExistsInDbc(string methodName, List<_Msg_> messages)
|
||||
{
|
||||
if (string.IsNullOrEmpty(methodName)) return false;
|
||||
int dotIndex = methodName.IndexOf('.');
|
||||
if (dotIndex <= 0 || dotIndex >= methodName.Length - 1) return false;
|
||||
|
||||
if (!uint.TryParse(methodName.Substring(0, dotIndex), System.Globalization.NumberStyles.HexNumber, null, out uint msgId))
|
||||
return false;
|
||||
string signalName = methodName.Substring(dotIndex + 1);
|
||||
|
||||
return messages.Any(m => m.msg_id == msgId && m.signal_Name != null && m.signal_Name.Contains(signalName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保 ValueLimitList 中存在指定信号;不存在则添加默认值。
|
||||
/// </summary>
|
||||
private void EnsureValueLimit(string signalName)
|
||||
{
|
||||
if (ValueLimitList.Any(x => x.SignalName == signalName)) return;
|
||||
|
||||
ValueLimitList.Add(new ValueLimitVM
|
||||
{
|
||||
SignalName = SelectCurve,
|
||||
SignalName = signalName,
|
||||
Upper = 9999,
|
||||
Lower = -9999,
|
||||
UpperExtreme = 9999,
|
||||
@@ -126,6 +238,34 @@ namespace MonitorModule.ViewModels.Dialogs
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 ValueLimitList 和 DeviceSingleList 中移除指定信号。
|
||||
/// </summary>
|
||||
private void RemoveValueLimit(string signalName)
|
||||
{
|
||||
var item = ValueLimitList.FirstOrDefault(x => x.SignalName == signalName);
|
||||
if (item != null) ValueLimitList.Remove(item);
|
||||
|
||||
//if (DeviceSingleList.Contains(signalName))
|
||||
// DeviceSingleList.Remove(signalName);
|
||||
}
|
||||
|
||||
//private void SelectSignal()
|
||||
//{
|
||||
// if (ValueLimitList.Where(x => x.SignalName == SelectCurve).Count() > 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// ValueLimitList.Add(new ValueLimitVM
|
||||
// {
|
||||
// SignalName = SelectCurve,
|
||||
// Upper = 9999,
|
||||
// Lower = -9999,
|
||||
// UpperExtreme = 9999,
|
||||
// LowerExtreme = -9999,
|
||||
// });
|
||||
//}
|
||||
|
||||
private void OnClose()
|
||||
{
|
||||
RequestClose.Invoke();
|
||||
@@ -146,7 +286,96 @@ namespace MonitorModule.ViewModels.Dialogs
|
||||
if (parameters != null && parameters.ContainsKey("Scope")) // 修复:Prism 中应使用 ContainsKey
|
||||
{
|
||||
_scope = parameters.GetValue<IScopedProvider>("Scope");
|
||||
_systemConfig= _scope.Resolve<SystemConfig>();
|
||||
_systemConfig = _scope.Resolve<SystemConfig>();
|
||||
_deviceManager = _scope.Resolve<DeviceManager>();
|
||||
|
||||
// 3. 订阅 DBC 加载/卸载事件
|
||||
_dbcLoadedToken = _eventAggregator.GetEvent<DBCLoadedEvent>()
|
||||
.Subscribe(OnDbcLoaded, ThreadOption.UIThread);
|
||||
_dbcUnloadedToken = _eventAggregator.GetEvent<DBCUnloadedEvent>()
|
||||
.Subscribe(OnDbcUnloaded, ThreadOption.UIThread);
|
||||
|
||||
// 4. 初始化数据
|
||||
InitData();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
base.OnDialogClosed();
|
||||
|
||||
// 取消 DBC 事件订阅,避免内存泄漏
|
||||
if (_dbcLoadedToken != null)
|
||||
_eventAggregator.GetEvent<DBCLoadedEvent>().Unsubscribe(_dbcLoadedToken);
|
||||
if (_dbcUnloadedToken != null)
|
||||
_eventAggregator.GetEvent<DBCUnloadedEvent>().Unsubscribe(_dbcUnloadedToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DBCLoadedEvent 回调:清理该通道中不存在于 DBC 的 CAN 信号限制项。
|
||||
/// </summary>
|
||||
private void OnDbcLoaded(DBCLoadedArgs args)
|
||||
{
|
||||
if (args.Scope != _systemConfig?.Title) return;
|
||||
if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) return;
|
||||
|
||||
int channel = (int)args.Channel;
|
||||
var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
|
||||
if (channel < 0 || channel >= msgDb.Count) return;
|
||||
|
||||
string fingerprint = CANSignalBroadcaster.BuildFingerprint(args.Channel);
|
||||
|
||||
// 1. 移除该通道中已失效的 CAN 信号限制项
|
||||
var staleSignals = _canSignalMap
|
||||
.Where(kvp => kvp.Value.Fingerprint == fingerprint &&
|
||||
!ExistsInDbc(kvp.Value.MethodName, msgDb[channel]))
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var signalName in staleSignals)
|
||||
{
|
||||
RemoveValueLimit(signalName);
|
||||
_canSignalMap.Remove(signalName);
|
||||
}
|
||||
|
||||
// 2. 根据 ConfigurationList 补充新加载的有效信号
|
||||
if (_systemConfig?.ConfigurationList != null)
|
||||
{
|
||||
foreach (var cfg in _systemConfig.ConfigurationList.Where(c => c.Channel == channel))
|
||||
{
|
||||
if (string.IsNullOrEmpty(cfg.SignalName)) continue;
|
||||
|
||||
string methodName = CANSignalBroadcaster.BuildMethodName((uint)cfg.MessageID, cfg.SignalName);
|
||||
string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
|
||||
|
||||
if (ExistsInDbc(methodName, msgDb[channel]))
|
||||
{
|
||||
_canSignalMap[displayName] = (fingerprint, methodName);
|
||||
//if (!DeviceSingleList.Contains(displayName))
|
||||
// DeviceSingleList.Add(displayName);
|
||||
EnsureValueLimit(displayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DBCUnloadedEvent 回调:删除该通道所有 CAN 信号的限制项。
|
||||
/// </summary>
|
||||
private void OnDbcUnloaded(DBCUnloadedArgs args)
|
||||
{
|
||||
if (args.Scope != _systemConfig?.Title) return;
|
||||
|
||||
string fingerprint = CANSignalBroadcaster.BuildFingerprint(args.Channel);
|
||||
var toRemove = _canSignalMap
|
||||
.Where(kvp => kvp.Value.Fingerprint == fingerprint)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var signalName in toRemove)
|
||||
{
|
||||
RemoveValueLimit(signalName);
|
||||
_canSignalMap.Remove(signalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +109,12 @@ namespace MonitorModule.ViewModels
|
||||
};
|
||||
private int _colorIndex;
|
||||
|
||||
/// <summary>广播器(由 UIShare 层注入,每 Scope 一个实例)</summary>
|
||||
/// <summary>设备数据广播器(全局单例)</summary>
|
||||
private HardwareDataBroadcaster? _broadcaster;
|
||||
|
||||
/// <summary>CAN 信号广播器(全局单例)</summary>
|
||||
private CANSignalBroadcaster? _canSignalBroadcaster;
|
||||
|
||||
/// <summary>用于 OxyPlot X 轴相对秒数</summary>
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
|
||||
@@ -318,6 +321,7 @@ namespace MonitorModule.ViewModels
|
||||
_systemConfig = _scope.Resolve<SystemConfig>();
|
||||
RaisePropertyChanged(nameof(Channels));
|
||||
_broadcaster = _scope.Resolve<HardwareDataBroadcaster>();
|
||||
_canSignalBroadcaster = _scope.Resolve<CANSignalBroadcaster>();
|
||||
IsInitiated = true;
|
||||
|
||||
if (_monitorCTS == null) _monitorCTS = new CancellationTokenSource();
|
||||
@@ -334,6 +338,8 @@ namespace MonitorModule.ViewModels
|
||||
// 4. 启动广播器(Discover + Start)
|
||||
_broadcaster.Discover();
|
||||
_broadcaster.Start();
|
||||
_canSignalBroadcaster.Discover();
|
||||
_canSignalBroadcaster.Start();
|
||||
|
||||
// 5. 订阅 HardwareDataReportedEvent(UI 线程回调)
|
||||
_subscriptionToken = _eventAggregator.GetEvent<HardwareDataReportedEvent>()
|
||||
|
||||
@@ -73,15 +73,15 @@
|
||||
Width="auto" />
|
||||
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.ContextMenu>
|
||||
<!--<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="删除"
|
||||
Foreground="Red"
|
||||
Command="{Binding DeleteCommand}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
</DataGrid.ContextMenu>-->
|
||||
</DataGrid>
|
||||
<StackPanel Grid.Row="1"
|
||||
<!--<StackPanel Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0,10,0,0">
|
||||
@@ -95,7 +95,7 @@
|
||||
<Button Content="添加"
|
||||
Margin="10,0,10,0"
|
||||
Command="{Binding SelectSignalCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>-->
|
||||
<StackPanel Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用
|
||||
|
||||
@@ -179,6 +179,9 @@ namespace UIShare.GlobalVariable
|
||||
Value = value,
|
||||
Time = now
|
||||
});
|
||||
|
||||
// 同步检查该作用域 ValueLimitList 是否超限
|
||||
ValueLimitAlarmHelper.CheckAlarm(scope, entry.Fingerprint, entry.MethodName, value, _globalInfo);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
||||
56
UIShare/GlobalVariable/ValueLimitAlarmHelper.cs
Normal file
56
UIShare/GlobalVariable/ValueLimitAlarmHelper.cs
Normal 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.未报警;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ namespace UIShare.UIViewModel
|
||||
public class ValueLimitVM : BindableBase
|
||||
{
|
||||
private string _signalName = string.Empty;
|
||||
private string _fingerprint = string.Empty;
|
||||
private string _methodName = string.Empty;
|
||||
private double _upper;
|
||||
private double _lower;
|
||||
private double _upperExtreme;
|
||||
@@ -14,7 +16,7 @@ namespace UIShare.UIViewModel
|
||||
private AlarmStatus _alarmStatus = AlarmStatus.未报警;
|
||||
|
||||
/// <summary>
|
||||
/// 信号名
|
||||
/// 信号名(显示用)
|
||||
/// </summary>
|
||||
public string SignalName
|
||||
{
|
||||
@@ -22,6 +24,24 @@ namespace UIShare.UIViewModel
|
||||
set => SetProperty(ref _signalName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 硬件指纹(物理设备唯一标识,用于超限匹配)
|
||||
/// </summary>
|
||||
public string Fingerprint
|
||||
{
|
||||
get => _fingerprint;
|
||||
set => SetProperty(ref _fingerprint, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 方法名(唯一标识,用于超限匹配)
|
||||
/// </summary>
|
||||
public string MethodName
|
||||
{
|
||||
get => _methodName;
|
||||
set => SetProperty(ref _methodName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上限
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user