can信号监控曲线添加
This commit is contained in:
114
UIShare/GlobalVariable/CANSignalBroadcaster.cs
Normal file
114
UIShare/GlobalVariable/CANSignalBroadcaster.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Model.Models;
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Text;
|
||||
using UIShare.PubEvent;
|
||||
using ZLGUSBCANFD;
|
||||
|
||||
namespace UIShare.GlobalVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// CAN 信号广播器:订阅 ZLGCANFD.OnDbcMessageDecoded 事件,
|
||||
/// 将解码后的 DBC 信号值通过 HardwareDataReportedEvent 广播给监控系统。
|
||||
/// 由 DeviceManager 创建和管理,生命周期与 CAN 设备绑定。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定 CANFD 实例并订阅解码事件。
|
||||
/// </summary>
|
||||
public void Bind(ZLGCANFD canfd)
|
||||
{
|
||||
Unbind();
|
||||
_canfd = canfd;
|
||||
_canfd.OnDbcMessageDecoded += OnDbcMessageDecoded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消订阅。
|
||||
/// </summary>
|
||||
public void Unbind()
|
||||
{
|
||||
if (_canfd != null)
|
||||
{
|
||||
_canfd.OnDbcMessageDecoded -= OnDbcMessageDecoded;
|
||||
_canfd = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DBC 解码回调:提取所有信号并广播。
|
||||
/// </summary>
|
||||
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<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
|
||||
{
|
||||
Scope = _scopeName,
|
||||
HardwareFingerprint = fingerprint,
|
||||
MethodName = methodName,
|
||||
Value = physicalValue,
|
||||
Time = now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 CAN 信号的 DisplayName 格式:"{MessageName}.{SignalName}"
|
||||
/// </summary>
|
||||
public static string BuildDisplayName(string messageName, string signalName)
|
||||
{
|
||||
return $"{messageName}.{signalName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 CAN 信号的 MethodName 格式:"{MessageId:X}.{SignalName}"
|
||||
/// </summary>
|
||||
public static string BuildMethodName(uint messageId, string signalName)
|
||||
{
|
||||
return $"{messageId:X}.{signalName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 CAN 信号的 Fingerprint 格式:"CAN:{Channel}"
|
||||
/// </summary>
|
||||
public static string BuildFingerprint(uint channel)
|
||||
{
|
||||
return $"CAN:{channel}";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
Unbind();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ 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);
|
||||
@@ -412,7 +415,7 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开 CAN 卡:打开设备 + 初始化并启动所有通道 + 自动加载 DBC
|
||||
/// 打开 CAN 卡:打开设备 + 初始化并启动所有通道 + 自动加载 DBC + 启动信号广播
|
||||
/// </summary>
|
||||
private async Task<bool> 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<DBCFileNotFoundEvent>().Publish(new DBCFileNotFoundArgs
|
||||
// 发布 DBC 卸载事件,通知监控系统清除对应信号
|
||||
_eventAggregator.GetEvent<DBCUnloadedEvent>().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<DBCLoadedEvent>().Publish(new DBCLoadedArgs
|
||||
{
|
||||
Channel = (uint)item.DBCChannel,
|
||||
Scope = _scopeName
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerHelper.Warn($"CAN 通道 {item.DBCChannel} 自动加载 DBC 失败:{item.DBCFilePath}");
|
||||
_eventAggregator.GetEvent<DBCUnloadedEvent>().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;
|
||||
|
||||
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace UIShare.PubEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// DBC 自动加载时找不到文件事件。
|
||||
/// 参数:(通道号, 文件路径)
|
||||
/// </summary>
|
||||
public class DBCFileNotFoundEvent : PubSubEvent<DBCFileNotFoundArgs>
|
||||
{
|
||||
}
|
||||
|
||||
public class DBCFileNotFoundArgs
|
||||
{
|
||||
public int Channel { get; set; }
|
||||
public string FilePath { get; set; } = string.Empty;
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
17
UIShare/PubEvent/DBCLoadedEvent.cs
Normal file
17
UIShare/PubEvent/DBCLoadedEvent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace UIShare.PubEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// DBC 加载完成事件:通知订阅者某个通道的 DBC 已加载,可以刷新信号列表。
|
||||
/// </summary>
|
||||
public class DBCLoadedEvent : PubSubEvent<DBCLoadedArgs>
|
||||
{
|
||||
}
|
||||
|
||||
public class DBCLoadedArgs
|
||||
{
|
||||
public uint Channel { get; set; }
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
UIShare/PubEvent/DBCUnloadedEvent.cs
Normal file
18
UIShare/PubEvent/DBCUnloadedEvent.cs
Normal file
@@ -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<DBCUnloadedArgs>
|
||||
{
|
||||
}
|
||||
|
||||
public class DBCUnloadedArgs
|
||||
{
|
||||
public uint Channel { get; set; }
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user