diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs
index 5fd15d4..44b685d 100644
--- a/MonitorModule/ViewModels/MonitorViewModel.cs
+++ b/MonitorModule/ViewModels/MonitorViewModel.cs
@@ -22,6 +22,7 @@ using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
+using ZLGUSBCANFD;
namespace MonitorModule.ViewModels
@@ -119,6 +120,8 @@ namespace MonitorModule.ViewModels
/// 事件订阅令牌,用于 Dispose 时取消订阅
private SubscriptionToken? _subscriptionToken;
+ private SubscriptionToken? _dbcLoadedToken;
+ private SubscriptionToken? _dbcUnloadedToken;
// ==========================================
// 批量入库核心结构
@@ -166,6 +169,10 @@ namespace MonitorModule.ViewModels
// 取消 EventAggregator 订阅
if (_subscriptionToken != null)
_eventAggregator.GetEvent().Unsubscribe(_subscriptionToken);
+ if (_dbcLoadedToken != null)
+ _eventAggregator.GetEvent().Unsubscribe(_dbcLoadedToken);
+ if (_dbcUnloadedToken != null)
+ _eventAggregator.GetEvent().Unsubscribe(_dbcUnloadedToken);
if (_dbFlushTask != null)
{
@@ -332,6 +339,15 @@ namespace MonitorModule.ViewModels
_subscriptionToken = _eventAggregator.GetEvent()
.Subscribe(OnHardwareDataReceived, ThreadOption.UIThread);
+ // 5b. 订阅 DBC 加载/卸载事件,动态发现或清除 CAN 信号
+ _dbcLoadedToken = _eventAggregator.GetEvent()
+ .Subscribe(OnDbcLoaded, ThreadOption.UIThread);
+ _dbcUnloadedToken = _eventAggregator.GetEvent()
+ .Subscribe(OnDbcUnloaded, ThreadOption.UIThread);
+
+ // 5c. 根据 SystemConfig.ConfigurationList 刷新已加载 DBC 中的 CAN 信号
+ RefreshConfiguredCanSignals();
+
// 6. 启动 OxyPlot 视觉刷新定时器
_stopwatch.Start();
_plotRefreshTimer.Start();
@@ -449,7 +465,6 @@ namespace MonitorModule.ViewModels
private void RestoreChannelsFromConfig()
{
if (_systemConfig?.MonitorChannels == null || _systemConfig.MonitorChannels.Count == 0) return;
- if (AvailableMethods.Count == 0) return;
int restored = 0;
foreach (var config in _systemConfig.MonitorChannels)
@@ -508,6 +523,188 @@ namespace MonitorModule.ViewModels
StatusMessage = $"已从配置自动恢复 {restored} 个监测通道";
}
}
+
+ ///
+ /// 根据 SystemConfig.ConfigurationList 刷新所有已加载 DBC 中的 CAN 信号。
+ /// 只添加 ConfigurationList 中配置且实际存在于 DBC 的信号;不存在则清理。
+ ///
+ private void RefreshConfiguredCanSignals()
+ {
+ if (_systemConfig?.ConfigurationList == null || _systemConfig.ConfigurationList.Count == 0) return;
+ if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) return;
+
+ var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
+ foreach (var channel in _systemConfig.ConfigurationList.Select(c => c.Channel).Distinct())
+ {
+ if (channel < 0 || channel >= msgDb.Count) continue;
+ RefreshCanSignalsForChannel((uint)channel, msgDb[channel]);
+ }
+ }
+
+ ///
+ /// 刷新单个通道的 CAN 信号:以 ConfigurationList 为白名单,对照 DBC 实际存在性。
+ /// - DBC 中存在 → 加入 AvailableMethods;若 MonitorChannels 中也有,则恢复 Channel。
+ /// - DBC 中不存在 → 从 AvailableMethods 移除;若已在 Channels 中,则删除。
+ ///
+ private void RefreshCanSignalsForChannel(uint channel, List<_Msg_> messages)
+ {
+ if (_systemConfig?.ConfigurationList == null) return;
+
+ string fingerprint = CANSignalBroadcaster.BuildFingerprint(channel);
+ var configs = _systemConfig.ConfigurationList.Where(c => c.Channel == (int)channel).ToList();
+ bool changed = false;
+
+ foreach (var cfg in configs)
+ {
+ if (string.IsNullOrEmpty(cfg.SignalName)) continue;
+
+ string methodName = CANSignalBroadcaster.BuildMethodName((uint)cfg.MessageID, cfg.SignalName);
+ bool existsInDbc = messages.Any(m =>
+ m.msg_id == cfg.MessageID &&
+ m.signal_Name != null &&
+ m.signal_Name.Contains(cfg.SignalName));
+
+ if (existsInDbc)
+ {
+ // 加入 AvailableMethods(去重)
+ if (!AvailableMethods.Any(m => m.Fingerprint == fingerprint && m.MethodName == methodName))
+ {
+ string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
+ AvailableMethods.Add(new AvailableMethodItem
+ {
+ DeviceName = $"CAN{channel}",
+ Fingerprint = fingerprint,
+ MethodName = methodName,
+ DisplayName = displayName,
+ MethodInfo = null!,
+ Device = null!
+ });
+ changed = true;
+ }
+
+ // 如果 MonitorChannels 中有持久化记录,自动恢复 Channel
+ var monitorConfig = _systemConfig.MonitorChannels?.FirstOrDefault(m =>
+ m.Fingerprint == fingerprint && m.MethodName == methodName);
+ if (monitorConfig != null && !Channels.Any(c =>
+ c.Fingerprint == fingerprint && c.MethodName == methodName))
+ {
+ AddCanChannel(cfg, fingerprint, methodName, monitorConfig.IsDisplayed);
+ changed = true;
+ }
+ }
+ else
+ {
+ // DBC 中不存在:从 AvailableMethods 移除
+ var methodToRemove = AvailableMethods.FirstOrDefault(m =>
+ m.Fingerprint == fingerprint && m.MethodName == methodName);
+ if (methodToRemove != null)
+ {
+ AvailableMethods.Remove(methodToRemove);
+ changed = true;
+ }
+
+ // 如果该信号已经在 Channels(或 MonitorChannels)中,删除
+ var channelToRemove = Channels.FirstOrDefault(c =>
+ c.Fingerprint == fingerprint && c.MethodName == methodName);
+ if (channelToRemove != null)
+ {
+ if (channelToRemove.Series != null) Plot.Series.Remove(channelToRemove.Series);
+ Channels.Remove(channelToRemove);
+ changed = true;
+ }
+ }
+ }
+
+ if (changed)
+ {
+ Plot.InvalidatePlot(true);
+ StatusMessage = $"CAN{channel} 已刷新配置信号";
+ }
+ }
+
+ ///
+ /// DBCLoadedEvent 回调:DBC 加载后刷新该通道的配置信号。
+ ///
+ private void OnDbcLoaded(DBCLoadedArgs args)
+ {
+ if (args.Scope != TestStatus) 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;
+
+ RefreshCanSignalsForChannel(args.Channel, msgDb[channel]);
+ }
+
+ ///
+ /// DBCUnloadedEvent 回调:移除对应通道的 CAN 信号(AvailableMethods + Channels)。
+ ///
+ private void OnDbcUnloaded(DBCUnloadedArgs args)
+ {
+ if (args.Scope != TestStatus) return;
+
+ string fingerprint = CANSignalBroadcaster.BuildFingerprint(args.Channel);
+
+ // 从 AvailableMethods 中移除
+ var toRemoveMethods = AvailableMethods.Where(m => m.Fingerprint == fingerprint).ToList();
+ foreach (var m in toRemoveMethods)
+ AvailableMethods.Remove(m);
+
+ // 从 Channels 中移除(并清理 Plot)
+ var toRemoveChannels = Channels.Where(c => c.Fingerprint == fingerprint).ToList();
+ foreach (var c in toRemoveChannels)
+ {
+ if (c.Series != null) Plot.Series.Remove(c.Series);
+ Channels.Remove(c);
+ }
+
+ if (toRemoveMethods.Count > 0 || toRemoveChannels.Count > 0)
+ {
+ Plot.InvalidatePlot(true);
+ StatusMessage = $"CAN{args.Channel} DBC 已卸载,移除 {toRemoveMethods.Count} 个信号";
+ }
+ }
+
+ ///
+ /// 将单个 CAN 信号添加为监测通道。
+ ///
+ private void AddCanChannel(CANSignalConfig cfg, string fingerprint, string methodName, bool isDisplayed)
+ {
+ var color = _palette[_colorIndex % _palette.Length];
+ _colorIndex++;
+
+ string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
+ var channel = new MonitorChannelVM
+ {
+ DeviceName = $"CAN{cfg.Channel}",
+ Fingerprint = fingerprint,
+ MethodName = methodName,
+ DisplayName = displayName,
+ Color = color,
+ IsMonitored = true,
+ IsDisplayed = isDisplayed
+ };
+
+ if (channel.IsDisplayed)
+ {
+ channel.Series = new LineSeries
+ {
+ Title = channel.DisplayName,
+ Color = color,
+ StrokeThickness = 1.5
+ };
+ Plot.Series.Add(channel.Series);
+ }
+
+ channel.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(MonitorChannelVM.IsDisplayed))
+ OnChannelDisplayChanged(channel);
+ };
+
+ Channels.Add(channel);
+ }
#endregion
#region 命令处理
diff --git a/UIShare/GlobalVariable/CANSignalBroadcaster.cs b/UIShare/GlobalVariable/CANSignalBroadcaster.cs
new file mode 100644
index 0000000..e207a4c
--- /dev/null
+++ b/UIShare/GlobalVariable/CANSignalBroadcaster.cs
@@ -0,0 +1,114 @@
+using Model.Models;
+using Prism.Events;
+using System;
+using System.Text;
+using UIShare.PubEvent;
+using ZLGUSBCANFD;
+
+namespace UIShare.GlobalVariable
+{
+ ///
+ /// CAN 信号广播器:订阅 ZLGCANFD.OnDbcMessageDecoded 事件,
+ /// 将解码后的 DBC 信号值通过 HardwareDataReportedEvent 广播给监控系统。
+ /// 由 DeviceManager 创建和管理,生命周期与 CAN 设备绑定。
+ ///
+ public class CANSignalBroadcaster : IDisposable
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private readonly string _scopeName;
+ private ZLGCANFD? _canfd;
+ private bool _disposed;
+
+ public CANSignalBroadcaster(IEventAggregator eventAggregator, string scopeName)
+ {
+ _eventAggregator = eventAggregator;
+ _scopeName = scopeName;
+ }
+
+ ///
+ /// 绑定 CANFD 实例并订阅解码事件。
+ ///
+ public void Bind(ZLGCANFD canfd)
+ {
+ Unbind();
+ _canfd = canfd;
+ _canfd.OnDbcMessageDecoded += OnDbcMessageDecoded;
+ }
+
+ ///
+ /// 取消订阅。
+ ///
+ public void Unbind()
+ {
+ if (_canfd != null)
+ {
+ _canfd.OnDbcMessageDecoded -= OnDbcMessageDecoded;
+ _canfd = null;
+ }
+ }
+
+ ///
+ /// DBC 解码回调:提取所有信号并广播。
+ ///
+ private void OnDbcMessageDecoded(uint channel, ZDBC.DBCMessage msg)
+ {
+ if (_disposed) return;
+
+ string fingerprint = $"CAN:{channel}";
+ var now = DateTime.Now;
+
+ 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;
+
+ // MethodName 格式:"{MessageId:X}.{SignalName}"
+ string methodName = $"{msg.nID:X}.{signalName}";
+
+ _eventAggregator.GetEvent().Publish(new HardwareReportArgs
+ {
+ Scope = _scopeName,
+ HardwareFingerprint = fingerprint,
+ MethodName = methodName,
+ Value = physicalValue,
+ Time = now
+ });
+ }
+ }
+
+ ///
+ /// 生成 CAN 信号的 DisplayName 格式:"{MessageName}.{SignalName}"
+ ///
+ public static string BuildDisplayName(string messageName, string signalName)
+ {
+ return $"{messageName}.{signalName}";
+ }
+
+ ///
+ /// 生成 CAN 信号的 MethodName 格式:"{MessageId:X}.{SignalName}"
+ ///
+ public static string BuildMethodName(uint messageId, string signalName)
+ {
+ return $"{messageId:X}.{signalName}";
+ }
+
+ ///
+ /// 生成 CAN 信号的 Fingerprint 格式:"CAN:{Channel}"
+ ///
+ public static string BuildFingerprint(uint channel)
+ {
+ return $"CAN:{channel}";
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ Unbind();
+ }
+ }
+}
diff --git a/UIShare/GlobalVariable/DeviceManager.cs b/UIShare/GlobalVariable/DeviceManager.cs
index 8fd6e44..c0bacdc 100644
--- a/UIShare/GlobalVariable/DeviceManager.cs
+++ b/UIShare/GlobalVariable/DeviceManager.cs
@@ -27,6 +27,9 @@ namespace UIShare.GlobalVariable
private readonly string _scopeName;
private readonly IEventAggregator _eventAggregator;
+ /// CAN 信号广播器:将 DBC 解码后的信号值广播给监控系统
+ private CANSignalBroadcaster? _canSignalBroadcaster;
+
/// 按 DeviceName 索引的设备字典,便于业务层按名取实例。
public IDictionary DeviceMap { get; private set; }
= new Dictionary(StringComparer.OrdinalIgnoreCase);
@@ -412,7 +415,7 @@ namespace UIShare.GlobalVariable
}
///
- /// 打开 CAN 卡:打开设备 + 初始化并启动所有通道 + 自动加载 DBC
+ /// 打开 CAN 卡:打开设备 + 初始化并启动所有通道 + 自动加载 DBC + 启动信号广播
///
private async Task ConnectCanAsync(DeviceInfoVM info)
{
@@ -436,51 +439,49 @@ namespace UIShare.GlobalVariable
bool ok = await Task.Run(() =>
{
if (!CANFD.打开设备()) return false;
-
- // 初始化并启动所有通道
- int maxCh = CANFD.DBCParser.MaxChannels;
- for (uint i = 0; i < maxCh; i++)
- {
- try
- {
- CANFD.初始化并启动通道(i);
- }
- catch (Exception ex)
- {
- LoggerHelper.Warn($"CAN 通道 {i} 初始化失败:{ex.Message}");
- break;
- }
- }
+ // 启动 CAN 信号广播器
+ _canSignalBroadcaster = new CANSignalBroadcaster(_eventAggregator, _scopeName);
+ _canSignalBroadcaster.Bind(CANFD);
// 自动加载 DBC 文件
if (_systemConfig?.DBCAutoLoadList != null)
{
foreach (var item in _systemConfig.DBCAutoLoadList)
{
- if (item.DBCChannel < 0 || item.DBCChannel >= maxCh) continue;
+ if (item.DBCChannel < 0 || item.DBCChannel >= 4) continue;
if (string.IsNullOrWhiteSpace(item.DBCFilePath)) continue;
if (!File.Exists(item.DBCFilePath))
{
- // 文件不存在,发布事件广播
- _eventAggregator.GetEvent().Publish(new DBCFileNotFoundArgs
+ // 发布 DBC 卸载事件,通知监控系统清除对应信号
+ _eventAggregator.GetEvent().Publish(new DBCUnloadedArgs
{
- Channel = item.DBCChannel,
- FilePath = item.DBCFilePath,
+ Channel = (uint)item.DBCChannel,
Scope = _scopeName
});
LoggerHelper.Warn($"CAN 通道 {item.DBCChannel} 自动加载 DBC 失败:文件不存在 [{item.DBCFilePath}]");
continue;
}
-
+ CANFD.初始化并启动通道((uint)item.DBCChannel);
bool loadOk = CANFD.加载通道DBC文件((uint)item.DBCChannel, item.DBCFilePath);
if (loadOk)
{
LoggerHelper.Info($"CAN 通道 {item.DBCChannel} 已自动加载 DBC:{item.DBCFilePath}");
+ // 发布 DBC 加载完成事件,通知监控系统刷新信号列表
+ _eventAggregator.GetEvent().Publish(new DBCLoadedArgs
+ {
+ Channel = (uint)item.DBCChannel,
+ Scope = _scopeName
+ });
}
else
{
LoggerHelper.Warn($"CAN 通道 {item.DBCChannel} 自动加载 DBC 失败:{item.DBCFilePath}");
+ _eventAggregator.GetEvent().Publish(new DBCUnloadedArgs
+ {
+ Channel = (uint)item.DBCChannel,
+ Scope = _scopeName
+ });
}
}
}
@@ -527,6 +528,10 @@ namespace UIShare.GlobalVariable
return;
}
+ // 先释放 CAN 信号广播器,取消事件订阅,防止关闭过程中仍触发回调
+ _canSignalBroadcaster?.Dispose();
+ _canSignalBroadcaster = null;
+
await Task.Run(() => CANFD.关闭CAN卡设备());
LoggerHelper.Info($"CAN 设备 [{name}] 已成功关闭连接。");
}
@@ -586,6 +591,10 @@ namespace UIShare.GlobalVariable
public void Dispose()
{
+ // 释放 CAN 信号广播器
+ _canSignalBroadcaster?.Dispose();
+ _canSignalBroadcaster = null;
+
if (string.IsNullOrEmpty(_scopeName)) return;
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用
diff --git a/UIShare/PubEvent/DBCFileNotFoundEvent.cs b/UIShare/PubEvent/DBCFileNotFoundEvent.cs
deleted file mode 100644
index 66f62dd..0000000
--- a/UIShare/PubEvent/DBCFileNotFoundEvent.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace UIShare.PubEvent
-{
- ///
- /// DBC 自动加载时找不到文件事件。
- /// 参数:(通道号, 文件路径)
- ///
- public class DBCFileNotFoundEvent : PubSubEvent
- {
- }
-
- public class DBCFileNotFoundArgs
- {
- public int Channel { get; set; }
- public string FilePath { get; set; } = string.Empty;
- public string Scope { get; set; } = string.Empty;
- }
-}
diff --git a/UIShare/PubEvent/DBCLoadedEvent.cs b/UIShare/PubEvent/DBCLoadedEvent.cs
new file mode 100644
index 0000000..1e5e34e
--- /dev/null
+++ b/UIShare/PubEvent/DBCLoadedEvent.cs
@@ -0,0 +1,17 @@
+namespace UIShare.PubEvent
+{
+ ///
+ /// DBC 加载完成事件:通知订阅者某个通道的 DBC 已加载,可以刷新信号列表。
+ ///
+ public class DBCLoadedEvent : PubSubEvent
+ {
+ }
+
+ public class DBCLoadedArgs
+ {
+ public uint Channel { get; set; }
+ public string Scope { get; set; } = string.Empty;
+ }
+
+
+}
diff --git a/UIShare/PubEvent/DBCUnloadedEvent.cs b/UIShare/PubEvent/DBCUnloadedEvent.cs
new file mode 100644
index 0000000..c987193
--- /dev/null
+++ b/UIShare/PubEvent/DBCUnloadedEvent.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace UIShare.PubEvent
+{
+ public class DBCUnloadedEvent : PubSubEvent
+ {
+ }
+
+ public class DBCUnloadedArgs
+ {
+ public uint Channel { get; set; }
+ public string Scope { get; set; } = string.Empty;
+ }
+}
diff --git a/UIShare/UIViewModel/DeviceInfoVM.cs b/UIShare/UIViewModel/DeviceInfoVM.cs
index bab204c..441877a 100644
--- a/UIShare/UIViewModel/DeviceInfoVM.cs
+++ b/UIShare/UIViewModel/DeviceInfoVM.cs
@@ -1,5 +1,7 @@
+using Newtonsoft.Json;
+
namespace UIShare.UIViewModel
{
public class DeviceInfoVM : BindableBase
@@ -31,8 +33,9 @@ namespace UIShare.UIViewModel
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
-
- private bool _isConnected;
+
+ private bool _isConnected=false;
+ [JsonIgnore]
public bool IsConnected
{
get => _isConnected;