using Common.Attributes;
using Logger;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ZLGUSBCANFD
{
///
/// 周立功 USBCANFD 系列 CAN 卡控制类(如 USBCANFD-400U),封装设备连接、通道初始化、
/// DBC 矩阵加载与解码、报文发送(单次/循环)、信号物理值设置等功能。
/// 每个通道拥有独立的 DBC 引擎句柄与细粒度锁,支持多台架并行互不干扰。
///
[ADPCommand]
public class ZLGCANFD : IDisposable
{
// 硬件设备与通道句柄
private IntPtr _deviceHandle = IntPtr.Zero;
private readonly IntPtr[] _channelHandles;
// 每个通道分配独立的 DBC 引擎句柄与锁,彻底解决台架间DBC冲突与锁竞争
private readonly uint[] _dbcHandles;
private readonly bool[] _isDbcLoadedArray;
private readonly object[] _channelLocks; // 通道级细粒度锁
// 异步高性能接收线程控制
private volatile bool _isRunning = false;
private volatile bool _isClosing = false; // 关闭流程标志:通知循环发送任务尽快退出
private readonly List _receiveThreads = new List();
// DBC 循环发送任务管理:Key = (通道号, 帧ID),Value = (CTS, Task)
private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), (CancellationTokenSource Cts, Task SendTask)> _cyclicSenders = new ConcurrentDictionary<(uint, uint), (CancellationTokenSource, Task)>();
// 信号值持久化覆盖表:Key = (通道号, 帧ID),Value = 信号名 → 物理值
// 每次 设置报文 会累积写入此表,发送时以 DBC 初始值为底再叠加此表覆盖值
private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), Dictionary> _signalOverrides = new ConcurrentDictionary<(uint, uint), Dictionary>();
// 最新解码报文缓存:Key = (通道号, 帧ID),Value = 最近一次 DBC 解码成功的报文快照(含各信号原始值)
// 接收线程解码成功时写入,供 获取报文信号值 主动查询最新信号物理值
private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), ZDBC.DBCMessage> _latestDecodedMessages = new ConcurrentDictionary<(uint, uint), ZDBC.DBCMessage>();
// 动态硬件参数
private readonly uint _deviceType; // 76: USBCANFD-400U
private readonly uint _deviceIndex; // 设备索引
private readonly int _maxChannels; // 动态通道数
// 通道波特率与终端电阻配置(构造时传入,所有通道共用)
private readonly string _abitBaud;
private readonly string _dbitBaud;
private readonly bool _enableTerminalResistance;
///
/// 当前 CAN 卡专属的 DBC 解析器,按通道隔离报文数据库。
///
public ZLGDBCParser DBCParser { get; }
///
/// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
/// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
///
public event Action? OnDbcMessageDecoded;
///
/// 事件:当接收到任意 CAN/CANFD 原始报文时触发(不依赖 DBC,自定义报文也能收到)
/// 参数:通道号, 帧ID, 数据, 时间戳(微秒), 是否FD, 数据长度
///
public event Action? OnRawMessageReceived;
///
/// 构造函数,配置设备类型、索引、通道数及所有通道共用的波特率与终端电阻参数,
/// 并初始化通道状态数组与 DBC 解析器。
///
/// 设备类型,76 为 USBCANFD-400U
/// 设备索引,多台同型号设备时区分,默认 0
/// 通道数量,默认 4
/// 仲裁域波特率,默认 "500000"
/// 数据域波特率(仅 CANFD 生效),默认 "2000000"
/// 是否启用通道内部终端电阻,默认 true
public ZLGCANFD(uint deviceType = 76, uint deviceIndex = 0, int maxChannels = 4,
string abitBaud = "500000", string dbitBaud = "2000000", bool enableTerminalResistance = true)
{
_deviceType = deviceType;
_deviceIndex = deviceIndex;
_maxChannels = maxChannels;
_abitBaud = abitBaud;
_dbitBaud = dbitBaud;
_enableTerminalResistance = enableTerminalResistance;
// 初始化通道相关状态数组
_channelHandles = new IntPtr[_maxChannels];
_dbcHandles = new uint[_maxChannels];
_isDbcLoadedArray = new bool[_maxChannels];
_channelLocks = new object[_maxChannels];
DBCParser = new ZLGDBCParser(_maxChannels);
for (int i = 0; i < _maxChannels; i++)
{
_channelHandles[i] = IntPtr.Zero;
_dbcHandles[i] = 0;
_isDbcLoadedArray[i] = false;
_channelLocks[i] = new object();
}
}
#region 1. 硬件连接与通道初始化
///
/// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)。
///
/// 是否打开成功;设备已打开时重复调用直接返回 true
public virtual bool 打开设备()
{
if (_deviceHandle != IntPtr.Zero) return true;
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_400U, _deviceIndex, 0);
return _deviceHandle != IntPtr.Zero;
}
///
/// 针对特定通道进行参数初始化并启动(使用构造时传入的波特率与终端电阻配置),
/// 同时为该通道启动专属的后台高性能接收轮询线程。
///
/// 通道索引 (0 起始)
/// 是否初始化并启动成功;通道已启动时重复调用直接返回 true
/// 未先调用“打开设备()”时抛出
public virtual bool 初始化并启动通道(uint 通道号)
{
_isClosing = false; // 重置关闭标志,允许循环发送
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
if (通道号 >= _maxChannels) return false;
lock (_channelLocks[通道号])
{
// 如果已经启动过,直接退出
if (_channelHandles[通道号] != IntPtr.Zero)
{
return true;
//ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]);
}
// 1. 设置该通道专属的仲裁域与数据域波特率
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_abit_baud_rate", _abitBaud) != 1) return false;
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_dbit_baud_rate", _dbitBaud) != 1) return false;
// 2. 设置该通道专属内部终端电阻状态
string resistanceStr = _enableTerminalResistance ? "1" : "0";
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false;
// 3. 规范配置通道结构体
ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG config = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG();
config.can_type = 1; // 1 代表 CANFD 模式
config.config.canfd.mode = 0; // 0 代表正常工作模式
IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config));
Marshal.StructureToPtr(config, pConfig, true);
_channelHandles[通道号] = ZLGCAN.ZCAN_InitCAN(_deviceHandle, 通道号, pConfig);
Marshal.FreeHGlobal(pConfig);
if (_channelHandles[通道号] == IntPtr.Zero) return false;
// 4. 启动 CAN 通道
if (ZLGCAN.ZCAN_StartCAN(_channelHandles[通道号]) != 1) return false;
// 5. 为该通道启动专属的独立后台高性能轮询接收线程(如果尚未启动轮询)
if (!_isRunning) _isRunning = true;
int chnIdx = (int)通道号;
Thread rxThread = new Thread(() => 接收轮询核心(_channelHandles[chnIdx], (uint)chnIdx))
{
IsBackground = true,
Name = $"ZLGCANFD_Dev{_deviceIndex}_CH{chnIdx}_RxThread"
};
_receiveThreads.Add(rxThread);
rxThread.Start();
return true;
}
}
///
/// 关闭 CAN 卡设备:先停止所有循环发送与接收轮询线程,再复位全部通道、
/// 释放各通道 DBC 资源,最后关闭设备主句柄。
///
public virtual void 关闭CAN卡设备()
{
_isClosing = true; // 通知循环发送任务尽快退出
_isRunning = false; // 通知接收轮询线程退出
停止所有循环发送(); // 内部会等待所有循环发送 Task 退出
// 等待所有接收轮询线程真正退出(每个最多 1 秒)
foreach (var thread in _receiveThreads)
{
if (thread.IsAlive)
thread.Join(1000);
}
_receiveThreads.Clear();
// 动态复位所有通道并释放 DBC(使用 TryEnter 防止死锁)
for (uint i = 0; i < _maxChannels; i++)
{
if (Monitor.TryEnter(_channelLocks[i], TimeSpan.FromSeconds(2)))
{
try
{
if (_channelHandles[i] != IntPtr.Zero)
{
ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
_channelHandles[i] = IntPtr.Zero;
}
释放通道DBC(i);
}
finally
{
Monitor.Exit(_channelLocks[i]);
}
}
else
{
// 锁超时:循环发送 Task 可能仍阻塞在原生 API 调用中,强制清理
LoggerHelper.Info($"[ZLGCANFD] 关闭通道 {i} 时获取锁超时,强制清理");
if (_channelHandles[i] != IntPtr.Zero)
{
try { ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); } catch { }
_channelHandles[i] = IntPtr.Zero;
}
释放通道DBC(i);
}
}
// 关闭设备主句柄
if (_deviceHandle != IntPtr.Zero)
{
ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
_deviceHandle = IntPtr.Zero;
}
_isClosing = false;
}
#endregion
#region 2. DBC 矩阵文件操作
///
/// 指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵)。
///
/// 通道索引 (0 起始)
/// DBC 矩阵文件的完整路径
/// 是否加载成功;该通道已加载过则直接返回 true
public virtual bool 加载通道DBC文件(uint 通道号, string dbcFilePath)
{
if (通道号 >= _maxChannels) return false;
lock (_channelLocks[通道号])
{
try
{
if (_isDbcLoadedArray[通道号]) return true;
//加载前先卸载
释放通道DBC(通道号);
uint chDbcHandle = ZDBC.ZDBC_Init();
if (chDbcHandle == 0) return false;
IntPtr ptrPath = Marshal.StringToHGlobalAnsi(dbcFilePath);
bool success = ZDBC.ZDBC_LoadFile(chDbcHandle, ptrPath);
Marshal.FreeHGlobal(ptrPath);
if (success)
{
_dbcHandles[通道号] = chDbcHandle;
_isDbcLoadedArray[通道号] = true;
DBCParser.ParseAndLoadToDatabase(chDbcHandle, (int)通道号);
}
else
{
ZDBC.ZDBC_Release(chDbcHandle);
}
return success;
}
catch
{
return false;
}
}
}
///
/// 释放指定通道已加载的 DBC 资源,并清空该通道的报文数据库。
///
/// 通道索引 (0 起始)
public virtual void 释放通道DBC(uint 通道号)
{
if (通道号 >= _maxChannels) return;
lock (_channelLocks[通道号])
{
if (_isDbcLoadedArray[通道号] && _dbcHandles[通道号] != 0)
{
ZDBC.ZDBC_Release(_dbcHandles[通道号]);
_dbcHandles[通道号] = 0;
_isDbcLoadedArray[通道号] = false;
DBCParser.MsgDatabase[(int)通道号].Clear();
// 同步清理该通道的最新解码报文缓存,避免残留旧信号值
foreach (var key in _latestDecodedMessages.Keys)
{
if (key.通道号 == 通道号)
_latestDecodedMessages.TryRemove(key, out _);
}
}
}
}
#endregion
#region 3. 轮询接收与实时自动化 DBC 解码
private void 接收轮询核心(IntPtr channelHandle, uint channelIndex)
{
const int bufferSize = 100;
int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
int dbcMsgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage));
IntPtr ptrCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferSize);
IntPtr ptrCanFDBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferSize);
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(dbcMsgSize);
try
{
while (_isRunning)
{
bool currentTurnHasData = false;
// 1. 自动提取并解析经典 CAN 帧
uint canNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 0);
if (canNum > 0)
{
uint actualRecv = ZLGCAN.ZCAN_Receive(channelHandle, ptrCanBuffer, bufferSize, 5);
for (int i = 0; i < actualRecv; i++)
{
IntPtr framePtr = IntPtr.Add(ptrCanBuffer, i * canStructSize);
// 触发原始报文事件(不依赖 DBC,自定义报文也能收到)
var rawData = (ZLGCAN.ZCAN_Receive_Data)Marshal.PtrToStructure(framePtr, typeof(ZLGCAN.ZCAN_Receive_Data))!;
OnRawMessageReceived?.Invoke(channelIndex, rawData.frame.can_id, rawData.frame.data, rawData.timestamp, false, rawData.frame.can_dlc);
// 只锁定当前通道的DBC句柄进行解码,高并发完全不卡顿
lock (_channelLocks[channelIndex])
{
if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 0))
{
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
// 缓存最新解码报文快照,供 获取报文信号值 查询
_latestDecodedMessages[(channelIndex, msg.nID)] = msg;
OnDbcMessageDecoded?.Invoke(channelIndex, msg);
}
}
}
currentTurnHasData = true;
}
// 2. 自动提取并解析高速 CANFD 帧
uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 1);
if (canfdNum > 0)
{
uint actualRecvFd = ZLGCAN.ZCAN_ReceiveFD(channelHandle, ptrCanFDBuffer, bufferSize, 5);
for (int i = 0; i < actualRecvFd; i++)
{
IntPtr framePtr = IntPtr.Add(ptrCanFDBuffer, i * canfdStructSize);
// 触发原始报文事件(不依赖 DBC,自定义报文也能收到)
var rawFdData = (ZLGCAN.ZCAN_ReceiveFD_Data)Marshal.PtrToStructure(framePtr, typeof(ZLGCAN.ZCAN_ReceiveFD_Data))!;
OnRawMessageReceived?.Invoke(channelIndex, rawFdData.frame.can_id, rawFdData.frame.data, rawFdData.timestamp, true, rawFdData.frame.len);
lock (_channelLocks[channelIndex])
{
if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 1))
{
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
// 缓存最新解码报文快照,供 获取报文信号值 查询
_latestDecodedMessages[(channelIndex, msg.nID)] = msg;
OnDbcMessageDecoded?.Invoke(channelIndex, msg);
}
}
}
currentTurnHasData = true;
}
if (!currentTurnHasData)
{
Thread.Sleep(2);
}
}
}
finally
{
Marshal.FreeHGlobal(ptrCanBuffer);
Marshal.FreeHGlobal(ptrCanFDBuffer);
Marshal.FreeHGlobal(ptrDbcMsg);
}
}
#endregion
#region 4. DBC 自动打包与智能报文发送
///
/// 发送一条 DBC 定义报文。
///
/// 通道号
/// DBC 中定义的帧 ID
/// 信号名称 → 物理值;只设置字典中包含的信号,其余信号保持 DBC 默认值
/// 0 = 只发送一次;>0 = 按指定间隔循环发送(毫秒)
/// 1 = CANFD,0 = CAN
/// 是否成功启动/发送
[Browsable(false)]
public virtual bool 发送DBC定义报文(
uint 通道号,
uint 帧ID,
Dictionary 信号物理值字典,
int 循环间隔毫秒 = 0,
int 是否使用CANFD = 1)
{
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[通道号]) throw new InvalidOperationException($"通道 {通道号} 的 DBC 协议未加载,无法执行打包发送指令。");
if (循环间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环间隔毫秒), "循环间隔必须大于或等于 0。");
if (信号物理值字典 == null || 信号物理值字典.Count == 0)
throw new ArgumentException("信号物理值字典不能为空。", nameof(信号物理值字典));
if (循环间隔毫秒 == 0)
{
// 单次发送:同步执行
lock (_channelLocks[通道号])
{
return 发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD);
}
}
// 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环
停止循环发送(通道号, 帧ID);
var cts = new CancellationTokenSource();
var sendTask = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
if (_isClosing) break; // 关闭流程中立即退出
try
{
lock (_channelLocks[通道号])
{
if (_channelHandles[通道号] == IntPtr.Zero || _isClosing) break;
发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD);
}
await Task.Delay(循环间隔毫秒, cts.Token);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}");
break;
}
}
// 清理:仅当字典中存的仍然是本任务创建的 CTS 时才移除,避免误删新任务的 CTS
if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current.Cts, cts))
{
_cyclicSenders.TryRemove((通道号, 帧ID), out _);
}
cts.Dispose();
}, cts.Token);
_cyclicSenders[(通道号, 帧ID)] = (cts, sendTask);
return true;
}
///
/// 停止指定通道、指定帧 ID 的循环发送。
///
/// 通道索引 (0 起始)
/// 要停止循环发送的报文帧 ID
public virtual void 停止循环发送(uint 通道号, uint 帧ID)
{
if (_cyclicSenders.TryRemove((通道号, 帧ID), out var entry))
{
entry.Cts.Cancel();
// 不在这里 Dispose,由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token
}
}
///
/// 停止所有 DBC 循环发送任务,并等待全部发送任务退出(最多 3 秒)。
///
public virtual void 停止所有循环发送()
{
var entries = _cyclicSenders.ToArray();
// 1. 取消所有 CTS(不 Dispose,由 Task 清理代码负责)
foreach (var kvp in entries)
{
kvp.Value.Cts.Cancel();
}
// 2. 等待所有循环发送 Task 退出(最多 3 秒)
var tasks = new List();
foreach (var kvp in entries)
{
if (!kvp.Value.SendTask.IsCompleted)
tasks.Add(kvp.Value.SendTask);
}
if (tasks.Count > 0)
{
try
{
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(3));
}
catch { /* 忽略等待异常 */ }
}
_cyclicSenders.Clear();
}
///
/// 清除指定通道、指定帧 ID 的信号覆盖值,恢复为 DBC 初始值。
///
/// 通道索引 (0 起始)
/// 要清除覆盖值的报文帧 ID
public virtual void 清除信号覆盖(uint 通道号, uint 帧ID)
{
if (_signalOverrides.TryRemove((通道号, 帧ID), out var dict))
{
lock (dict) dict.Clear();
}
}
///
/// 清除指定通道所有帧的信号覆盖值,全部恢复为 DBC 初始值。
///
/// 通道索引 (0 起始)
public virtual void 清除通道信号覆盖(uint 通道号)
{
foreach (var key in _signalOverrides.Keys)
{
if (key.通道号 == 通道号 && _signalOverrides.TryRemove(key, out var dict))
{
lock (dict) dict.Clear();
}
}
}
///
/// 单次发送 DBC 报文(调用方已持有通道锁)。
///
[Browsable(false)]
private bool 发送DBC定义报文单次(uint 通道号, uint 帧ID, Dictionary 信号物理值字典, int 是否使用CANFD)
{
uint chDbcHandle = _dbcHandles[通道号];
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
IntPtr ptrCount = Marshal.AllocHGlobal(sizeof(uint));
Marshal.WriteInt32(ptrCount, 1);
try
{
if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg)) return false;
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
// 根据传入的物理值设置对应信号的原始值
for (int i = 0; i < msg.nSignalCount; i++)
{
var signal = msg.vSignals[i];
string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (信号物理值字典.TryGetValue(signalName, out double physicalValue))
{
signal.nRawvalue = 物理值转原始值(signal, physicalValue);
msg.vSignals[i] = signal;
}
}
Marshal.StructureToPtr(msg, ptrDbcMsg, true);
if (是否使用CANFD == 0)
{
IntPtr ptrCanFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.can_frame)));
try
{
if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFrame, ptrCount, ptrDbcMsg, 0)) return false;
ZLGCAN.can_frame canFrame = (ZLGCAN.can_frame)Marshal.PtrToStructure(ptrCanFrame, typeof(ZLGCAN.can_frame));
canFrame.__pad |= 0x20;
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
Marshal.StructureToPtr(txData, pTx, true);
return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1;
}
finally
{
Marshal.FreeHGlobal(pTx);
}
}
finally
{
Marshal.FreeHGlobal(ptrCanFrame);
}
}
else
{
IntPtr ptrCanFDFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.canfd_frame)));
try
{
if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFDFrame, ptrCount, ptrDbcMsg, 1)) return false;
ZLGCAN.canfd_frame canfdFrame = (ZLGCAN.canfd_frame)Marshal.PtrToStructure(ptrCanFDFrame, typeof(ZLGCAN.canfd_frame));
canfdFrame.flags |= 0x20;
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
Marshal.StructureToPtr(txFdData, pTxFd, true);
return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1;
}
finally
{
Marshal.FreeHGlobal(pTxFd);
}
}
finally
{
Marshal.FreeHGlobal(ptrCanFDFrame);
}
}
}
finally
{
Marshal.FreeHGlobal(ptrDbcMsg);
Marshal.FreeHGlobal(ptrCount);
}
}
///
/// 不依赖通道锁的物理值到原始值转换。
///
private static ulong 物理值转原始值(ZDBC.DBCSignal 信号定义, double 实际物理值)
{
IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
try
{
Marshal.StructureToPtr(信号定义, pSignal, true);
Marshal.StructureToPtr(实际物理值, pValue, true);
return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
}
finally
{
Marshal.FreeHGlobal(pSignal);
Marshal.FreeHGlobal(pValue);
}
}
#endregion
#region 4.1 报文设置与发送 (支持智能进制转换)
///
/// 设置并发送指定信号值(帧 ID 支持 string 兼容模式)。
/// 其余未指定信号使用 DBC 中定义的初始值填充,避免总线数据被意外清零。
///
/// CAN/CANFD 通道索引
/// 对应报文的帧 ID(可以是 "26"、"0x1A"、"1A")
/// DBC 中定义的英文信号名称(不区分大小写)
/// 要写入的实际物理数值
/// 0 = 单次发送;>0 = 周期循环发送(单位毫秒)
/// true = 设置后立即发送;false = 仅记录到持久化覆盖表,等后续 发送报文 时生效
/// 操作是否成功
public virtual bool 设置报文(uint 通道号, string 帧IDStr, string 信号名称, double 物理值, int 循环发送间隔毫秒 = 0, bool 直接发送 = false)
{
if (!TryParseFrameId(帧IDStr, out uint 帧ID))
{
Console.WriteLine($"[ZLGCANFD] 设置报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[通道号]) return false;
if (string.IsNullOrWhiteSpace(信号名称)) return false;
// 将本次设置的信号值写入持久化覆盖表(累积,不会丢失之前设置的值)
var overrides = _signalOverrides.GetOrAdd((通道号, 帧ID), _ => new Dictionary(StringComparer.OrdinalIgnoreCase));
lock (overrides)
{
overrides[信号名称] = 物理值;
}
// 构建发送字典:DBC 初始值 + 持久化覆盖表(已包含本次设置)
var merged = 构建报文发送字典(通道号, 帧ID);
if (直接发送) return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒);
else return true;
}
///
/// 发送报文:以 DBC 中所有信号的初始值作为默认值发送(帧 ID 支持 string 兼容模式)。
/// 如果之前调用 设置报文 保存过覆盖值,则会优先使用覆盖值。
///
/// CAN/CANFD 通道索引
/// DBC 中定义的帧 ID(可以是 "26"、"0x1A"、"1A")
/// 0 = 单次发送;>0 = 周期循环发送(毫秒)
/// 1 = 以 CANFD 格式发送;0 = 经典 CAN 格式
/// 是否成功启动/发送
public virtual bool 发送报文(uint 通道号, string 帧IDStr, int 循环发送间隔毫秒 = 0, int 是否使用CANFD = 1)
{
if (!TryParseFrameId(帧IDStr, out uint 帧ID))
{
Console.WriteLine($"[ZLGCANFD] 发送报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[通道号]) return false;
if (循环发送间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环发送间隔毫秒));
var merged = 构建报文发送字典(通道号, 帧ID);
return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒, 是否使用CANFD);
}
///
/// 发送自定义报文(原始字节,帧 ID 支持 string 兼容模式)。
/// 循环发送间隔毫秒 = 0 时只发送一次;>0 时按指定间隔循环发送,可调用 停止循环发送 停止。
///
/// CAN/CANFD 通道索引
/// 帧 ID(可以是 "26"、"0x1A"、"1A");扩展帧会自动置位 0x80000000
/// 原始帧数据
/// 数据长度;CANFD 仅允许 0-8,12,16,20,24,32,48,64
/// 是否为 29 位扩展帧
/// true = CANFD;false = 经典 CAN
/// CANFD 是否开启波特率加速
/// 0 = 单次发送;>0 = 周期循环发送(毫秒)
/// 是否成功启动/发送
public virtual bool 发送自定义报文(
uint 通道号,
string 帧IDStr,
byte[] 原始数据,
byte dlcLength,
bool 是否是扩展帧 = false,
bool 是否是CANFD = true,
bool 开启波特率加速BRS = true,
int 循环发送间隔毫秒 = 0)
{
if (!TryParseFrameId(帧IDStr, out uint 帧ID))
{
Console.WriteLine($"[ZLGCANFD] 发送自定义报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (原始数据 == null) return false;
if (循环发送间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环发送间隔毫秒), "循环发送间隔必须大于或等于 0。");
// 扩展帧需要把最高位标志位置起来
uint canId = 帧ID & 0x7FFFFFFF;
if (是否是扩展帧) canId |= 0x80000000;
// 单次发送
if (循环发送间隔毫秒 == 0)
{
return 发送自定义报文单次(通道号, canId, 原始数据, dlcLength, 是否是扩展帧, 是否是CANFD, 开启波特率加速BRS);
}
// 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环
停止循环发送(通道号, 帧ID);
// 拷贝原始数据,避免外部修改影响循环发送
byte[] dataCopy = new byte[原始数据.Length];
Array.Copy(原始数据, dataCopy, 原始数据.Length);
var cts = new CancellationTokenSource();
var sendTask = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
if (_isClosing) break;
try
{
lock (_channelLocks[通道号])
{
if (_channelHandles[通道号] == IntPtr.Zero || _isClosing) break;
发送自定义报文单次(通道号, canId, dataCopy, dlcLength, 是否是扩展帧, 是否是CANFD, 开启波特率加速BRS);
}
await Task.Delay(循环发送间隔毫秒, cts.Token);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送自定义报文失败: {ex.Message}");
break;
}
}
if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current.Cts, cts))
{
_cyclicSenders.TryRemove((通道号, 帧ID), out _);
}
cts.Dispose();
}, cts.Token);
_cyclicSenders[(通道号, 帧ID)] = (cts, sendTask);
return true;
}
///
/// 发送自定义报文的单次执行(内部方法,无循环逻辑)。
///
[Browsable(false)]
private bool 发送自定义报文单次(
uint 通道号,
uint canId,
byte[] 原始数据,
byte dlcLength,
bool 是否是扩展帧,
bool 是否是CANFD,
bool 开启波特率加速BRS)
{
lock (_channelLocks[通道号])
{
if (是否是CANFD)
{
byte validFdDlc = 校验CANFD的Dlc(dlcLength);
ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = validFdDlc,
flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20),
data = new byte[64]
};
Array.Copy(原始数据, 0, fdFrame.data, 0, Math.Min(原始数据.Length, 64));
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
Marshal.StructureToPtr(txFdData, pTxFd, true);
return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTxFd); }
}
else
{
ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame
{
can_id = canId,
can_dlc = dlcLength > 8 ? (byte)8 : dlcLength,
__pad = 0x20,
data = new byte[8]
};
Array.Copy(原始数据, 0, standardFrame.data, 0, Math.Min(原始数据.Length, 8));
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
Marshal.StructureToPtr(txData, pTx, true);
return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTx); }
}
}
}
///
/// 直接发送 8 字节原始报文(经典 CAN 或 CANFD 均可)。
/// 无需加载 DBC,适合手动构造简单报文。
///
/// 通道号(0 ~ MaxChannels-1)
/// 帧 ID(标准 11 位,扩展帧自动置位 0x80000000)
/// 原始数据;超过 8 字节会自动截断,不足 8 字节自动补零
/// 是否为 29 位扩展帧
/// true = CANFD 格式;false = 经典 CAN 格式
/// CANFD 是否开启波特率加速(经典 CAN 忽略此参数)
/// 是否发送成功
public virtual bool 发送原始报文(
uint 通道号,
uint 帧ID,
byte[] data,
bool 是否是扩展帧 = false,
bool 是否是CANFD = true,
bool 开启波特率加速BRS = true)
{
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
byte[] buffer = new byte[8];
if (data != null)
{
Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, 8));
}
uint canId = 帧ID & 0x7FFFFFFF;
if (是否是扩展帧) canId |= 0x80000000;
lock (_channelLocks[通道号])
{
if (是否是CANFD)
{
ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = 8,
flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20),
data = new byte[64]
};
Array.Copy(buffer, 0, fdFrame.data, 0, 8);
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
Marshal.StructureToPtr(txFdData, pTxFd, true);
return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTxFd); }
}
else
{
ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame
{
can_id = canId,
can_dlc = 8,
__pad = 0x20,
data = new byte[8]
};
Array.Copy(buffer, 0, standardFrame.data, 0, 8);
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
Marshal.StructureToPtr(txData, pTx, true);
return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTx); }
}
}
}
///
/// 构建发送用信号字典:DBC 初始值 + 额外覆盖。
/// 调用方需自行保证通道已初始化且 DBC 已加载。
///
private Dictionary 构建报文发送字典(uint 通道号, uint 帧ID, Dictionary? 额外覆盖 = null)
{
uint chDbcHandle = _dbcHandles[通道号];
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
try
{
lock (_channelLocks[通道号])
{
if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg))
return new Dictionary(StringComparer.OrdinalIgnoreCase);
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];
string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (!string.IsNullOrEmpty(signalName) && signal.initialValueValid != 0)
{
dict[signalName] = signal.initialValue;
}
}
// 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 额外覆盖)
{
dict[kvp.Key] = kvp.Value;
}
}
return dict;
}
}
finally
{
Marshal.FreeHGlobal(ptrDbcMsg);
}
}
///
/// CANFD 有效 DLC 校验,非法值自动就近取到下一个合法 DLC。
///
private static byte 校验CANFD的Dlc(byte dlc)
{
if (dlc <= 8) return dlc;
if (dlc <= 12) return 12;
if (dlc <= 16) return 16;
if (dlc <= 20) return 20;
if (dlc <= 24) return 24;
if (dlc <= 32) return 32;
if (dlc <= 48) return 48;
return 64;
}
#endregion
#region 4.2 报文信号查询
///
/// 获取指定通道最新一帧 DBC 报文中某个信号的物理值(帧 ID 支持 string 兼容模式)。
///
/// 返回值为最近一次接收并解码成功的报文快照:物理值 = 原始值 × 因子 + 偏移,
/// 与监控界面广播的数值口径一致。若报文尚未收到、DBC 未加载、报文或信号名称不存在,
/// 将记录错误日志并返回 0。
///
///
/// CAN/CANFD 通道索引
/// 对应报文的帧 ID(可以是 "26"、"0x1A"、"1A")
/// DBC 中定义的英文信号名称(不区分大小写)
/// 信号物理值;失败时返回 0
public virtual double 获取报文信号值(uint 通道号, string 帧IDStr, string 信号名称)
{
if (!TryParseFrameId(帧IDStr, out uint 帧ID))
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 无法解析的帧 ID '{帧IDStr}'");
return 0;
}
if (通道号 >= _maxChannels)
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 通道号 {通道号} 越界(有效范围 0~{_maxChannels - 1})");
return 0;
}
if (!_isDbcLoadedArray[通道号])
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 通道 {通道号} 尚未加载 DBC 文件");
return 0;
}
if (string.IsNullOrWhiteSpace(信号名称))
{
LoggerHelper.Error("[ZLGCANFD] 获取报文信号值失败: 信号名称为空");
return 0;
}
// 查找该通道该帧 ID 最近一次解码成功的报文快照
if (!_latestDecodedMessages.TryGetValue((通道号, 帧ID), out var msg))
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 通道 {通道号} 未收到帧 ID 0x{帧ID:X} 的报文(尚未接收或 DBC 中无此报文)");
return 0;
}
// 在报文信号集合中查找目标信号(不区分大小写,与 设置报文 的信号匹配口径一致)
for (int i = 0; i < msg.nSignalCount; i++)
{
var signal = msg.vSignals[i];
string name = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (string.Equals(name, 信号名称, StringComparison.OrdinalIgnoreCase))
{
// 物理值 = 原始值 × 因子 + 偏移(与 CANSignalBroadcaster 广播公式一致)
return signal.nRawvalue * signal.nFactor + signal.nOffset;
}
}
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 帧 ID 0x{帧ID:X} 的报文中不存在信号 '{信号名称}'");
return 0;
}
#endregion
#region 5. 辅助工具子系统
///
/// 智能解析帧 ID 字符串,兼容 10 进制、16 进制(如 "0x1A", "1A", "26")
///
/// 输入的帧 ID 字符串
/// 解析成功后的 uint 帧 ID
/// 是否解析成功
[Browsable(false)]
public static bool TryParseFrameId(string idStr, out uint frameId)
{
frameId = 0;
if (string.IsNullOrWhiteSpace(idStr)) return false;
idStr = idStr.Trim();
try
{
// 1. 处理带 0x 或 0X 的标准 16 进制
if (idStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
frameId = Convert.ToUInt32(idStr.Substring(2), 16);
return true;
}
// 2. 尝试当做纯 10 进制解析(如 "26")
if (uint.TryParse(idStr, out frameId))
{
return true;
}
// 3. 如果不是纯数字,但符合 16 进制特征(如 "1A"、"ABC"),尝试按 16 进制解析
// 使用 System.Globalization.NumberStyles.HexNumber
if (uint.TryParse(idStr, System.Globalization.NumberStyles.HexNumber, null, out frameId))
{
return true;
}
}
catch
{
return false;
}
return false;
}
[Browsable(false)]
public ulong 物理值转原始寄存器值(uint 通道号, ZDBC.DBCSignal 信号定义, double 实际物理值)
{
if (通道号 >= _maxChannels) return 0;
IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
try
{
Marshal.StructureToPtr(信号定义, pSignal, true);
Marshal.StructureToPtr(实际物理值, pValue, true);
lock (_channelLocks[通道号])
{
return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
}
}
finally
{
Marshal.FreeHGlobal(pSignal);
Marshal.FreeHGlobal(pValue);
}
}
#endregion
[Browsable(false)]
public void Dispose()
{
关闭CAN卡设备();
}
}
}