diff --git a/ADP/App.xaml.cs b/ADP/App.xaml.cs index 6700c36..e41103a 100644 --- a/ADP/App.xaml.cs +++ b/ADP/App.xaml.cs @@ -85,6 +85,7 @@ namespace ADP containerRegistry.RegisterScoped(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); //注册AutoMapper var config = new MapperConfiguration( cfg => cfg.AddProfile(), diff --git a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs index 8de91b4..86dbc84 100644 --- a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs +++ b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs @@ -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 _DeviceSingleList = new(); + //private ObservableCollection _DeviceSingleList = new(); - public ObservableCollection DeviceSingleList - { - get => _DeviceSingleList; - set => SetProperty(ref _DeviceSingleList, value); - } - private string _SelectCurve; + //public ObservableCollection 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; + + /// SignalName → CAN信号标识,用于DBC加载/卸载时清理 + private readonly Dictionary _canSignalMap = new(); + + private SubscriptionToken? _dbcLoadedToken; + private SubscriptionToken? _dbcUnloadedToken; + public ValueLimitViewModel(IContainerProvider containerProvider) : base(containerProvider) { _globalInfo = containerProvider.Resolve(); _eventAggregator = containerProvider.Resolve(); 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(); + //DeviceSingleList = new ObservableCollection(); + _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() + /// + /// 发现当前作用域所有可监测的设备方法信号(与 MonitorViewModel 逻辑一致)。 + /// 返回 DisplayName 列表。 + /// + private IEnumerable 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() == null) return false; + if (m.ReturnType != typeof(Task)) 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(); + string displayName = !string.IsNullOrEmpty(attr?.Description) + ? $"{deviceName}.{attr.Description}" + : $"{deviceName}.{method.Name}"; + yield return displayName; + } } + } + + /// + /// 构建设备实例 → 硬件指纹的反向查找表。 + /// + private Dictionary BuildInstanceToFingerprint() + { + var map = new Dictionary(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; + } + + /// + /// 发现当前已加载 DBC 中的 CAN 信号(以 ConfigurationList 为白名单)。 + /// 返回 (DisplayName, Fingerprint, MethodName) 列表。 + /// + 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); + } + } + + /// + /// 判断指定的 MethodName 是否存在于该通道的 DBC 消息数据库中。 + /// + 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)); + } + + /// + /// 确保 ValueLimitList 中存在指定信号;不存在则添加默认值。 + /// + 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 }); } + /// + /// 从 ValueLimitList 和 DeviceSingleList 中移除指定信号。 + /// + 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("Scope"); - _systemConfig= _scope.Resolve(); + _systemConfig = _scope.Resolve(); + _deviceManager = _scope.Resolve(); + + // 3. 订阅 DBC 加载/卸载事件 + _dbcLoadedToken = _eventAggregator.GetEvent() + .Subscribe(OnDbcLoaded, ThreadOption.UIThread); + _dbcUnloadedToken = _eventAggregator.GetEvent() + .Subscribe(OnDbcUnloaded, ThreadOption.UIThread); + + // 4. 初始化数据 + InitData(); + } + } + + public override void OnDialogClosed() + { + base.OnDialogClosed(); + + // 取消 DBC 事件订阅,避免内存泄漏 + if (_dbcLoadedToken != null) + _eventAggregator.GetEvent().Unsubscribe(_dbcLoadedToken); + if (_dbcUnloadedToken != null) + _eventAggregator.GetEvent().Unsubscribe(_dbcUnloadedToken); + } + + /// + /// DBCLoadedEvent 回调:清理该通道中不存在于 DBC 的 CAN 信号限制项。 + /// + 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); + } + } + } + } + + /// + /// DBCUnloadedEvent 回调:删除该通道所有 CAN 信号的限制项。 + /// + 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); } } } diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs index 44b685d..59eb4b0 100644 --- a/MonitorModule/ViewModels/MonitorViewModel.cs +++ b/MonitorModule/ViewModels/MonitorViewModel.cs @@ -109,9 +109,12 @@ namespace MonitorModule.ViewModels }; private int _colorIndex; - /// 广播器(由 UIShare 层注入,每 Scope 一个实例) + /// 设备数据广播器(全局单例) private HardwareDataBroadcaster? _broadcaster; + /// CAN 信号广播器(全局单例) + private CANSignalBroadcaster? _canSignalBroadcaster; + /// 用于 OxyPlot X 轴相对秒数 private readonly Stopwatch _stopwatch = new(); @@ -318,6 +321,7 @@ namespace MonitorModule.ViewModels _systemConfig = _scope.Resolve(); RaisePropertyChanged(nameof(Channels)); _broadcaster = _scope.Resolve(); + _canSignalBroadcaster = _scope.Resolve(); 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() diff --git a/MonitorModule/Views/Dialogs/ValueLimitView.xaml b/MonitorModule/Views/Dialogs/ValueLimitView.xaml index 3eddfcd..3153065 100644 --- a/MonitorModule/Views/Dialogs/ValueLimitView.xaml +++ b/MonitorModule/Views/Dialogs/ValueLimitView.xaml @@ -73,15 +73,15 @@ Width="auto" /> - + - @@ -95,7 +95,7 @@