CAN修复
This commit is contained in:
+604
-89
@@ -22,8 +22,28 @@ namespace TSMasterCAN
|
||||
public static event Action DisConnectEvent;
|
||||
public static bool ConnectFlag { get; set; } = false;
|
||||
|
||||
/// <summary>初始化互斥锁:TSMaster 原生库每个进程只允许初始化一次</summary>
|
||||
private static readonly object _initLock = new object();
|
||||
/// <summary>初始化结果缓存(0 表示已成功初始化,无需再次初始化)</summary>
|
||||
private static int _initResult = int.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化CAN驱动
|
||||
/// 驱动是否刚被释放、需要重新下发 DBC。
|
||||
/// <para>
|
||||
/// Release()/UnLoadDBC 成功后置 true,任一次 DBC 加载成功后置 false;
|
||||
/// 上层(如 CANModule 点「初始化驱动」后)据此决定是否重载 DBC,
|
||||
/// 避免拿残留的托管缓存去匹配已经清空的原生数据库(返回 93)。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static bool NeedReloadDbc { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化CAN驱动。
|
||||
/// <para>
|
||||
/// TSMaster 原生库每个进程只允许初始化一次:多工位(一拖三)各自调用时,
|
||||
/// 重复 initialize_lib_tsmaster 会重建原生引擎,正在运行的循环发送表与回调队列随之失效,
|
||||
/// 下一次发报文即触发原生访问冲突闪退。因此这里做进程级一次性保护,后续调用直接复用首次结果。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="ProjectName"></param>
|
||||
/// <param name="filePath"></param>
|
||||
@@ -31,46 +51,264 @@ namespace TSMasterCAN
|
||||
[Browsable(false)]
|
||||
public static int Init(string ProjectName, string? filePath = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
try
|
||||
{
|
||||
return TsMasterApi.initialize_lib_tsmaster(ProjectName);
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
return TsMasterApi.initialize_lib_tsmaster_with_project(ProjectName, Path.GetFullPath(filePath));
|
||||
// 原生 API 异常统一转为返回码,不得向外传播导致进程闪退
|
||||
Debug.WriteLine($"CAN.Init 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放CAN驱动
|
||||
/// 释放CAN驱动。
|
||||
/// <para>释放后清除初始化缓存与回调注册状态,允许下一次 Init 重新初始化。</para>
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public static void Release()
|
||||
{
|
||||
TsMasterApi.finalize_lib_tsmaster();
|
||||
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;
|
||||
public static TCANFDQueueEvent_Win32 listener;
|
||||
|
||||
#region 一拖多引用计数
|
||||
|
||||
/// <summary>作用域名缺失时的兜底键</summary>
|
||||
private const string DefaultScopeKey = "__default__";
|
||||
|
||||
/// <summary>
|
||||
/// CAN 卡的作用域引用表(作用域名 → 引用计数)。
|
||||
/// <para>
|
||||
/// CAN 卡是一拖多共享硬件:多个工位共用同一个 TSMaster 实例,
|
||||
/// 必须等最后一个工位销毁后才能停止循环报文、断开总线并释放原生驱动。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, int> _scopeRefs =
|
||||
new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>当前仍持有 CAN 卡引用的作用域数量。</summary>
|
||||
public static int ScopeRefCount => _scopeRefs.Count;
|
||||
|
||||
private static string NormalizeScopeKey(string? scopeName)
|
||||
=> string.IsNullOrWhiteSpace(scopeName) ? DefaultScopeKey : scopeName!;
|
||||
|
||||
/// <summary>
|
||||
/// 登记一个作用域对 CAN 卡的引用,需与 <see cref="ReleaseScope"/> 成对调用。
|
||||
/// </summary>
|
||||
/// <param name="scopeName">作用域名(通常为 SystemConfig.Title)</param>
|
||||
public static void AcquireScope(string? scopeName)
|
||||
{
|
||||
var key = NormalizeScopeKey(scopeName);
|
||||
_scopeRefs.AddOrUpdate(key, 1, (_, count) => count + 1);
|
||||
Debug.WriteLine($"CAN.AcquireScope:{key},当前引用数 {_scopeRefs.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销一个作用域对 CAN 卡的引用;最后一个引用移除时停止循环报文、断开总线并释放驱动。
|
||||
/// </summary>
|
||||
/// <param name="scopeName">作用域名(通常为 SystemConfig.Title)</param>
|
||||
/// <returns>剩余引用数,0 表示已停止 CAN 卡</returns>
|
||||
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<string, int>(key, count)))
|
||||
{
|
||||
Debug.WriteLine($"CAN.ReleaseScope:{key},剩余引用数 {_scopeRefs.Count}");
|
||||
|
||||
if (!_scopeRefs.IsEmpty) return _scopeRefs.Count;
|
||||
|
||||
// 最后一个作用域已销毁:先停循环报文,再断开总线,最后释放原生驱动
|
||||
DeleteCyclicMsgs();
|
||||
DisConnect();
|
||||
Release();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>订阅者增删锁(仅保护列表本身,分发时不持锁,避免与原生回调线程死锁)</summary>
|
||||
private static readonly object _listenerLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 发送类原生 API 串行锁:多工位共用同一个 TSMaster 实例,
|
||||
/// 并发进入 tsapp_add_cyclic_msg_canfd / tsapp_transmit_canfd_async / tsapp_delete_cyclic_msgs 会破坏原生状态。
|
||||
/// </summary>
|
||||
private static readonly object _nativeSendLock = new object();
|
||||
|
||||
/// <summary>各工位注册的回调(在托管侧保根,原生库只持有唯一的 _dispatcher 函数指针)</summary>
|
||||
private static readonly List<TCANFDQueueEvent_Win32> _listeners = new List<TCANFDQueueEvent_Win32>();
|
||||
|
||||
/// <summary>
|
||||
/// 唯一注册到 TSMaster 的原生回调委托。
|
||||
/// <para>
|
||||
/// 必须由静态字段长期持有:原生库只保存函数指针,托管委托一旦被 GC 回收,
|
||||
/// 原生线程回调到已释放的 thunk 就会直接访问冲突闪退(.NET 无法捕获)。
|
||||
/// 多工位场景下原先每个工位各自覆盖 listener 静态字段,前一工位的委托随即失去根引用,
|
||||
/// 这正是开两个工位发报文必闪退的原因。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private static TCANFDQueueEvent_Win32? _dispatcher;
|
||||
|
||||
/// <summary>原生回调是否已注册(保证全进程只注册一次,避免同一个 obj 重复注册破坏 TSMaster 内部回调表)</summary>
|
||||
private static bool _dispatcherRegistered = false;
|
||||
|
||||
/// <summary>
|
||||
/// 注册 CANFD 报文回调(多工位安全)。
|
||||
/// <para>首个订阅者触发原生注册,其余订阅者仅加入托管侧分发列表,从而避免重复注册与委托被回收。</para>
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public static int RegisterListener(TCANFDQueueEvent_Win32 listenEvent)
|
||||
{
|
||||
listener = listenEvent;
|
||||
var re = TsMasterApi.tsapp_register_event_canfd(ref obj, listener);
|
||||
Debug.Assert(re == 0);
|
||||
return re;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销 CANFD 报文回调。
|
||||
/// <para>指定委托时只移除该订阅者;不指定时移除全部订阅者。所有订阅者移除后才反注册原生回调。</para>
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public static int UnRegisterListener(TCANFDQueueEvent_Win32 listenEvent = null)
|
||||
public static int UnRegisterListener(TCANFDQueueEvent_Win32? listenEvent = null)
|
||||
{
|
||||
if (listenEvent is not null)
|
||||
try
|
||||
{
|
||||
listener = listenEvent;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>清除回调注册状态(调用方需自行持有 <see cref="_listenerLock"/>)</summary>
|
||||
private static void ResetListenerRegistrationCore()
|
||||
{
|
||||
_dispatcherRegistered = false;
|
||||
_dispatcher = null;
|
||||
}
|
||||
|
||||
/// <summary>清除回调注册状态与全部订阅者</summary>
|
||||
private static void ResetListenerRegistration()
|
||||
{
|
||||
lock (_listenerLock)
|
||||
{
|
||||
_listeners.Clear();
|
||||
ResetListenerRegistrationCore();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 原生 CANFD 回调统一入口:向所有已注册工位分发同一帧数据。
|
||||
/// <para>单个订阅者异常必须就地隔离,异常一旦跨出原生边界即进程闪退。</para>
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
var re = TsMasterApi.tsapp_unregister_event_canfd(ref obj, listener);
|
||||
Debug.Assert(re == 0);
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,27 +318,44 @@ namespace TSMasterCAN
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败(可用 <see cref="GetErrorDescription"/> 查询错误说明)</returns>
|
||||
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(() => ConnectEvent?.Invoke());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"CAN_RBS启动失败,错误代码:{re}");
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CAN连接失败,错误代码:{re}");
|
||||
return re;
|
||||
Debug.WriteLine($"CAN.Connect 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,20 +364,36 @@ namespace TSMasterCAN
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败(可用 <see cref="GetErrorDescription"/> 查询错误说明)</returns>
|
||||
public static int DisConnect()
|
||||
{
|
||||
var re = TsMasterApi.tsapp_disconnect();
|
||||
//Debug.Assert(re == 0);
|
||||
if (re == 0)
|
||||
try
|
||||
{
|
||||
ConnectFlag = false;
|
||||
Task.Run(() => DisConnectEvent?.Invoke());
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"断开CAN连接失败,错误代码:{re}");
|
||||
Debug.WriteLine($"CAN.DisConnect 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return re;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,15 +408,102 @@ namespace TSMasterCAN
|
||||
{
|
||||
databaseID = 0;
|
||||
if (!Path.Exists(filePath)) return -1;
|
||||
var re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID);
|
||||
foreach (var item in channel)
|
||||
int re;
|
||||
try
|
||||
{
|
||||
DBCParse.parse(databaseID, item);
|
||||
DBCParse.rbs_parse(databaseID, item);
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已加载过的 DBC 记录(键 = 通道集合 + 绝对路径,值 = TSMaster 数据库 ID)。
|
||||
/// <para>
|
||||
/// 一拖多场景下多个工位共用同一张 CAN 卡:DBC 只需下发并解析一次,
|
||||
/// 重复加载会让 DBCParse 把同一批报文重复追加进 MsgDatabase。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, uint> _loadedDbcPool =
|
||||
new ConcurrentDictionary<string, uint>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static string BuildDbcKey(string filePath, int[] channels)
|
||||
=> $"{string.Join(",", channels.OrderBy(c => c))}|{Path.GetFullPath(filePath)}";
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道上的 DBC 是否已由其他工位加载过。
|
||||
/// </summary>
|
||||
/// <param name="filePath">DBC 文件路径</param>
|
||||
/// <param name="channel">通道号数组</param>
|
||||
[Browsable(false)]
|
||||
public static bool IsDbcLoaded(string filePath, int[] channel)
|
||||
{
|
||||
if (!Path.Exists(filePath)) return false;
|
||||
return _loadedDbcPool.ContainsKey(BuildDbcKey(filePath, channel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载DBC(同一通道 + 同一文件只加载一次)。
|
||||
/// <para>
|
||||
/// 供多工位自动加载使用:其他工位已加载过同一 DBC 时直接返回 0,
|
||||
/// 不重复调用 tsdb_load_can_db,也不重复追加 DBCParse.MsgDatabase。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="filePath">DBC 文件路径</param>
|
||||
/// <param name="channel">通道号数组</param>
|
||||
/// <param name="databaseID">首次加载返回的数据库 ID;已加载过时返回首次的 ID</param>
|
||||
/// <returns>返回代码,0 表示成功(含已加载跳过)</returns>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>清空 DBC 加载记录(卸载 DBC 或释放驱动时调用)。</summary>
|
||||
[Browsable(false)]
|
||||
public static void ClearDbcLoadRecords() => _loadedDbcPool.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// 加载DBC至单一通道
|
||||
/// </summary>
|
||||
@@ -156,9 +514,28 @@ namespace TSMasterCAN
|
||||
{
|
||||
uint databaseID = 0;
|
||||
if (!Path.Exists(filePath)) return -1;
|
||||
var re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID);
|
||||
DBCParse.parse(databaseID, channel);
|
||||
DBCParse.rbs_parse(databaseID, channel);
|
||||
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;
|
||||
}
|
||||
@@ -170,13 +547,32 @@ namespace TSMasterCAN
|
||||
[Browsable(false)]
|
||||
public static int UnLoadDBC(uint? databaseID = null)
|
||||
{
|
||||
if (databaseID == null)
|
||||
try
|
||||
{
|
||||
return TsMasterApi.tsdb_unload_can_dbs();
|
||||
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;
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
return TsMasterApi.tsdb_unload_can_db(databaseID.Value);
|
||||
Debug.WriteLine($"CAN.UnLoadDBC 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +583,15 @@ namespace TSMasterCAN
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
|
||||
public static int StartLogging(string filePath)
|
||||
{
|
||||
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
|
||||
try
|
||||
{
|
||||
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CAN.StartLogging 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +600,15 @@ namespace TSMasterCAN
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
|
||||
public static int StopLogging()
|
||||
{
|
||||
return TsMasterApi.tsapp_stop_logging();
|
||||
try
|
||||
{
|
||||
return TsMasterApi.tsapp_stop_logging();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CAN.StopLogging 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -220,8 +632,16 @@ namespace TSMasterCAN
|
||||
public static double GetSignalValue(byte channel, string AMsgName, string ASgnName)
|
||||
{
|
||||
double value = double.NaN;
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
var re = TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
|
||||
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;
|
||||
}
|
||||
@@ -237,8 +657,17 @@ namespace TSMasterCAN
|
||||
[Browsable(false)]
|
||||
public static int GetSignalValue(byte channel, string AMsgName, string ASgnName, ref double value)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
return TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -269,21 +698,41 @@ namespace TSMasterCAN
|
||||
/// <exception cref="Exception">信号编码写入报文失败时抛出</exception>
|
||||
public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
// 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);
|
||||
Debug.Assert(re == 0);
|
||||
if (re != 0) throw new Exception($"设置报文失败!返回代码{re}");
|
||||
if (re != 0) return re;
|
||||
if (re != 0)
|
||||
{
|
||||
// 93 = message not found in database:原生库在该通道上找不到这个报文名。
|
||||
// 常见成因:驱动被释放 / DBC 被卸载后托管缓存没同步,或 DBC 实际加载在别的通道。
|
||||
throw new Exception($"设置报文失败!返回代码{re}({GetErrorDescription(re)})");
|
||||
}
|
||||
if (isSend)
|
||||
{
|
||||
if (sendPeriod == 0)
|
||||
try
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
|
||||
// 多工位共用同一个 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
||||
Debug.WriteLine($"CAN.SetSignalValue 发送异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -299,15 +748,27 @@ namespace TSMasterCAN
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
|
||||
public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName);
|
||||
var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.msg_name == AMsgName);
|
||||
if (find == null) return -1;
|
||||
find.ACANFD.FIdxChn = (byte)channel;
|
||||
if (sendPeriod == 0)
|
||||
try
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
||||
Debug.WriteLine($"CAN.SetSignalValue 发送异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +779,18 @@ namespace TSMasterCAN
|
||||
[Browsable(false)]
|
||||
public static int SetMsg(TLIBCANFD msg)
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
|
||||
try
|
||||
{
|
||||
lock (_nativeSendLock)
|
||||
{
|
||||
return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CAN.SetMsg 发送异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -391,16 +863,27 @@ namespace TSMasterCAN
|
||||
int length = Math.Min(ADataArray.Length, 64);
|
||||
Array.Copy(ADataArray, 0, send.FData, 0, length);
|
||||
int re;
|
||||
if (sendPeriod == 0)
|
||||
try
|
||||
{
|
||||
re = TsMasterApi.tsapp_transmit_canfd_async(ref send);
|
||||
lock (_nativeSendLock)
|
||||
{
|
||||
if (sendPeriod == 0)
|
||||
{
|
||||
re = TsMasterApi.tsapp_transmit_canfd_async(ref send);
|
||||
}
|
||||
else
|
||||
{
|
||||
re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod);
|
||||
Debug.WriteLine($"CAN.SendMsg 发送异常(通道 {(byte)AIdxChn} ID 0x{AID:X}):{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (isUpdateDBCDatabase)
|
||||
if (isUpdateDBCDatabase && (byte)AIdxChn < DBCParse.MsgDatabase.Count)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[(byte)AIdxChn].FirstOrDefault(s => s.ACANFD.FIdentifier == send.FIdentifier);
|
||||
if (find != null)
|
||||
@@ -420,18 +903,42 @@ namespace TSMasterCAN
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
|
||||
public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
||||
var find = DBCParse.MsgDatabase[channel].FirstOrDefault(s => s.msg_name == AMsgName);
|
||||
if (find == null) return -1;
|
||||
find.ACANFD.FIdxChn = channel;
|
||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有循环发送的报文,停止全部周期发送任务。
|
||||
/// <para>注意:TSMaster 的循环发送表是全局的,此调用会同时清掉其他工位的循环发送。</para>
|
||||
/// </summary>
|
||||
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
|
||||
public static int DeleteCyclicMsgs()
|
||||
{
|
||||
return TsMasterApi.tsapp_delete_cyclic_msgs();
|
||||
try
|
||||
{
|
||||
lock (_nativeSendLock)
|
||||
{
|
||||
return TsMasterApi.tsapp_delete_cyclic_msgs();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CAN.DeleteCyclicMsgs 异常:{ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -441,15 +948,23 @@ namespace TSMasterCAN
|
||||
/// <returns>错误描述文本,未知错误代码时返回提示信息</returns>
|
||||
public static string GetErrorDescription(int errorCode)
|
||||
{
|
||||
IntPtr ADesc = IntPtr.Zero;
|
||||
TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc);
|
||||
try
|
||||
{
|
||||
IntPtr ADesc = IntPtr.Zero;
|
||||
TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc);
|
||||
|
||||
if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}";
|
||||
if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}";
|
||||
|
||||
// 假设返回的是 ANSI 字符串(如 C 的 char*)
|
||||
string? description = Marshal.PtrToStringAnsi(ADesc);
|
||||
// 假设返回的是 ANSI 字符串(如 C 的 char*)
|
||||
string? description = Marshal.PtrToStringAnsi(ADesc);
|
||||
|
||||
return description ?? $"未知错误代码: {errorCode}";
|
||||
return description ?? $"未知错误代码: {errorCode}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CAN.GetErrorDescription 异常:{ex.Message}");
|
||||
return $"未知错误代码: {errorCode}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -125,6 +125,19 @@ namespace TSMasterCAN
|
||||
public static List<List<_Msg_>> MsgDatabase { get; set; } =
|
||||
[.. Enumerable.Range(0, 12).Select(_ => new List<_Msg_>())];
|
||||
public static can_network can_Network;
|
||||
|
||||
/// <summary>
|
||||
/// 清空托管侧报文缓存。
|
||||
/// <para>
|
||||
/// TSMaster 原生数据库被卸载(tsdb_unload_can_dbs)或驱动被 finalize 后,
|
||||
/// 这里的缓存必须同步清空,否则再次发送时会拿缓存里的报文去匹配空数据库,
|
||||
/// tsdb_set_signal_value_canfd 会返回 93(message not found in database)。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static void ClearMsgDatabase()
|
||||
{
|
||||
foreach (var list in MsgDatabase) list.Clear();
|
||||
}
|
||||
public static string tsdb_get_can_db_info(uint ADatabaseId, int AType, int AIndex, int ASubIndex)
|
||||
{
|
||||
nint tmpInt = new nint();
|
||||
|
||||
Reference in New Issue
Block a user