From 8e44be48947e82fbd7375843ccef4ce520371f86 Mon Sep 17 00:00:00 2001 From: hsc Date: Wed, 15 Jul 2026 10:11:17 +0800 Subject: [PATCH] =?UTF-8?q?CAN=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectCANSingalSettingViewModel.cs | 1 + CANModule/ViewModels/ZLGCANFDViewModel.cs | 4 +- .../ViewModels/Dialogs/ValueLimitViewModel.cs | 30 ++++---- MonitorModule/ViewModels/MonitorViewModel.cs | 6 +- .../GlobalVariable/CANSignalBroadcaster.cs | 9 +-- UIShare/GlobalVariable/DeviceManager.cs | 14 ++++ UIShare/UIViewModel/CANSignalConfigVM.cs | 1 + UIShare/UIViewModel/ValueLimitVM.cs | 9 +++ ZLGUSBCANFD/USBCANFD.cs | 72 ++++++++++++++++--- ZLGUSBCANFD/ZLGUSBCANFD.csproj | 1 + 10 files changed, 117 insertions(+), 30 deletions(-) diff --git a/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs b/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs index d5eff53..5fc9ae3 100644 --- a/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs +++ b/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs @@ -204,6 +204,7 @@ namespace CANModule.ViewModels { CollectionID = Guid.NewGuid(), Channel = MessageChannel, + MethodName = CANSignalBroadcaster.BuildMethodName((uint)decimalValue, SelectedSignal), MessageName = ExtractMessageName(SelectedMessage), SignalName = SelectedSignal, MessageID = decimalValue, diff --git a/CANModule/ViewModels/ZLGCANFDViewModel.cs b/CANModule/ViewModels/ZLGCANFDViewModel.cs index 130f6a6..1ebc85e 100644 --- a/CANModule/ViewModels/ZLGCANFDViewModel.cs +++ b/CANModule/ViewModels/ZLGCANFDViewModel.cs @@ -452,7 +452,7 @@ namespace CANModule.ViewModels if (targetMsg != null) { - _canfd.设置报文((uint)MessageSendChannel, targetMsg.msg_id.ToString(), SelectedSignal, SValue, sendPeriod); + _canfd.设置报文((uint)MessageSendChannel, targetMsg.msg_id.ToString(), SelectedSignal, SValue, sendPeriod, IsDirectlySend); } } } @@ -603,7 +603,7 @@ namespace CANModule.ViewModels private void OnDbcMessageDecoded(uint channel, ZDBC.DBCMessage msg) { // DBC 解码后的报文回调 - Application.Current.Dispatcher.Invoke(() => + Application.Current.Dispatcher.BeginInvoke(() => { var msgName = System.Text.Encoding.Default.GetString(msg.strName).TrimEnd('\0'); var find = CanMessageList.FirstOrDefault(s => s.报文ID == (int)msg.nID&& s.通道==channel); diff --git a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs index 86dbc84..7a40ecd 100644 --- a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs +++ b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs @@ -111,10 +111,10 @@ namespace MonitorModule.ViewModels.Dialogs } // 2. 发现设备方法信号并加入 ValueLimitList / DeviceSingleList - foreach (var signal in DiscoverDeviceSignals()) + foreach (var (displayName, fingerprint, methodName) in DiscoverDeviceSignals()) { - //DeviceSingleList.Add(signal); - EnsureValueLimit(signal); + //DeviceSingleList.Add(displayName); + EnsureValueLimit(fingerprint, displayName, displayName, methodName); } // 3. 发现 CAN 信号并加入 ValueLimitList / DeviceSingleList @@ -122,15 +122,15 @@ namespace MonitorModule.ViewModels.Dialogs { //DeviceSingleList.Add(displayName); _canSignalMap[displayName] = (fingerprint, methodName); - EnsureValueLimit(displayName); + EnsureValueLimit(fingerprint, displayName, displayName, methodName); } } /// /// 发现当前作用域所有可监测的设备方法信号(与 MonitorViewModel 逻辑一致)。 - /// 返回 DisplayName 列表。 + /// 返回 (DisplayName, Fingerprint, MethodName) 列表。 /// - private IEnumerable DiscoverDeviceSignals() + private IEnumerable<(string DisplayName, string Fingerprint, string MethodName)> DiscoverDeviceSignals() { if (_deviceManager?.DeviceMap == null) yield break; @@ -159,7 +159,7 @@ namespace MonitorModule.ViewModels.Dialogs string displayName = !string.IsNullOrEmpty(attr?.Description) ? $"{deviceName}.{attr.Description}" : $"{deviceName}.{method.Name}"; - yield return displayName; + yield return (displayName, fingerprint, method.Name); } } } @@ -191,12 +191,13 @@ namespace MonitorModule.ViewModels.Dialogs if (_systemConfig?.ConfigurationList == null) yield break; var msgDb = _deviceManager.CANFD.DBCParser.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((uint)cfg.Channel); + 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); @@ -224,13 +225,16 @@ namespace MonitorModule.ViewModels.Dialogs /// /// 确保 ValueLimitList 中存在指定信号;不存在则添加默认值。 /// - private void EnsureValueLimit(string signalName) + 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, @@ -323,7 +327,8 @@ namespace MonitorModule.ViewModels.Dialogs var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase; if (channel < 0 || channel >= msgDb.Count) return; - string fingerprint = CANSignalBroadcaster.BuildFingerprint(args.Channel); + string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint(); + string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, args.Channel); // 1. 移除该通道中已失效的 CAN 信号限制项 var staleSignals = _canSignalMap @@ -353,7 +358,7 @@ namespace MonitorModule.ViewModels.Dialogs _canSignalMap[displayName] = (fingerprint, methodName); //if (!DeviceSingleList.Contains(displayName)) // DeviceSingleList.Add(displayName); - EnsureValueLimit(displayName); + EnsureValueLimit(fingerprint, displayName, displayName, methodName); } } } @@ -366,7 +371,8 @@ namespace MonitorModule.ViewModels.Dialogs { if (args.Scope != _systemConfig?.Title) return; - string fingerprint = CANSignalBroadcaster.BuildFingerprint(args.Channel); + string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint(); + string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, args.Channel); var toRemove = _canSignalMap .Where(kvp => kvp.Value.Fingerprint == fingerprint) .Select(kvp => kvp.Key) diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs index 59eb4b0..f2d1bee 100644 --- a/MonitorModule/ViewModels/MonitorViewModel.cs +++ b/MonitorModule/ViewModels/MonitorViewModel.cs @@ -556,7 +556,8 @@ namespace MonitorModule.ViewModels { if (_systemConfig?.ConfigurationList == null) return; - string fingerprint = CANSignalBroadcaster.BuildFingerprint(channel); + string canDeviceFingerprint = _deviceManager?.GetCanDeviceFingerprint() ?? string.Empty; + string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, channel); var configs = _systemConfig.ConfigurationList.Where(c => c.Channel == (int)channel).ToList(); bool changed = false; @@ -650,7 +651,8 @@ namespace MonitorModule.ViewModels { if (args.Scope != TestStatus) return; - string fingerprint = CANSignalBroadcaster.BuildFingerprint(args.Channel); + string canDeviceFingerprint = _deviceManager?.GetCanDeviceFingerprint() ?? string.Empty; + string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, args.Channel); // 从 AvailableMethods 中移除 var toRemoveMethods = AvailableMethods.Where(m => m.Fingerprint == fingerprint).ToList(); diff --git a/UIShare/GlobalVariable/CANSignalBroadcaster.cs b/UIShare/GlobalVariable/CANSignalBroadcaster.cs index eb47959..d6e4049 100644 --- a/UIShare/GlobalVariable/CANSignalBroadcaster.cs +++ b/UIShare/GlobalVariable/CANSignalBroadcaster.cs @@ -69,7 +69,7 @@ namespace UIShare.GlobalVariable { if (_disposed) return; - string signalFingerprint = BuildFingerprint(channel); + string signalFingerprint = BuildFingerprint(canFingerprint, channel); var now = DateTime.Now; // 获取引用该 CAN 设备的所有作用域 @@ -133,11 +133,12 @@ namespace UIShare.GlobalVariable } /// - /// 生成 CAN 信号的 Fingerprint 格式:"CAN:{Channel}" + /// 生成 CAN 信号的 Fingerprint 格式:"{canDeviceFingerprint}:{channel}" + /// canDeviceFingerprint 来自 DeviceManager.ExtractHardwareFingerprint,如 "CAN:0" /// - public static string BuildFingerprint(uint channel) + public static string BuildFingerprint(string canDeviceFingerprint, uint channel) { - return $"CAN:{channel}"; + return $"{canDeviceFingerprint}:{channel}"; } public void Dispose() diff --git a/UIShare/GlobalVariable/DeviceManager.cs b/UIShare/GlobalVariable/DeviceManager.cs index e1e9bfe..676e047 100644 --- a/UIShare/GlobalVariable/DeviceManager.cs +++ b/UIShare/GlobalVariable/DeviceManager.cs @@ -71,6 +71,20 @@ namespace UIShare.GlobalVariable return string.Empty; } + + /// + /// 获取当前作用域配置的 CAN 设备硬件指纹(与 GlobalInfo.CanPool 的 Key 一致)。 + /// 如 "CAN:0",其中 0 是 CANConfig.DeviceIndex。 + /// + public string GetCanDeviceFingerprint() + { + if (_systemConfig?.DeviceList == null) return string.Empty; + var canConfig = _systemConfig.DeviceList.FirstOrDefault(d => + d != null && d.IsEnabled && + string.Equals(d.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase)); + return canConfig != null ? ExtractHardwareFingerprint(canConfig) : string.Empty; + } + private void InitDevices() { DeviceMap = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/UIShare/UIViewModel/CANSignalConfigVM.cs b/UIShare/UIViewModel/CANSignalConfigVM.cs index 2fa8093..59c50a5 100644 --- a/UIShare/UIViewModel/CANSignalConfigVM.cs +++ b/UIShare/UIViewModel/CANSignalConfigVM.cs @@ -10,5 +10,6 @@ namespace UIShare.UIViewModel public string SignalName { get; set; } public int CollectionInterval { get; set; } public Guid CollectionID { get; set; } + public string MethodName {get;set;} } } diff --git a/UIShare/UIViewModel/ValueLimitVM.cs b/UIShare/UIViewModel/ValueLimitVM.cs index a3df4d9..bf3c64d 100644 --- a/UIShare/UIViewModel/ValueLimitVM.cs +++ b/UIShare/UIViewModel/ValueLimitVM.cs @@ -8,6 +8,7 @@ namespace UIShare.UIViewModel private string _signalName = string.Empty; private string _fingerprint = string.Empty; private string _methodName = string.Empty; + private string _displayName = string.Empty; private double _upper; private double _lower; private double _upperExtreme; @@ -22,6 +23,14 @@ namespace UIShare.UIViewModel { get => _signalName; set => SetProperty(ref _signalName, value); + } + /// + /// 信号名(显示用) + /// + public string DisplayName + { + get => _displayName; + set => SetProperty(ref _displayName, value); } /// diff --git a/ZLGUSBCANFD/USBCANFD.cs b/ZLGUSBCANFD/USBCANFD.cs index c038b91..202b6f5 100644 --- a/ZLGUSBCANFD/USBCANFD.cs +++ b/ZLGUSBCANFD/USBCANFD.cs @@ -1,4 +1,5 @@ using Common.Attributes; +using Logger; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -29,6 +30,10 @@ namespace ZLGUSBCANFD // DBC 循环发送任务管理:Key = (通道号, 帧ID) private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), CancellationTokenSource> _cyclicSenders = new ConcurrentDictionary<(uint, uint), CancellationTokenSource>(); + // 信号值持久化覆盖表:Key = (通道号, 帧ID),Value = 信号名 → 物理值 + // 每次 设置报文 会累积写入此表,发送时以 DBC 初始值为底再叠加此表覆盖值 + private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), Dictionary> _signalOverrides = new ConcurrentDictionary<(uint, uint), Dictionary>(); + // 动态硬件参数 private readonly uint _deviceType; // 76: USBCANFD-400U private readonly uint _deviceIndex; // 设备索引 @@ -371,14 +376,17 @@ namespace ZLGUSBCANFD } catch (Exception ex) { - // 循环发送出错时退出,避免刷屏 - Console.WriteLine($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}"); + Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}"); break; } } - _cyclicSenders.TryRemove((通道号, 帧ID), out var removedCts); - removedCts?.Dispose(); + // 清理:仅当字典中存的仍然是本任务创建的 CTS 时才移除,避免误删新任务的 CTS + if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current, cts)) + { + _cyclicSenders.TryRemove((通道号, 帧ID), out _); + } + cts.Dispose(); }, cts.Token); return true; @@ -392,7 +400,7 @@ namespace ZLGUSBCANFD if (_cyclicSenders.TryRemove((通道号, 帧ID), out var cts)) { cts.Cancel(); - cts.Dispose(); + // 不在这里 Dispose,由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token } } @@ -409,6 +417,31 @@ namespace ZLGUSBCANFD _cyclicSenders.Clear(); } + /// + /// 清除指定通道、指定帧 ID 的信号覆盖值,恢复为 DBC 初始值。 + /// + public virtual void 清除信号覆盖(uint 通道号, uint 帧ID) + { + if (_signalOverrides.TryRemove((通道号, 帧ID), out var dict)) + { + lock (dict) dict.Clear(); + } + } + + /// + /// 清除指定通道所有帧的信号覆盖值。 + /// + public virtual void 清除通道信号覆盖(uint 通道号) + { + foreach (var key in _signalOverrides.Keys) + { + if (key.通道号 == 通道号 && _signalOverrides.TryRemove(key, out var dict)) + { + lock (dict) dict.Clear(); + } + } + } + /// @@ -536,7 +569,7 @@ namespace ZLGUSBCANFD /// 要写入的实际物理数值 /// 0 = 单次发送;>0 = 周期循环发送(单位毫秒) /// 操作是否成功 - public virtual bool 设置报文(uint 通道号, string 帧IDStr, string 信号名称, double 物理值, int 循环发送间隔毫秒 = 0) + public virtual bool 设置报文(uint 通道号, string 帧IDStr, string 信号名称, double 物理值, int 循环发送间隔毫秒 = 0, bool 直接发送=false) { if (!TryParseFrameId(帧IDStr, out uint 帧ID)) { @@ -548,12 +581,18 @@ namespace ZLGUSBCANFD if (!_isDbcLoadedArray[通道号]) return false; if (string.IsNullOrWhiteSpace(信号名称)) return false; - var merged = 构建报文发送字典(通道号, 帧ID, new Dictionary(StringComparer.OrdinalIgnoreCase) + // 将本次设置的信号值写入持久化覆盖表(累积,不会丢失之前设置的值) + var overrides = _signalOverrides.GetOrAdd((通道号, 帧ID), _ => new Dictionary(StringComparer.OrdinalIgnoreCase)); + lock (overrides) { - { 信号名称, 物理值 } - }); + overrides[信号名称] = 物理值; + } - return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒); + // 构建发送字典:DBC 初始值 + 持久化覆盖表(已包含本次设置) + var merged = 构建报文发送字典(通道号, 帧ID); + + if (直接发送) return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒); + else return true; } /// @@ -753,6 +792,7 @@ namespace ZLGUSBCANFD var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage)); var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + // 1. DBC 初始值打底 for (int i = 0; i < msg.nSignalCount; i++) { var signal = msg.vSignals[i]; @@ -763,6 +803,17 @@ namespace ZLGUSBCANFD } } + // 2. 叠加持久化覆盖表(之前 设置报文 累积的值) + if (_signalOverrides.TryGetValue((通道号, 帧ID), out var persisted)) + { + lock (persisted) + { + foreach (var kvp in persisted) + dict[kvp.Key] = kvp.Value; + } + } + + // 3. 叠加本次调用传入的额外覆盖(优先级最高) if (额外覆盖 != null) { foreach (var kvp in 额外覆盖) @@ -776,6 +827,7 @@ namespace ZLGUSBCANFD } finally { + Marshal.FreeHGlobal(ptrDbcMsg); } } diff --git a/ZLGUSBCANFD/ZLGUSBCANFD.csproj b/ZLGUSBCANFD/ZLGUSBCANFD.csproj index 0eabc2b..2e99c7b 100644 --- a/ZLGUSBCANFD/ZLGUSBCANFD.csproj +++ b/ZLGUSBCANFD/ZLGUSBCANFD.csproj @@ -8,6 +8,7 @@ +