399 lines
16 KiB
C#
399 lines
16 KiB
C#
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.Windows.Input;
|
||
using TSMasterCAN;
|
||
using UIShare.GlobalVariable;
|
||
using UIShare.PubEvent;
|
||
using UIShare.UIViewModel;
|
||
using UIShare.ViewModelBase;
|
||
|
||
namespace MonitorModule.ViewModels.Dialogs
|
||
{
|
||
public class ValueLimitViewModel : DialogViewModelBase
|
||
{
|
||
#region 属性
|
||
private string _Title = "报警设置";
|
||
|
||
public string Title
|
||
{
|
||
get { return _Title; }
|
||
set { SetProperty(ref _Title, value); }
|
||
}
|
||
|
||
private ValueLimitVM _SelectValueLimitVM;
|
||
|
||
public ValueLimitVM SelectValueLimitVM
|
||
{
|
||
get => _SelectValueLimitVM;
|
||
set => SetProperty(ref _SelectValueLimitVM, value);
|
||
}
|
||
private ObservableCollection<ValueLimitVM> _ValueLimitList;
|
||
public ObservableCollection<ValueLimitVM> ValueLimitList
|
||
{
|
||
get => _ValueLimitList;
|
||
set => SetProperty(ref _ValueLimitList, value);
|
||
}
|
||
//private ObservableCollection<string> _DeviceSingleList = new();
|
||
|
||
//public ObservableCollection<string> DeviceSingleList
|
||
//{
|
||
// get => _DeviceSingleList;
|
||
// set => SetProperty(ref _DeviceSingleList, value);
|
||
//}
|
||
//private string _SelectCurve;
|
||
|
||
//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 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);
|
||
DeleteCommand = new DelegateCommand(Deletel);
|
||
}
|
||
#region 命令处理
|
||
private void Deletel()
|
||
{
|
||
if (SelectValueLimitVM == null)
|
||
{
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
ValueLimitList.Remove(SelectValueLimitVM);
|
||
SelectValueLimitVM = null;
|
||
}
|
||
}
|
||
|
||
|
||
private void InitData()
|
||
{
|
||
ValueLimitList = new ObservableCollection<ValueLimitVM>();
|
||
//DeviceSingleList = new ObservableCollection<string>();
|
||
_canSignalMap.Clear();
|
||
|
||
// 1. 加载已保存的 ValueLimitList(保留用户设置)
|
||
if (_systemConfig?.ValueLimitList != null)
|
||
{
|
||
foreach (var item in _systemConfig.ValueLimitList)
|
||
ValueLimitList.Add(item);
|
||
}
|
||
|
||
// 2. 发现设备方法信号并加入 ValueLimitList / DeviceSingleList
|
||
foreach (var (displayName, fingerprint, methodName) in DiscoverDeviceSignals())
|
||
{
|
||
//DeviceSingleList.Add(displayName);
|
||
EnsureValueLimit(fingerprint, displayName, displayName, methodName);
|
||
}
|
||
|
||
// 3. 发现 CAN 信号并加入 ValueLimitList,同时收集有效信号名
|
||
var validCanSignalNames = new HashSet<string>();
|
||
foreach (var (displayName, fingerprint, methodName) in DiscoverCanSignals())
|
||
{
|
||
_canSignalMap[displayName] = (fingerprint, methodName);
|
||
EnsureValueLimit(fingerprint, displayName, displayName, methodName);
|
||
validCanSignalNames.Add(displayName);
|
||
}
|
||
|
||
// 4. 清理已不存在于 DBC 中的 CAN 信号(Fingerprint 以 "CAN" 开头且不在有效集合中)
|
||
var staleCanSignals = ValueLimitList
|
||
.Where(x => !string.IsNullOrEmpty(x.Fingerprint) &&
|
||
x.Fingerprint.StartsWith("CAN") &&
|
||
!validCanSignalNames.Contains(x.SignalName))
|
||
.ToList();
|
||
foreach (var item in staleCanSignals)
|
||
ValueLimitList.Remove(item);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发现当前作用域所有可监测的设备方法信号(与 MonitorViewModel 逻辑一致)。
|
||
/// 返回 (DisplayName, Fingerprint, MethodName) 列表。
|
||
/// </summary>
|
||
private IEnumerable<(string DisplayName, string Fingerprint, string MethodName)> DiscoverDeviceSignals()
|
||
{
|
||
if (_deviceManager?.DeviceMap == null) yield break;
|
||
|
||
var instanceToFp = BuildInstanceToFingerprint();
|
||
foreach (var kvp in _deviceManager.DeviceMap)
|
||
{
|
||
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, fingerprint, method.Name);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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 == null) yield break;
|
||
if (_systemConfig?.ConfigurationList == null) yield break;
|
||
|
||
var msgDb = DBCParse.MsgDatabase;
|
||
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
|
||
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(canDeviceFingerprint, (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 fingerprint, string displayName, string signalName, string methodName = "")
|
||
{
|
||
if (ValueLimitList.Any(x => x.SignalName == signalName)) return;
|
||
|
||
ValueLimitList.Add(new ValueLimitVM
|
||
{
|
||
DisplayName = displayName,
|
||
Fingerprint = fingerprint,
|
||
SignalName = signalName,
|
||
MethodName = methodName,
|
||
Upper = 9999,
|
||
Lower = -9999,
|
||
UpperExtreme = 9999,
|
||
LowerExtreme = -9999,
|
||
});
|
||
}
|
||
|
||
/// <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();
|
||
}
|
||
|
||
private void OnSave()
|
||
{
|
||
_systemConfig.ValueLimitList = ValueLimitList;
|
||
ConfigService.Save(_systemConfig);
|
||
}
|
||
#endregion
|
||
public override void OnDialogOpened(IDialogParameters parameters)
|
||
{
|
||
// 1. 优先执行基类的打开逻辑(如果基类有需要初始化的通用事务)
|
||
base.OnDialogOpened(parameters);
|
||
|
||
// 2. 解析参数
|
||
if (parameters != null && parameters.ContainsKey("Scope")) // 修复:Prism 中应使用 ContainsKey
|
||
{
|
||
_scope = parameters.GetValue<IScopedProvider>("Scope");
|
||
_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 == null) return;
|
||
|
||
int channel = (int)args.Channel;
|
||
var msgDb = DBCParse.MsgDatabase;
|
||
if (channel < 0 || channel >= msgDb.Count) return;
|
||
|
||
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
|
||
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, 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(fingerprint, displayName, displayName, methodName);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// DBCUnloadedEvent 回调:删除该通道所有 CAN 信号的限制项。
|
||
/// </summary>
|
||
private void OnDbcUnloaded(DBCUnloadedArgs args)
|
||
{
|
||
if (args.Scope != _systemConfig?.Title) return;
|
||
|
||
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
|
||
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, 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);
|
||
}
|
||
}
|
||
}
|
||
}
|