using CapMachine.Wpf.CanDrive; using CapMachine.Wpf.Services; using Prism.Mvvm; using System; using System.ComponentModel; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace CapMachine.Wpf.CanDrive.ZlgCan { /// /// 周立功 USBCANFD-200U 工程化封装(CAN/CANFD/LIN)。 /// public sealed class ZlgCanFd200uDriver : BindableBase, IDisposable { private readonly object _sync = new object(); private readonly ILogService _log; private static IntPtr _preloadedZlgcanHandle = IntPtr.Zero; private static string? _preloadedZlgcanError; private IntPtr _deviceHandle = IntPtr.Zero; private readonly IntPtr[] _canChannelHandles = new IntPtr[2]; private IntPtr _linChannelHandle = IntPtr.Zero; private CancellationTokenSource? _recvCts; private Task? _recvTask; private volatile bool _disposed; private readonly object _dbcSync = new object(); private ZlgDbcDatabase? _dbc; private ObservableCollection? _dbcModels; private Dictionary? _dbcModelIndex; private List? _cmdData; private int _cmdSendChannelIndex; private byte _cmdSendFrameType; /// /// 构造函数。 /// /// 日志服务。 public ZlgCanFd200uDriver(ILogService logService) { _log = logService; } /// /// 安全关闭设备与通道(需在 _sync 锁内调用)。 /// /// /// - 该方法会尽力调用 Reset/Close,并在异常时记录日志且继续执行释放流程; /// - 该方法不会停止接收线程,调用方应优先 StopReceiveLoop。 /// private bool _openState; /// /// 设备打开状态。 /// public bool OpenState { get { return _openState; } private set { _openState = value; RaisePropertyChanged(); } } private bool _isReceiving; /// /// 是否正在接收。 /// public bool IsReceiving { get { return _isReceiving; } private set { _isReceiving = value; RaisePropertyChanged(); } } private bool _isSendOk; /// /// 最近是否发生过“发送成功”(用于 UI 指示灯)。 /// public bool IsSendOk { get { return _isSendOk; } private set { _isSendOk = value; RaisePropertyChanged(); } } private bool _isReviceOk; /// /// 最近是否发生过“接收成功”(用于 UI 指示灯)。 /// public bool IsReviceOk { get { return _isReviceOk; } private set { _isReviceOk = value; RaisePropertyChanged(); } } private int _sendOkToken; private int _reviceOkToken; private void MarkSendOk(int holdMs = 800) { var token = Interlocked.Increment(ref _sendOkToken); IsSendOk = true; Task.Run(async () => { await Task.Delay(Math.Max(50, holdMs)); if (token == _sendOkToken) { IsSendOk = false; } }); } private void MarkReviceOk(int holdMs = 800) { var token = Interlocked.Increment(ref _reviceOkToken); IsReviceOk = true; Task.Run(async () => { await Task.Delay(Math.Max(50, holdMs)); if (token == _reviceOkToken) { IsReviceOk = false; } }); } /// /// CAN/CANFD 原始帧接收事件。 /// public event Action? CanFrameReceived; /// /// LIN 原始帧接收事件。 /// public event Action? LinFrameReceived; private bool _dbcDecodeEnabled; /// /// 是否启用 DBC 解码更新(接收帧触发)。 /// public bool DbcDecodeEnabled { get { return _dbcDecodeEnabled; } set { _dbcDecodeEnabled = value; RaisePropertyChanged(); } } private bool _isCycleSend; /// /// 是否启用“事件驱动发送”。 /// /// /// 与现有 ToomossCan/ToomossCanFD 行为对齐:只有 IsCycleSend=true 且 SchEnable=true 时, /// 才会在 CmdDataChanged 事件中触发增量下发。 /// public bool IsCycleSend { get { return _isCycleSend; } set { _isCycleSend = value; RaisePropertyChanged(); } } private bool _schEnable; /// /// 发送使能(与 UI 的调度表使能语义对齐)。 /// public bool SchEnable { get { return _schEnable; } set { _schEnable = value; RaisePropertyChanged(); } } /// /// 打开设备(不初始化 CAN/LIN 通道)。 /// /// 设备索引(通常 0)。 public void OpenDevice(uint deviceIndex) { ThrowIfDisposed(); lock (_sync) { if (_deviceHandle != IntPtr.Zero) { OpenState = true; return; } EnsureNativeDllExists("zlgcan.dll"); var deviceType = ZLGCAN.ZCAN_USBCANFD_200U; try { _deviceHandle = ZLGCAN.ZCAN_OpenDevice(deviceType, deviceIndex, 0); } catch (Exception ex) { var baseDir = AppContext.BaseDirectory; var dllFullPath = Path.Combine(baseDir, "zlgcan.dll"); var msg = $"ZCAN_OpenDevice 调用异常。deviceType={deviceType}, deviceIndex={deviceIndex}, " + $"Is64BitProcess={Environment.Is64BitProcess}, ProcessArch={RuntimeInformation.ProcessArchitecture}, " + $"DllPath={dllFullPath}, BaseDir={baseDir}。异常:{ex.Message}"; _log.Error(msg); throw new InvalidOperationException(msg, ex); } if (_deviceHandle == IntPtr.Zero) { var lastError = Marshal.GetLastWin32Error(); var lastErrorMsg = string.Empty; try { lastErrorMsg = new Win32Exception(lastError).Message; } catch { } var baseDir = AppContext.BaseDirectory; var dllFullPath = Path.Combine(baseDir, "zlgcan.dll"); var loadedModulePath = GetLoadedModulePath("zlgcan.dll") ?? string.Empty; var depDiag = string.Join(" | ", new[] { BuildDllDiag(baseDir, "zlgcan.dll"), BuildDllDiag(baseDir, "USB2XXX.dll"), BuildDllDiag(baseDir, "libusb-1.0.dll"), BuildDllDiag(baseDir, "zdbc.dll"), BuildDllDiag(baseDir, "kerneldlls\\USBCANFD.dll"), BuildDllDiag(baseDir, "kerneldlls\\CANDevCore.dll"), BuildDllDiag(baseDir, "kerneldlls\\CANDevice.dll"), BuildDllDiag(baseDir, "kerneldlls\\CANFDCOM.dll"), BuildDllDiag(baseDir, "kerneldlls\\CANFDNET.dll"), BuildDllDiag(baseDir, "kerneldlls\\ZPSCANFD.dll"), BuildDllDiag(baseDir, "kerneldlls\\USBCANFD800U.dll"), BuildDllDiag(baseDir, "kerneldlls\\USBCAN.dll"), BuildDllDiag(baseDir, "kerneldlls\\usbcan.dll"), }.Where(s => !string.IsNullOrWhiteSpace(s))); string dllVer = string.Empty; try { if (File.Exists(dllFullPath)) { var vi = FileVersionInfo.GetVersionInfo(dllFullPath); dllVer = vi?.FileVersion ?? string.Empty; } } catch { } var msg = $"ZCAN_OpenDevice 失败。deviceType={deviceType}, deviceIndex={deviceIndex}。" + $"Win32LastError={lastError}(0x{lastError:X}){(string.IsNullOrWhiteSpace(lastErrorMsg) ? string.Empty : $"({lastErrorMsg})")}。" + $"Is64BitProcess={Environment.Is64BitProcess}, ProcessArch={RuntimeInformation.ProcessArchitecture}。" + $"DllPath={dllFullPath}, DllVersion={dllVer}, LoadedModulePath={loadedModulePath}, BaseDir={baseDir}。" + $"PreloadError={_preloadedZlgcanError ?? string.Empty}。" + $"DirDllDiag={depDiag}。" + "请确认:1) 已安装ZLG驱动;2) 设备已连接且未被其它程序占用;3) 程序位数与 zlgcan.dll 匹配;4) 设备类型/索引正确。"; _log.Error(msg); throw new InvalidOperationException(msg); } OpenState = true; } } /// /// 预加载指定路径的原生 DLL,尽量避免被 PATH 中的同名 DLL 干扰。 /// /// DLL 完整路径。 private static void TryPreloadNativeDll(string dllFullPath) { try { if (_preloadedZlgcanHandle != IntPtr.Zero) { return; } if (!string.IsNullOrWhiteSpace(_preloadedZlgcanError)) { return; } _preloadedZlgcanHandle = NativeLibrary.Load(dllFullPath); } catch (Exception ex) { _preloadedZlgcanError = $"{ex.GetType().Name}: {ex.Message}"; } } /// /// 构造输出目录下某个 DLL 的诊断信息(架构/大小/版本)。 /// /// 输出目录。 /// DLL 文件名。 /// 诊断信息字符串;不存在返回 "xxx:not_found";异常返回 null。 private static string? BuildDllDiag(string baseDir, string fileName) { try { if (string.IsNullOrWhiteSpace(baseDir) || string.IsNullOrWhiteSpace(fileName)) { return null; } var full = Path.Combine(baseDir, fileName); if (!File.Exists(full)) { return $"{fileName}:not_found"; } var arch = TryGetPeArchitecture(full) ?? string.Empty; long size = 0; try { size = new FileInfo(full).Length; } catch { } var ver = string.Empty; try { var vi = FileVersionInfo.GetVersionInfo(full); ver = vi?.FileVersion ?? string.Empty; } catch { } return $"{fileName}:arch={arch},size={size},ver={ver}"; } catch { return null; } } /// /// 获取当前进程中已加载模块的完整路径。 /// /// 模块文件名(如 zlgcan.dll)。 /// 完整路径;获取失败返回 null。 private static string? GetLoadedModulePath(string moduleFileName) { try { if (string.IsNullOrWhiteSpace(moduleFileName)) { return null; } using var p = Process.GetCurrentProcess(); foreach (ProcessModule m in p.Modules) { if (string.Equals(m.ModuleName, moduleFileName, StringComparison.OrdinalIgnoreCase)) { return m.FileName; } } return null; } catch { return null; } } /// /// 打开设备并初始化 CANFD 通道。 /// /// 设备索引(通常 0)。 /// 通道0配置。 /// 通道1配置(可为 null 表示不初始化)。 public void OpenAndInitCan(uint deviceIndex, ZlgCanFdChannelOptions channel0, ZlgCanFdChannelOptions? channel1 = null) { ThrowIfDisposed(); lock (_sync) { if (_deviceHandle == IntPtr.Zero) { OpenDevice(deviceIndex); } try { InitCanChannelInternal(0, channel0); _canChannelHandles[0] = StartCanChannelInternal(0); if (channel1 != null) { InitCanChannelInternal(1, channel1); _canChannelHandles[1] = StartCanChannelInternal(1); } // 合并接收:这是设备级能力,按通道0的配置为准(两通道同时开启/关闭) var mergeEnable = channel0.EnableMergeReceive; var mergeRet = ZLGCAN.ZCAN_SetValue(_deviceHandle, "0/set_device_recv_merge", mergeEnable ? "1" : "0"); if (mergeRet != 1) { _log.Warn("设置合并接收失败(0/set_device_recv_merge)。将继续运行,但接收模式可能不符合预期。"); } } catch { SafeClose_NoLock(); throw; } } } /// /// 初始化并启动 LIN 通道。 /// /// LIN 通道索引(通常 0)。 /// LIN 初始化参数。 public void OpenAndInitLin(uint linIndex, ZlgLinChannelOptions options) { ThrowIfDisposed(); lock (_sync) { if (_deviceHandle == IntPtr.Zero) { OpenDevice(0); } if (_linChannelHandle != IntPtr.Zero) { throw new InvalidOperationException("LIN 通道已初始化。"); } var cfg = new ZLGCAN.ZCAN_LIN_INIT_CONFIG { linMode = (byte)(options.IsMaster ? 1 : 0), chkSumMode = options.ChecksumMode, maxLength = options.MaxLength, reserved = 0, libBaud = options.BaudRate }; IntPtr pCfg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_LIN_INIT_CONFIG))); try { Marshal.StructureToPtr(cfg, pCfg, false); _linChannelHandle = ZLGCAN.ZCAN_InitLIN(_deviceHandle, linIndex, pCfg); } finally { Marshal.FreeHGlobal(pCfg); } if (_linChannelHandle == IntPtr.Zero) { throw new InvalidOperationException("ZCAN_InitLIN 失败。"); } var ret = ZLGCAN.ZCAN_StartLIN(_linChannelHandle); if (ret != 1) { throw new InvalidOperationException("ZCAN_StartLIN 失败。"); } } } /// /// 启动后台接收线程。 /// /// 是否启用合并接收(ZCAN_ReceiveData)。建议与通道初始化时的 EnableMergeReceive 保持一致。 /// 接收缓冲最大帧数(合并接收固定数组大小,过小会丢帧)。 public void StartReceiveLoop(bool mergeReceive, int bufferFrames = 100) { ThrowIfDisposed(); lock (_sync) { if (_deviceHandle == IntPtr.Zero) { throw new InvalidOperationException("设备未打开。"); } if (_recvTask != null) { throw new InvalidOperationException("接收线程已启动。"); } if (bufferFrames <= 0) bufferFrames = 1; _recvCts = new CancellationTokenSource(); var token = _recvCts.Token; _recvTask = Task.Run(() => { IsReceiving = true; try { if (mergeReceive) { ReceiveLoop_Merge(token, bufferFrames); } else { ReceiveLoop_PerChannel(token, bufferFrames); } } catch (Exception ex) { _log.Error($"ZLG 接收线程异常退出:{ex.Message}"); } finally { IsReceiving = false; } }, token); } } /// /// 加载 DBC 文件,并生成 UI 绑定用的信号集合。 /// /// DBC 文件路径。 /// 是否启用异步解析。 /// 是否合并加载。 /// 协议类型。 /// CanDbcModel 集合。 public ObservableCollection StartDbc(string dbcPath, bool enableAsyncAnalyse = true, bool merge = false, byte protocolType = ZDBC.PROTOCOL_OTHER) { ThrowIfDisposed(); lock (_dbcSync) { _dbc?.Dispose(); _dbc = new ZlgDbcDatabase(_log); _dbc.Load(dbcPath, enableAsyncAnalyse, merge, protocolType); _dbcModels = _dbc.BuildCanDbcModels(); _dbcModelIndex = BuildDbcModelIndex(_dbcModels); DbcDecodeEnabled = true; return _dbcModels; } } /// /// 设置并订阅要发送的指令集合(事件驱动)。 /// /// 指令集合。 /// 发送通道(0/1)。 /// 帧类型:ZDBC.FT_CAN 或 ZDBC.FT_CANFD。 public void LoadCmdDataToDrive(List cmdData, int channelIndex, byte frameType) { ThrowIfDisposed(); lock (_dbcSync) { if (_cmdData != null) { foreach (var old in _cmdData) { old.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler; } } _cmdData = cmdData; _cmdSendChannelIndex = channelIndex; _cmdSendFrameType = frameType; if (_cmdData != null) { foreach (var item in _cmdData) { item.CanCmdDataChangedHandler += CmdData_CanCmdDataChangedHandler; } } } } /// /// 事件驱动回调:某个 MsgName 的信号值变更时,仅增量编码并下发该消息。 /// /// 发送方。 /// 发生变化的消息名称。 private void CmdData_CanCmdDataChangedHandler(object? sender, string msgName) { try { if (!IsCycleSend) return; if (!SchEnable) return; SendOneMsgByCmdData(msgName, _cmdSendChannelIndex, _cmdSendFrameType); } catch (Exception ex) { _log.Warn($"事件驱动发送异常:{ex.Message}"); } } /// /// 根据 MsgName,从当前 CmdData 中取出信号值,编码并发送一帧。 /// /// 消息名称。 /// 发送通道。 /// 帧类型:ZDBC.FT_CAN 或 ZDBC.FT_CANFD。 public void SendOneMsgByCmdData(string msgName, int channelIndex, byte frameType) { ThrowIfDisposed(); ZlgDbcDatabase? dbc; List? cmdData; lock (_dbcSync) { dbc = _dbc; cmdData = _cmdData; } if (dbc == null || !dbc.IsLoaded) { return; } if (cmdData == null || cmdData.Count == 0) { return; } if (string.IsNullOrWhiteSpace(msgName)) { return; } var dict = new Dictionary(StringComparer.Ordinal); foreach (var item in cmdData) { if (!string.Equals(item.MsgName, msgName, StringComparison.Ordinal)) { continue; } if (string.IsNullOrWhiteSpace(item.SignalName)) { continue; } dict[item.SignalName] = item.SignalCmdValue; } if (dict.Count == 0) { return; } var (canId, data, dataLen) = dbc.EncodeToRawFrame(msgName, dict, frameType); if (frameType == ZDBC.FT_CAN) { SendCan(channelIndex, canId, data, requestTxEcho: true); return; } if (frameType == ZDBC.FT_CANFD) { SendCanFd(channelIndex, canId, data, requestTxEcho: true); return; } throw new ArgumentOutOfRangeException(nameof(frameType), "frameType 仅支持 ZDBC.FT_CAN=0 或 ZDBC.FT_CANFD=1。"); } /// /// 停止后台接收线程。 /// public void StopReceiveLoop() { lock (_sync) { if (_recvCts == null || _recvTask == null) { return; } try { _recvCts.Cancel(); _recvTask.Wait(TimeSpan.FromSeconds(2)); } catch (Exception ex) { _log.Warn($"停止接收线程等待超时或异常:{ex.Message}"); } finally { _recvCts.Dispose(); _recvCts = null; _recvTask = null; } } } /// /// 发送 CAN 报文。 /// /// 通道索引(0/1)。 /// 包含扩展帧标志位的 can_id(可用 ZlgCanIdHelper.MakeCanId 生成)。 /// 数据(0~8字节)。 /// 是否请求发送回显。 /// 发送方式,0=正常发送,1=单次发送,2=自发自收,3=单次自发自收。 public uint SendCan(int channelIndex, uint canId, byte[] data, bool requestTxEcho = false, uint transmitType = 0) { ThrowIfDisposed(); var chn = GetCanChannelHandleOrThrow(channelIndex); var frame = new ZLGCAN.can_frame { can_id = canId, can_dlc = (byte)Math.Min(8, data?.Length ?? 0), __pad = 0, __res0 = 0, __res1 = 0, data = new byte[8] }; if (data != null && data.Length > 0) { Array.Copy(data, frame.data, Math.Min(8, data.Length)); } if (requestTxEcho) { frame.__pad |= 0x20; } var tx = new ZLGCAN.ZCAN_Transmit_Data { frame = frame, transmit_type = transmitType }; IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Transmit_Data))); try { Marshal.StructureToPtr(tx, pTx, false); var ret = ZLGCAN.ZCAN_Transmit(chn, pTx, 1); if (ret > 0) { MarkSendOk(); } return ret; } finally { Marshal.FreeHGlobal(pTx); } } /// /// 发送 CANFD 报文。 /// /// 通道索引(0/1)。 /// 包含扩展帧标志位的 can_id(可用 ZlgCanIdHelper.MakeCanId 生成)。 /// 数据(0~64字节)。 /// 是否请求发送回显。 /// 发送方式,0=正常发送,1=单次发送,2=自发自收,3=单次自发自收。 public uint SendCanFd(int channelIndex, uint canId, byte[] data, bool requestTxEcho = false, uint transmitType = 0) { ThrowIfDisposed(); var chn = GetCanChannelHandleOrThrow(channelIndex); var len = (byte)Math.Min(64, data?.Length ?? 0); var frame = new ZLGCAN.canfd_frame { can_id = canId, len = len, flags = 0, __res0 = 0, __res1 = 0, data = new byte[64] }; if (data != null && data.Length > 0) { Array.Copy(data, frame.data, Math.Min(64, data.Length)); } if (requestTxEcho) { frame.flags |= 0x20; } var tx = new ZLGCAN.ZCAN_TransmitFD_Data { frame = frame, transmit_type = transmitType }; IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_TransmitFD_Data))); try { Marshal.StructureToPtr(tx, pTx, false); var ret = ZLGCAN.ZCAN_TransmitFD(chn, pTx, 1); if (ret > 0) { MarkSendOk(); } return ret; } finally { Marshal.FreeHGlobal(pTx); } } /// /// 设置设备定时发送(CAN)。 /// /// 通道索引。 /// 定时任务索引。 /// 是否使能。 /// 周期(ms)。 /// can_id。 /// 数据(0~8)。 public void ConfigureAutoSendCan(int channelIndex, ushort taskIndex, bool enable, uint intervalMs, uint canId, byte[] data) { ThrowIfDisposed(); if (_deviceHandle == IntPtr.Zero) { throw new InvalidOperationException("设备未打开。"); } var frame = new ZLGCAN.can_frame { can_id = canId, can_dlc = (byte)Math.Min(8, data?.Length ?? 0), __pad = 0x20, // 默认发送回显 __res0 = 0, __res1 = 0, data = new byte[8] }; if (data != null && data.Length > 0) { Array.Copy(data, frame.data, Math.Min(8, data.Length)); } var obj = new ZLGCAN.ZCAN_AUTO_TRANSMIT_OBJ { enable = (ushort)(enable ? 1 : 0), index = taskIndex, interval = intervalMs, obj = new ZLGCAN.ZCAN_Transmit_Data { frame = frame, transmit_type = 0 } }; var path = string.Format("{0}/auto_send", channelIndex); var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, ref obj); if (ret != 1) { throw new InvalidOperationException($"配置定时发送失败:{path}"); } } /// /// 启动通道定时发送任务(apply_auto_send)。 /// /// 通道索引。 public void ApplyAutoSend(int channelIndex) { ThrowIfDisposed(); if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("设备未打开。"); var path = string.Format("{0}/apply_auto_send", channelIndex); var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, "0"); if (ret != 1) { throw new InvalidOperationException($"启动定时发送失败:{path}"); } } /// /// 清空通道定时发送任务(clear_auto_send)。 /// /// 通道索引。 public void ClearAutoSend(int channelIndex) { ThrowIfDisposed(); if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("设备未打开。"); var path = string.Format("{0}/clear_auto_send", channelIndex); var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, "0"); if (ret != 1) { throw new InvalidOperationException($"清空定时发送失败:{path}"); } } /// /// 配置 LIN 发布表。 /// /// 发布配置集合。 public void SetLinPublish(IEnumerable publishCfg) { ThrowIfDisposed(); if (_linChannelHandle == IntPtr.Zero) throw new InvalidOperationException("LIN 未初始化。"); var list = publishCfg?.ToList() ?? new List(); if (list.Count == 0) { return; } int size = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_LIN_PUBLISH_CFG)); IntPtr pArr = Marshal.AllocHGlobal(size * list.Count); try { for (int i = 0; i < list.Count; i++) { var item = list[i]; if (item.data == null || item.data.Length != 8) { item.data = new byte[8]; } if (item.reserved == null || item.reserved.Length != 5) { item.reserved = new byte[5]; } Marshal.StructureToPtr(item, IntPtr.Add(pArr, i * size), false); } var ret = ZLGCAN.ZCAN_SetLINPublish(_linChannelHandle, pArr, (uint)list.Count); if (ret != 1) { throw new InvalidOperationException("ZCAN_SetLINPublish 失败。"); } } finally { Marshal.FreeHGlobal(pArr); } } /// /// 配置 LIN 订阅表。 /// /// 订阅配置集合。 public void SetLinSubscribe(IEnumerable subscribeCfg) { ThrowIfDisposed(); if (_linChannelHandle == IntPtr.Zero) throw new InvalidOperationException("LIN 未初始化。"); var list = subscribeCfg?.ToList() ?? new List(); if (list.Count == 0) { return; } int size = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_LIN_SUBSCIBE_CFG)); IntPtr pArr = Marshal.AllocHGlobal(size * list.Count); try { for (int i = 0; i < list.Count; i++) { var item = list[i]; if (item.reserved == null || item.reserved.Length != 5) { item.reserved = new byte[5]; } Marshal.StructureToPtr(item, IntPtr.Add(pArr, i * size), false); } var ret = ZLGCAN.ZCAN_SetLINSubscribe(_linChannelHandle, pArr, (uint)list.Count); if (ret != 1) { throw new InvalidOperationException("ZCAN_SetLINSubscribe 失败。"); } } finally { Marshal.FreeHGlobal(pArr); } } /// /// 关闭设备。 /// public void Close() { lock (_sync) { StopReceiveLoop(); SafeClose_NoLock(); } lock (_dbcSync) { if (_cmdData != null) { foreach (var item in _cmdData) { item.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler; } } _cmdData = null; _dbcModels = null; _dbcModelIndex = null; _dbc?.Dispose(); _dbc = null; } } /// public void Dispose() { if (_disposed) return; _disposed = true; try { Close(); } catch { // ignored } } private void SafeClose_NoLock() { try { if (_linChannelHandle != IntPtr.Zero) { var ret = ZLGCAN.ZCAN_ResetLIN(_linChannelHandle); if (ret != 1) { _log.Warn("ZCAN_ResetLIN 失败。"); } } } catch (Exception ex) { _log.Warn($"关闭 LIN 异常:{ex.Message}"); } finally { _linChannelHandle = IntPtr.Zero; } for (int i = 0; i < _canChannelHandles.Length; i++) { try { if (_canChannelHandles[i] != IntPtr.Zero) { var ret = ZLGCAN.ZCAN_ResetCAN(_canChannelHandles[i]); if (ret != 1) { _log.Warn($"ZCAN_ResetCAN 失败,通道{i}。"); } } } catch (Exception ex) { _log.Warn($"关闭 CAN 通道{i}异常:{ex.Message}"); } finally { _canChannelHandles[i] = IntPtr.Zero; } } try { if (_deviceHandle != IntPtr.Zero) { var ret = ZLGCAN.ZCAN_CloseDevice(_deviceHandle); if (ret != 1) { _log.Warn("ZCAN_CloseDevice 失败。"); } } } catch (Exception ex) { _log.Warn($"关闭设备异常:{ex.Message}"); } finally { _deviceHandle = IntPtr.Zero; OpenState = false; } } /// /// 初始化指定 CANFD 通道(仅设置参数并 InitCAN,不负责 StartCAN)。 /// /// 通道索引。 /// 通道初始化参数。 private void InitCanChannelInternal(int chnIdx, ZlgCanFdChannelOptions options) { var path = string.Format("{0}/canfd_abit_baud_rate", chnIdx); var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.ArbitrationBaudRate.ToString()); if (ret != 1) { throw new InvalidOperationException($"设置仲裁域波特率失败:{path}"); } path = string.Format("{0}/canfd_dbit_baud_rate", chnIdx); ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.DataBaudRate.ToString()); if (ret != 1) { throw new InvalidOperationException($"设置数据域波特率失败:{path}"); } path = string.Format("{0}/initenal_resistance", chnIdx); ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.EnableInternalResistance ? "1" : "0"); if (ret != 1) { throw new InvalidOperationException($"设置终端电阻失败:{path}"); } if (options.EnableBusUsage) { path = string.Format("{0}/set_bus_usage_enable", chnIdx); ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, "1"); if (ret != 1) { _log.Warn($"启用总线利用率失败:{path}"); } path = string.Format("{0}/set_bus_usage_period", chnIdx); ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.BusUsagePeriodMs.ToString()); if (ret != 1) { _log.Warn($"设置总线利用率周期失败:{path}"); } } var initCfg = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG { can_type = 1, config = new ZLGCAN._ZCAN_CHANNEL_INIT_CONFIG { canfd = new ZLGCAN._ZCAN_CHANNEL_CANFD_INIT_CONFIG { acc_code = 0, acc_mask = 0xFFFFFFFF, abit_timing = 0, dbit_timing = 0, brp = 0, filter = 0, mode = (byte)(options.ListenOnly ? 1 : 0), pad = 0, reserved = 0 } } }; var chnHandle = ZLGCAN.ZCAN_InitCAN(_deviceHandle, (uint)chnIdx, ref initCfg); if (chnHandle == IntPtr.Zero) { throw new InvalidOperationException($"初始化 CANFD 通道失败:{chnIdx}"); } _canChannelHandles[chnIdx] = chnHandle; } /// /// 启动指定 CAN 通道。 /// /// 通道索引。 /// 通道句柄。 private IntPtr StartCanChannelInternal(int chnIdx) { var h = _canChannelHandles[chnIdx]; if (h == IntPtr.Zero) { throw new InvalidOperationException("CAN 通道句柄为空。"); } var ret = ZLGCAN.ZCAN_StartCAN(h); if (ret != 1) { throw new InvalidOperationException($"启动 CAN 通道失败:{chnIdx}"); } return h; } /// /// 获取 CAN 通道句柄(不存在则抛异常)。 /// /// 通道索引。 /// 通道句柄。 private IntPtr GetCanChannelHandleOrThrow(int channelIndex) { if (channelIndex < 0 || channelIndex >= _canChannelHandles.Length) { throw new ArgumentOutOfRangeException(nameof(channelIndex)); } var h = _canChannelHandles[channelIndex]; if (h == IntPtr.Zero) { throw new InvalidOperationException($"CAN 通道{channelIndex}未初始化。"); } return h; } /// /// 普通接收模式:按通道轮询 CAN/CANFD 缓冲区读取。 /// /// 取消令牌。 /// 每次接收的最大帧数。 private void ReceiveLoop_PerChannel(CancellationToken token, int bufferFrames) { const int WaitMs = 10; int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data)); int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data)); IntPtr pCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferFrames); IntPtr pCanfdBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferFrames); try { while (!token.IsCancellationRequested) { for (int ch = 0; ch < _canChannelHandles.Length; ch++) { var handle = _canChannelHandles[ch]; if (handle == IntPtr.Zero) continue; uint canNum = ZLGCAN.ZCAN_GetReceiveNum(handle, 0); if (canNum > 0) { uint actual = ZLGCAN.ZCAN_Receive(handle, pCanBuffer, (uint)bufferFrames, WaitMs); for (int i = 0; i < actual; i++) { IntPtr pCur = IntPtr.Add(pCanBuffer, i * canStructSize); var rec = Marshal.PtrToStructure(pCur); RaiseCanFrame(ch, rec.timestamp, false, rec.frame.can_id, rec.frame.data, rec.frame.can_dlc, (rec.frame.__pad & 0x20) == 0x20); } } uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(handle, 1); if (canfdNum > 0) { uint actual = ZLGCAN.ZCAN_ReceiveFD(handle, pCanfdBuffer, (uint)bufferFrames, WaitMs); for (int i = 0; i < actual; i++) { IntPtr pCur = IntPtr.Add(pCanfdBuffer, i * canfdStructSize); var rec = Marshal.PtrToStructure(pCur); RaiseCanFrame(ch, rec.timestamp, true, rec.frame.can_id, rec.frame.data, rec.frame.len, (rec.frame.flags & 0x20) == 0x20); } } ZLGCAN.ZCAN_CHANNEL_ERR_INFO err = new ZLGCAN.ZCAN_CHANNEL_ERR_INFO { error_code = 0, passive_ErrData = new byte[3], arLost_ErrData = 0 }; try { ZLGCAN.ZCAN_ReadChannelErrInfo(handle, ref err); if (err.error_code != 0) { _log.Warn($"CAN通道{ch}错误码:0x{err.error_code:X}"); } } catch { // ignore } } Thread.Sleep(10); } } finally { Marshal.FreeHGlobal(pCanfdBuffer); Marshal.FreeHGlobal(pCanBuffer); } } /// /// 合并接收模式:通过设备级 API(ZCAN_ReceiveData)统一接收 CAN/CANFD/LIN。 /// /// 取消令牌。 /// 接收缓存容量。 private void ReceiveLoop_Merge(CancellationToken token, int bufferFrames) { int dataObjSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANDataObj)); int canfdSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANCANFDData)); int linSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANLINData)); IntPtr pDataObjs = Marshal.AllocHGlobal(dataObjSize * bufferFrames); IntPtr pCanfdBuffer = Marshal.AllocHGlobal(canfdSize); IntPtr pLinBuffer = Marshal.AllocHGlobal(linSize); try { while (!token.IsCancellationRequested) { uint recvNum = ZLGCAN.ZCAN_GetReceiveNum(_deviceHandle, 2); if (recvNum == 0) { Thread.Sleep(10); continue; } uint actualRecv = ZLGCAN.ZCAN_ReceiveData(_deviceHandle, pDataObjs, (uint)bufferFrames, 10); if (actualRecv == 0) { continue; } for (int i = 0; i < actualRecv; i++) { IntPtr pCur = IntPtr.Add(pDataObjs, i * dataObjSize); var obj = Marshal.PtrToStructure(pCur); switch (obj.dataType) { case 1: Marshal.Copy(obj.data, 0, pCanfdBuffer, canfdSize); var canfdData = Marshal.PtrToStructure(pCanfdBuffer); bool isFd = (canfdData.flag & 1) == 1; bool isTx = (canfdData.flag & (1 << 9)) != 0; RaiseCanFrame(obj.chnl, canfdData.timeStamp, isFd, canfdData.frame.can_id, canfdData.frame.data, canfdData.frame.len, isTx); break; case 4: if (obj.data == null || obj.data.Length < linSize) { break; } Marshal.Copy(obj.data, 0, pLinBuffer, linSize); var linData = Marshal.PtrToStructure(pLinBuffer); RaiseLinFrame(obj.chnl, linData.rxData.timeStamp, linData.pid.rawVal, linData.rxData.data, linData.rxData.datalen, linData.rxData.dir); break; } } Thread.Sleep(1); } } finally { Marshal.FreeHGlobal(pLinBuffer); Marshal.FreeHGlobal(pCanfdBuffer); Marshal.FreeHGlobal(pDataObjs); } } /// /// 将接收到的 CAN/CANFD 帧转换为托管对象并触发事件。 /// /// 通道号。 /// 时间戳(us)。 /// 是否 CANFD。 /// can_id(含标志位)。 /// 原始数据缓冲区。 /// 数据长度。 /// 是否为发送回显(Tx)。 private void RaiseCanFrame(int channel, ulong timestamp, bool isCanFd, uint canId, byte[] data, byte dlc, bool isTx) { try { var len = Math.Min(isCanFd ? 64 : 8, Math.Min((int)dlc, data?.Length ?? 0)); var bytes = new byte[len]; if (len > 0 && data != null) { Array.Copy(data, bytes, len); } CanFrameReceived?.Invoke(new ZlgCanRxFrame(channel, isCanFd, canId, bytes, timestamp, isTx)); if (isTx) { MarkSendOk(); } else { MarkReviceOk(); } if (DbcDecodeEnabled) { TryDecodeAndUpdateModels(canId, bytes, isCanFd ? (byte)ZDBC.FT_CANFD : (byte)ZDBC.FT_CAN); } } catch (Exception ex) { _log.Warn($"派发 CAN 帧事件异常:{ex.Message}"); } } /// /// 将接收到的 LIN 帧转换为托管对象并触发事件。 /// /// 通道号。 /// 时间戳(us)。 /// PID。 /// 原始数据缓冲区。 /// 数据长度。 /// 方向(由设备回传)。 private void RaiseLinFrame(int channel, ulong timestamp, byte pid, byte[] data, byte datalen, byte dir) { try { var len = Math.Min(8, Math.Min((int)datalen, data?.Length ?? 0)); var bytes = new byte[len]; if (len > 0 && data != null) { Array.Copy(data, bytes, len); } LinFrameReceived?.Invoke(new ZlgLinRxFrame(channel, pid, bytes, timestamp, dir)); } catch (Exception ex) { _log.Warn($"派发 LIN 帧事件异常:{ex.Message}"); } } /// /// 校验指定原生 DLL 是否存在于程序输出目录(AppContext.BaseDirectory)。 /// /// DLL 文件名。 /// 找不到 DLL。 private static void EnsureNativeDllExists(string dllName) { var baseDir = AppContext.BaseDirectory; var full = Path.Combine(baseDir, dllName); if (!File.Exists(full)) { throw new FileNotFoundException($"未找到 {dllName},请将其复制到程序输出目录:{baseDir}", full); } TryPreloadNativeDll(full); if (_preloadedZlgcanHandle == IntPtr.Zero && !string.IsNullOrWhiteSpace(_preloadedZlgcanError)) { throw new InvalidOperationException($"预加载 {dllName} 失败:{_preloadedZlgcanError}。DLL 路径:{full}"); } var dllArch = TryGetPeArchitecture(full); if (!string.IsNullOrWhiteSpace(dllArch)) { var procArch = Environment.Is64BitProcess ? "x64" : "x86"; if (Environment.Is64BitProcess && !string.Equals(dllArch, "x64", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException($"{dllName} 位数不匹配:当前进程为 {procArch},但 DLL 架构为 {dllArch}。请替换为 x64 版本 DLL,或将程序改为 x86 运行。DLL 路径:{full}"); } if (!Environment.Is64BitProcess && !string.Equals(dllArch, "x86", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException($"{dllName} 位数不匹配:当前进程为 {procArch},但 DLL 架构为 {dllArch}。请替换为 x86 版本 DLL,或将程序改为 x64 运行。DLL 路径:{full}"); } } } /// /// 尝试读取 PE 头判断 DLL 架构。 /// /// DLL 完整路径。 /// 返回 "x86"/"x64"/"arm64"/"unknown(...)";读取失败返回 null。 private static string? TryGetPeArchitecture(string dllFullPath) { try { using var fs = new FileStream(dllFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var br = new BinaryReader(fs); if (fs.Length < 0x40) { return null; } var mz = br.ReadUInt16(); if (mz != 0x5A4D) { return null; } fs.Position = 0x3C; var peOffset = br.ReadInt32(); if (peOffset <= 0 || peOffset > fs.Length - 6) { return null; } fs.Position = peOffset; var peSig = br.ReadUInt32(); if (peSig != 0x00004550) { return null; } var machine = br.ReadUInt16(); return machine switch { 0x014c => "x86", 0x8664 => "x64", 0xAA64 => "arm64", _ => $"unknown(0x{machine:X})" }; } catch { return null; } } private static Dictionary BuildDbcModelIndex(IEnumerable models) { var dict = new Dictionary(StringComparer.Ordinal); foreach (var m in models) { if (string.IsNullOrWhiteSpace(m.MsgName) || string.IsNullOrWhiteSpace(m.SignalName)) { continue; } dict[BuildMsgSigKey(m.MsgName, m.SignalName)] = m; } return dict; } private static string BuildMsgSigKey(string msgName, string signalName) { return msgName + "\u0000" + signalName; } private void TryDecodeAndUpdateModels(uint canId, byte[] payload, byte frameType) { ZlgDbcDatabase? dbc; Dictionary? index; lock (_dbcSync) { dbc = _dbc; index = _dbcModelIndex; } if (dbc == null || !dbc.IsLoaded || index == null) { return; } try { var (msgName, signals) = dbc.DecodeRawFrame(canId, payload, frameType); if (string.IsNullOrWhiteSpace(msgName) || signals == null || signals.Count == 0) { return; } foreach (var kv in signals) { var key = BuildMsgSigKey(msgName, kv.Key); if (index.TryGetValue(key, out var model)) { model.SignalRtValue = kv.Value.ToString(); } } } catch (Exception ex) { _log.Warn($"DBC 解码异常:{ex.Message}"); } } /// /// 若对象已释放则抛异常。 /// private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(ZlgCanFd200uDriver)); } } } /// /// CAN/CANFD 接收帧。 /// public readonly struct ZlgCanRxFrame { /// /// 构造 CAN/CANFD 接收帧。 /// /// 通道号。 /// 是否 CANFD。 /// can_id(含标志位)。 /// 数据(已截断为实际长度)。 /// 时间戳(us)。 /// 是否为发送回显。 public ZlgCanRxFrame(int channel, bool isCanFd, uint canId, byte[] data, ulong timestampUs, bool isTx) { Channel = channel; IsCanFd = isCanFd; CanId = canId; Data = data; TimestampUs = timestampUs; IsTx = isTx; } public int Channel { get; } public bool IsCanFd { get; } public uint CanId { get; } public byte[] Data { get; } public ulong TimestampUs { get; } public bool IsTx { get; } } /// /// LIN 接收帧。 /// public readonly struct ZlgLinRxFrame { /// /// 构造 LIN 接收帧。 /// /// 通道号。 /// PID。 /// 数据(已截断为实际长度)。 /// 时间戳(us)。 /// 方向(由设备回传)。 public ZlgLinRxFrame(int channel, byte pid, byte[] data, ulong timestampUs, byte dir) { Channel = channel; Pid = pid; Data = data; TimestampUs = timestampUs; Dir = dir; } public int Channel { get; } public byte Pid { get; } public byte[] Data { get; } public ulong TimestampUs { get; } /// /// 方向:由设备回传,0/1 具体含义以 ZLG 文档为准。 /// public byte Dir { get; } } }