using Common.Attributes; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using TSMaster; namespace TSMasterCAN { /// /// TSMaster CAN/CANFD 总线控制类,基于 TSMaster API 实现总线连接管理、 /// DBC 信号读写、报文发送(单次/循环)、日志记录等功能,暴露给命令树供测试步骤调用。 /// [ACPCommand] public class CAN { public static event Action ConnectEvent; public static event Action DisConnectEvent; public static bool ConnectFlag { get; set; } = false; /// 初始化互斥锁:TSMaster 原生库每个进程只允许初始化一次 private static readonly object _initLock = new object(); /// 初始化结果缓存(0 表示已成功初始化,无需再次初始化) private static int _initResult = int.MinValue; /// /// 驱动是否刚被释放、需要重新下发 DBC。 /// /// Release()/UnLoadDBC 成功后置 true,任一次 DBC 加载成功后置 false; /// 上层(如 CANModule 点「初始化驱动」后)据此决定是否重载 DBC, /// 避免拿残留的托管缓存去匹配已经清空的原生数据库(返回 93)。 /// /// public static bool NeedReloadDbc { get; private set; } /// /// 初始化CAN驱动。 /// /// TSMaster 原生库每个进程只允许初始化一次:多工位(一拖三)各自调用时, /// 重复 initialize_lib_tsmaster 会重建原生引擎,正在运行的循环发送表与回调队列随之失效, /// 下一次发报文即触发原生访问冲突闪退。因此这里做进程级一次性保护,后续调用直接复用首次结果。 /// /// /// /// /// [Browsable(false)] public static int Init(string ProjectName, string? filePath = null) { try { lock (_initLock) { // 已成功初始化则直接复用:重复 initialize 会重建原生引擎, // 正在循环发送的报文表随即失效。初始化失败则允许后续重试。 if (_initResult == 0) return 0; _initResult = string.IsNullOrEmpty(filePath) ? TsMasterApi.initialize_lib_tsmaster(ProjectName) : TsMasterApi.initialize_lib_tsmaster_with_project(ProjectName, Path.GetFullPath(filePath)); return _initResult; } } catch (Exception ex) { // 原生 API 异常统一转为返回码,不得向外传播导致进程闪退 Debug.WriteLine($"CAN.Init 异常:{ex.Message}"); return -1; } } /// /// 释放CAN驱动。 /// 释放后清除初始化缓存与回调注册状态,允许下一次 Init 重新初始化。 /// [Browsable(false)] public static void Release() { try { lock (_nativeSendLock) { lock (_initLock) { TsMasterApi.finalize_lib_tsmaster(); _initResult = int.MinValue; // 驱动释放后 TSMaster 内部 DBC 数据库全部失效: // 本地加载记录与托管报文缓存都要清空,并标记需要重新下发 DBC, // 否则后续发送会拿残留缓存去匹配空数据库,返回 93(message not found in database) _loadedDbcPool.Clear(); DBCParse.ClearMsgDatabase(); NeedReloadDbc = true; ResetListenerRegistration(); } } } catch (Exception ex) { Debug.WriteLine($"CAN.Release 异常:{ex.Message}"); } } public static int obj = 0; #region 一拖多引用计数 /// 作用域名缺失时的兜底键 private const string DefaultScopeKey = "__default__"; /// /// CAN 卡的作用域引用表(作用域名 → 引用计数)。 /// /// CAN 卡是一拖多共享硬件:多个工位共用同一个 TSMaster 实例, /// 必须等最后一个工位销毁后才能停止循环报文、断开总线并释放原生驱动。 /// /// private static readonly ConcurrentDictionary _scopeRefs = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// 当前仍持有 CAN 卡引用的作用域数量。 public static int ScopeRefCount => _scopeRefs.Count; private static string NormalizeScopeKey(string? scopeName) => string.IsNullOrWhiteSpace(scopeName) ? DefaultScopeKey : scopeName!; /// /// 登记一个作用域对 CAN 卡的引用,需与 成对调用。 /// /// 作用域名(通常为 SystemConfig.Title) public static void AcquireScope(string? scopeName) { var key = NormalizeScopeKey(scopeName); _scopeRefs.AddOrUpdate(key, 1, (_, count) => count + 1); Debug.WriteLine($"CAN.AcquireScope:{key},当前引用数 {_scopeRefs.Count}"); } /// /// 注销一个作用域对 CAN 卡的引用;最后一个引用移除时停止循环报文、断开总线并释放驱动。 /// /// 作用域名(通常为 SystemConfig.Title) /// 剩余引用数,0 表示已停止 CAN 卡 public static int ReleaseScope(string? scopeName) { var key = NormalizeScopeKey(scopeName); while (true) { if (!_scopeRefs.TryGetValue(key, out var count)) return _scopeRefs.Count; if (count > 1) { if (_scopeRefs.TryUpdate(key, count - 1, count)) return _scopeRefs.Count; continue; } if (_scopeRefs.TryRemove(new KeyValuePair(key, count))) { Debug.WriteLine($"CAN.ReleaseScope:{key},剩余引用数 {_scopeRefs.Count}"); if (!_scopeRefs.IsEmpty) return _scopeRefs.Count; // 最后一个作用域已销毁:先停循环报文,再断开总线,最后释放原生驱动 DeleteCyclicMsgs(); DisConnect(); Release(); return 0; } } } #endregion /// 订阅者增删锁(仅保护列表本身,分发时不持锁,避免与原生回调线程死锁) private static readonly object _listenerLock = new object(); /// /// 发送类原生 API 串行锁:多工位共用同一个 TSMaster 实例, /// 并发进入 tsapp_add_cyclic_msg_canfd / tsapp_transmit_canfd_async / tsapp_delete_cyclic_msgs 会破坏原生状态。 /// private static readonly object _nativeSendLock = new object(); /// 各工位注册的回调(在托管侧保根,原生库只持有唯一的 _dispatcher 函数指针) private static readonly List _listeners = new List(); /// /// 唯一注册到 TSMaster 的原生回调委托。 /// /// 必须由静态字段长期持有:原生库只保存函数指针,托管委托一旦被 GC 回收, /// 原生线程回调到已释放的 thunk 就会直接访问冲突闪退(.NET 无法捕获)。 /// 多工位场景下原先每个工位各自覆盖 listener 静态字段,前一工位的委托随即失去根引用, /// 这正是开两个工位发报文必闪退的原因。 /// /// private static TCANFDQueueEvent_Win32? _dispatcher; /// 原生回调是否已注册(保证全进程只注册一次,避免同一个 obj 重复注册破坏 TSMaster 内部回调表) private static bool _dispatcherRegistered = false; /// /// 注册 CANFD 报文回调(多工位安全)。 /// 首个订阅者触发原生注册,其余订阅者仅加入托管侧分发列表,从而避免重复注册与委托被回收。 /// [Browsable(false)] public static int RegisterListener(TCANFDQueueEvent_Win32 listenEvent) { if (listenEvent == null) return -1; try { lock (_listenerLock) { if (!_listeners.Contains(listenEvent)) _listeners.Add(listenEvent); if (_dispatcherRegistered) return 0; _dispatcher = DispatchCanFdEvent; var re = TsMasterApi.tsapp_register_event_canfd(ref obj, _dispatcher); Debug.Assert(re == 0); if (re == 0) _dispatcherRegistered = true; return re; } } catch (Exception ex) { Debug.WriteLine($"CAN.RegisterListener 异常:{ex.Message}"); return -1; } } /// /// 注销 CANFD 报文回调。 /// 指定委托时只移除该订阅者;不指定时移除全部订阅者。所有订阅者移除后才反注册原生回调。 /// [Browsable(false)] public static int UnRegisterListener(TCANFDQueueEvent_Win32? listenEvent = null) { try { lock (_listenerLock) { if (listenEvent is not null) { _listeners.Remove(listenEvent); } else { _listeners.Clear(); } if (_listeners.Count > 0 || !_dispatcherRegistered) return 0; var dispatcher = _dispatcher; if (dispatcher is null) return 0; var re = TsMasterApi.tsapp_unregister_event_canfd(ref obj, dispatcher); Debug.Assert(re == 0); if (re == 0) ResetListenerRegistrationCore(); return re; } } catch (Exception ex) { Debug.WriteLine($"CAN.UnRegisterListener 异常:{ex.Message}"); return -1; } } /// 清除回调注册状态(调用方需自行持有 ) private static void ResetListenerRegistrationCore() { _dispatcherRegistered = false; _dispatcher = null; } /// 清除回调注册状态与全部订阅者 private static void ResetListenerRegistration() { lock (_listenerLock) { _listeners.Clear(); ResetListenerRegistrationCore(); } } /// /// 原生 CANFD 回调统一入口:向所有已注册工位分发同一帧数据。 /// 单个订阅者异常必须就地隔离,异常一旦跨出原生边界即进程闪退。 /// private static void DispatchCanFdEvent(ref int AObj, ref TLIBCANFD AData) { TCANFDQueueEvent_Win32[] snapshot; lock (_listenerLock) { snapshot = _listeners.ToArray(); } foreach (var listener in snapshot) { try { listener(ref AObj, ref AData); } catch (Exception ex) { Debug.WriteLine($"CAN 回调订阅者异常(已隔离):{ex.Message}"); } } } /// /// 连接 TSMaster CAN 总线:建立总线连接并启动 RBS 仿真系统, /// 成功后置位连接标志并触发 连接事件。 /// /// 返回代码,0 表示成功,非 0 表示失败(可用 查询错误说明) public static int Connect() { try { lock (_nativeSendLock) { // 多工位共用同一张 CAN 卡:任一工位已连接即复用,避免重复 tsapp_connect if (ConnectFlag) return 0; var re = TsMasterApi.tsapp_connect(); if (re == 0) { re = TsMasterApi.tscom_can_rbs_start(); if (re == 0) { ConnectFlag = true; Task.Run(() => { try { ConnectEvent?.Invoke(); } catch (Exception ex) { Debug.WriteLine($"CAN.ConnectEvent 订阅者异常:{ex.Message}"); } }); } else { Debug.WriteLine($"CAN_RBS启动失败,错误代码:{re}"); } } else { Debug.WriteLine($"CAN连接失败,错误代码:{re}"); return re; } return re; } } catch (Exception ex) { Debug.WriteLine($"CAN.Connect 异常:{ex.Message}"); return -1; } } /// /// 断开 TSMaster CAN 总线连接,成功后清除连接标志并触发 断开事件。 /// /// 返回代码,0 表示成功,非 0 表示失败(可用 查询错误说明) public static int DisConnect() { try { lock (_nativeSendLock) { // 已断开则直接返回:多工位场景下避免后续工位重复断开总线 if (!ConnectFlag) return 0; var re = TsMasterApi.tsapp_disconnect(); if (re == 0) { ConnectFlag = false; Task.Run(() => { try { DisConnectEvent?.Invoke(); } catch (Exception ex) { Debug.WriteLine($"CAN.DisConnectEvent 订阅者异常:{ex.Message}"); } }); } else { Debug.WriteLine($"断开CAN连接失败,错误代码:{re}"); } return re; } } catch (Exception ex) { Debug.WriteLine($"CAN.DisConnect 异常:{ex.Message}"); return -1; } } /// /// 加载DBC /// /// /// /// /// [Browsable(false)] public static int LoadDBC(string filePath, int[] channel, out uint databaseID) { databaseID = 0; if (!Path.Exists(filePath)) return -1; int re; try { re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID); } catch (Exception ex) { Debug.WriteLine($"CAN.LoadDBC 异常:{ex.Message}"); return -1; } if (re != 0) { // 加载失败时不再解析:databaseID 无效,解析只会把错误的报文混进托管缓存, // 之后发送这些报文会得到 93(message not found in database) Debug.WriteLine($"CAN.LoadDBC 失败:{filePath},返回代码 {re}"); return re; } foreach (var item in channel) { try { if (item < 0 || item >= DBCParse.MsgDatabase.Count) continue; DBCParse.parse(databaseID, item); DBCParse.rbs_parse(databaseID, item); } catch (Exception ex) { Debug.WriteLine($"CAN.LoadDBC 解析失败(通道 {item}):{ex.Message}"); } } // 原生库已重新装载报文,托管缓存与原生库重新对齐 NeedReloadDbc = false; return re; } /// /// 已加载过的 DBC 记录(键 = 通道集合 + 绝对路径,值 = TSMaster 数据库 ID)。 /// /// 一拖多场景下多个工位共用同一张 CAN 卡:DBC 只需下发并解析一次, /// 重复加载会让 DBCParse 把同一批报文重复追加进 MsgDatabase。 /// /// private static readonly ConcurrentDictionary _loadedDbcPool = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private static string BuildDbcKey(string filePath, int[] channels) => $"{string.Join(",", channels.OrderBy(c => c))}|{Path.GetFullPath(filePath)}"; /// /// 查询指定通道上的 DBC 是否已由其他工位加载过。 /// /// DBC 文件路径 /// 通道号数组 [Browsable(false)] public static bool IsDbcLoaded(string filePath, int[] channel) { if (!Path.Exists(filePath)) return false; return _loadedDbcPool.ContainsKey(BuildDbcKey(filePath, channel)); } /// /// 加载DBC(同一通道 + 同一文件只加载一次)。 /// /// 供多工位自动加载使用:其他工位已加载过同一 DBC 时直接返回 0, /// 不重复调用 tsdb_load_can_db,也不重复追加 DBCParse.MsgDatabase。 /// /// /// DBC 文件路径 /// 通道号数组 /// 首次加载返回的数据库 ID;已加载过时返回首次的 ID /// 返回代码,0 表示成功(含已加载跳过) [Browsable(false)] public static int LoadDBCOnce(string filePath, int[] channel, out uint databaseID) { databaseID = 0; if (!Path.Exists(filePath)) return -1; var key = BuildDbcKey(filePath, channel); if (_loadedDbcPool.TryGetValue(key, out var loadedId)) { databaseID = loadedId; return 0; } var re = LoadDBC(filePath, channel, out databaseID); if (re == 0) _loadedDbcPool[key] = databaseID; return re; } /// 清空 DBC 加载记录(卸载 DBC 或释放驱动时调用)。 [Browsable(false)] public static void ClearDbcLoadRecords() => _loadedDbcPool.Clear(); /// /// 加载DBC至单一通道 /// /// /// /// public static int LoadDBCToSingleChannel(string filePath, int channel) { uint databaseID = 0; if (!Path.Exists(filePath)) return -1; int re; try { re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID); } catch (Exception ex) { Debug.WriteLine($"CAN.LoadDBCToSingleChannel 异常:{ex.Message}"); return -1; } try { if (channel >= 0 && channel < DBCParse.MsgDatabase.Count) { DBCParse.parse(databaseID, channel); DBCParse.rbs_parse(databaseID, channel); } } catch (Exception ex) { Debug.WriteLine($"CAN.LoadDBCToSingleChannel 解析失败(通道 {channel}):{ex.Message}"); } return re; } /// /// 卸载DBC /// /// /// [Browsable(false)] public static int UnLoadDBC(uint? databaseID = null) { try { int re; if (databaseID == null) { re = TsMasterApi.tsdb_unload_can_dbs(); } else { re = TsMasterApi.tsdb_unload_can_db(databaseID.Value); } // TSMaster 内部数据库已卸载:本地加载记录与托管报文缓存必须同步失效, // 否则再发送时会用残留的报文名去匹配空数据库,返回 93(message not found in database) if (re == 0) { _loadedDbcPool.Clear(); DBCParse.ClearMsgDatabase(); NeedReloadDbc = true; } return re; } catch (Exception ex) { Debug.WriteLine($"CAN.UnLoadDBC 异常:{ex.Message}"); return -1; } } /// /// 开始记录总线日志,将总线上的报文数据记录到指定文件。 /// /// 日志文件保存路径(自动转换为绝对路径) /// 返回代码,0 表示成功,非 0 表示失败 public static int StartLogging(string filePath) { try { return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath)); } catch (Exception ex) { Debug.WriteLine($"CAN.StartLogging 异常:{ex.Message}"); return -1; } } /// /// 结束总线日志记录。 /// /// 返回代码,0 表示成功,非 0 表示失败 public static int StopLogging() { try { return TsMasterApi.tsapp_stop_logging(); } catch (Exception ex) { Debug.WriteLine($"CAN.StopLogging 异常:{ex.Message}"); return -1; } } /// /// 弹出通道映射窗口 /// /// /// [Browsable(false)] public static int ShowChannelMappingWindow(bool isWait = false) { return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait); } /// /// 获取 DBC 中定义信号的当前物理值(从总线接收报文中解析)。 /// /// 通道号(0 起始) /// DBC 中的报文名称 /// DBC 中的信号名称 /// 信号物理值,获取失败时返回 public static double GetSignalValue(byte channel, string AMsgName, string ASgnName) { double value = double.NaN; var find = DBCParse.MsgDatabase[channel].FirstOrDefault(s => s.msg_name == AMsgName); if (find == null) return value; try { TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value); } catch (Exception ex) { Debug.WriteLine($"CAN.GetSignalValue 异常:{ex.Message}"); } return value; } /// /// 获取信号值 /// /// /// /// /// /// [Browsable(false)] public static int GetSignalValue(byte channel, string AMsgName, string ASgnName, ref double value) { var find = DBCParse.MsgDatabase[channel].FirstOrDefault(s => s.msg_name == AMsgName); if (find == null) return -1; try { return TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value); } catch (Exception ex) { Debug.WriteLine($"CAN.GetSignalValue 异常:{ex.Message}"); return -1; } } /// /// 获取信号值 /// /// /// /// /// /// [Browsable(false)] public static int GetSignalValue(ref TLIBCANFD ACANFD, string AMsgName, string ASgnName, ref double value) { return TsMasterApi.tsdb_get_signal_value_canfd(ref ACANFD, AMsgName, ASgnName, ref value); } /// /// 设置 DBC 中定义信号的值(按物理值编码进报文数据),可选择立即发送或周期发送。 /// /// 通道号(0 起始) /// DBC 中的报文名称 /// DBC 中的信号名称 /// 要设置的信号物理值 /// 是否发送:false 仅更新数据不发送,true 则发送 /// 发送周期(毫秒):0 = 只发送一次;>0 = 按该周期循环发送 /// 返回代码,0 表示成功,非 0 表示失败 /// 信号编码写入报文失败时抛出 public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0) { // MsgDatabase 只预分配 12 个通道,越界会让发送入口直接抛 IndexOutOfRange if (channel >= DBCParse.MsgDatabase.Count) return -1; var find = DBCParse.MsgDatabase[channel].FirstOrDefault(s => s.msg_name == AMsgName); if (find == null) return -1; find.ACANFD.FIdxChn = channel; var re = TsMasterApi.tsdb_set_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, AValue); if (re != 0) { // 93 = message not found in database:原生库在该通道上找不到这个报文名。 // 常见成因:驱动被释放 / DBC 被卸载后托管缓存没同步,或 DBC 实际加载在别的通道。 throw new Exception($"设置报文失败!返回代码{re}({GetErrorDescription(re)})"); } if (isSend) { try { // 多工位共用同一个 TSMaster 实例,发送类原生 API 必须串行化, // 并发调用会破坏原生内部循环发送表,表现为发报文瞬间闪退。 lock (_nativeSendLock) { if (sendPeriod == 0) { return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD); } else { return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod); } } } catch (Exception ex) { Debug.WriteLine($"CAN.SetSignalValue 发送异常:{ex.Message}"); return -1; } } return 0; } /// /// 按报文名称发送 DBC 中定义的报文(使用该报文在 DBC 数据库中的当前数据)。 /// /// 通道号 /// DBC 中的报文名称 /// 发送周期(毫秒):0 = 只发送一次;>0 = 按该周期循环发送 /// 返回代码,0 表示成功,非 0 表示失败 public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0) { var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.msg_name == AMsgName); if (find == null) return -1; find.ACANFD.FIdxChn = (byte)channel; try { lock (_nativeSendLock) { if (sendPeriod == 0) { return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD); } else { return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod); } } } catch (Exception ex) { Debug.WriteLine($"CAN.SetSignalValue 发送异常:{ex.Message}"); return -1; } } /// /// 发送自定义报文 /// /// [Browsable(false)] public static int SetMsg(TLIBCANFD msg) { try { lock (_nativeSendLock) { return TsMasterApi.tsapp_transmit_canfd_async(ref msg); } } catch (Exception ex) { Debug.WriteLine($"CAN.SetMsg 发送异常:{ex.Message}"); return -1; } } /// /// 按帧 ID 更新 DBC 数据库中指定报文的数据区(仅更新内存中的数据,不发送)。 /// /// 通道号 /// 报文帧 ID(DBC 中定义的 FIdentifier) /// 要写入的报文数据数组,超出报文数据长度的部分会被截断 public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes) { var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID); if (find != null) { Array.Copy(bytes, 0, find.ACANFD.FData, 0, Math.Min(bytes.Length, find.ACANFD.FData.Length)); } } /// /// 按帧 ID 构造并发送 CANFD 报文(不依赖 DBC 数据库与报文名),可选择单次发送或周期发送。 /// /// 通道号 /// 报文帧 ID /// 报文数据数组(最多 64 字节,超出部分截断) /// 发送周期(毫秒):0 = 只发送一次;>0 = 按该周期循环发送 /// 是否扩展帧(29 位 ID),默认标准帧 /// 返回代码,0 表示成功,非 0 表示失败(可用 查询错误说明) public static int SendMsgById(APP_CHANNEL channel, int ID, byte[] bytes, float sendPeriod = 0, bool isExt = false) { var data = new byte[64]; Array.Copy(bytes, data, Math.Min(bytes.Length, 64)); return SendMsg(channel, ID, true, isExt, false, (byte)Math.Min(bytes.Length, 64), data, true, false, false, sendPeriod); } /// /// 构造并发送自定义 CAN/CANFD 报文,可选择单次发送或周期发送,并可同步更新 DBC 数据库中同 ID 报文的数据。 /// /// 通道号 /// 报文帧 ID /// 是否为发送帧 /// 是否为扩展帧(29 位 ID) /// 是否为远程帧 /// 数据长度代码(数据字节数,最大 64) /// 报文数据数组,超过 64 字节的部分会被截断 /// 是否为 CANFD 帧,默认 true /// 是否切换数据段波特率(仅 CANFD 有效),默认 false /// 是否同步更新 DBC 数据库中同帧 ID 报文的数据,默认 false /// 发送周期(毫秒):0 = 只发送一次;>0 = 按该周期循环发送 /// 返回代码,0 表示成功,非 0 表示失败 public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt, bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false, bool isUpdateDBCDatabase = false, float sendPeriod = 0) { var send = new TLIBCANFD { FIdxChn = (byte)AIdxChn, FProperties = 0, FIdentifier = AID, FDLC = ADLC, FTimeUS = 0uL, FData = new byte[64], FFDProperties = 0, FIsTx = AIsTx, FIsError = false, FIsExt = AIsExt, FIsRemote = AIsRemote, FIsFD = AIsFD, FIsBRS = AIsBRS }; int length = Math.Min(ADataArray.Length, 64); Array.Copy(ADataArray, 0, send.FData, 0, length); int re; try { lock (_nativeSendLock) { if (sendPeriod == 0) { re = TsMasterApi.tsapp_transmit_canfd_async(ref send); } else { re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod); } } } catch (Exception ex) { Debug.WriteLine($"CAN.SendMsg 发送异常(通道 {(byte)AIdxChn} ID 0x{AID:X}):{ex.Message}"); return -1; } if (isUpdateDBCDatabase && (byte)AIdxChn < DBCParse.MsgDatabase.Count) { var find = DBCParse.MsgDatabase[(byte)AIdxChn].FirstOrDefault(s => s.ACANFD.FIdentifier == send.FIdentifier); if (find != null) { Array.Copy(send.FData, 0, find.ACANFD.FData, 0, Math.Min(ADataArray.Length, find.ACANFD.FData.Length)); } } return re; } /// /// 将 DBC 中定义的报文加入循环发送列表,按指定周期持续发送。 /// /// 通道号(0 起始) /// DBC 中的报文名称 /// 发送周期(毫秒) /// 返回代码,0 表示成功,非 0 表示失败 public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod) { var find = DBCParse.MsgDatabase[channel].FirstOrDefault(s => s.msg_name == AMsgName); if (find == null) return -1; find.ACANFD.FIdxChn = channel; try { lock (_nativeSendLock) { return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod); } } catch (Exception ex) { Debug.WriteLine($"CAN.AddCyclicMsg 异常(通道 {channel} {AMsgName}):{ex.Message}"); return -1; } } /// /// 清除所有循环发送的报文,停止全部周期发送任务。 /// 注意:TSMaster 的循环发送表是全局的,此调用会同时清掉其他工位的循环发送。 /// /// 返回代码,0 表示成功,非 0 表示失败 public static int DeleteCyclicMsgs() { try { lock (_nativeSendLock) { return TsMasterApi.tsapp_delete_cyclic_msgs(); } } catch (Exception ex) { Debug.WriteLine($"CAN.DeleteCyclicMsgs 异常:{ex.Message}"); return -1; } } /// /// 根据错误代码获取对应的错误文字描述,用于诊断 TSMaster API 返回码。 /// /// TSMaster API 返回的错误代码 /// 错误描述文本,未知错误代码时返回提示信息 public static string GetErrorDescription(int errorCode) { try { IntPtr ADesc = IntPtr.Zero; TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc); if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}"; // 假设返回的是 ANSI 字符串(如 C 的 char*) string? description = Marshal.PtrToStringAnsi(ADesc); return description ?? $"未知错误代码: {errorCode}"; } catch (Exception ex) { Debug.WriteLine($"CAN.GetErrorDescription 异常:{ex.Message}"); return $"未知错误代码: {errorCode}"; } } } }