Files
ACP/TSMasterCAN/CAN.cs
2026-07-29 14:20:32 +08:00

680 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
[ACPCommand]
public class CAN : IDisposable
{
#region
public static event Action? ConnectEvent;
public static event Action? DisConnectEvent;
public static bool ConnectFlag { get; set; } = false;
/// <summary>
/// 实时接收到的 CAN 报文缓存(按报文 ID 索引,保留最新帧)。
/// 供 CANViewModel.Refresh 等界面轮询读取。
/// </summary>
public static ConcurrentDictionary<int, TLIBCANFD> RealTimeMessages { get; } = new();
#endregion
#region
/// <summary>设备类型号43 = USBCANFD-400U 等TSMaster 中作为配置参考保留)</summary>
public uint DeviceType { get; }
/// <summary>设备索引</summary>
public uint DeviceIndex { get; }
/// <summary>最大通道数</summary>
public int MaxChannels { get; }
/// <summary>仲裁域波特率</summary>
public string ABitBaud { get; }
/// <summary>数据域波特率</summary>
public string DBitBaud { get; }
/// <summary>是否开启终端电阻</summary>
public bool EnableTerminalResistance { get; }
/// <summary>DBC 解析器(提供 MsgDatabase、MaxChannels 等访问)</summary>
public DBCParse DBCParser => DBCParse.Instance;
/// <summary>
/// DBC 报文解码事件:当接收到 CAN 帧并通过 DBC 解码后触发。
/// 参数:(通道号, 解码后的报文信息)
/// </summary>
public event Action<uint, DBCMessage>? OnDbcMessageDecoded;
private bool _disposed;
private bool _isOpened;
private TCANFDQueueEvent_Win32? _instanceListener;
#endregion
#region
/// <summary>默认构造函数(兼容 SystemConfig.CANFD = new() 等场景)</summary>
public CAN() : this(43, 0, 4, "500000", "2000000", true) { }
/// <summary>
/// 带配置参数的构造函数(兼容原 ZLG 设备管理流程)。
/// </summary>
/// <param name="deviceType">设备类型号</param>
/// <param name="deviceIndex">设备索引</param>
/// <param name="maxChannels">最大通道数</param>
/// <param name="aBitBaud">仲裁域波特率</param>
/// <param name="dBitBaud">数据域波特率</param>
/// <param name="enableTerminalResistance">是否开启终端电阻</param>
public CAN(uint deviceType, uint deviceIndex, int maxChannels, string aBitBaud, string dBitBaud, bool enableTerminalResistance)
{
DeviceType = deviceType;
DeviceIndex = deviceIndex;
MaxChannels = maxChannels;
ABitBaud = aBitBaud;
DBitBaud = dBitBaud;
EnableTerminalResistance = enableTerminalResistance;
}
#endregion
#region ZLG
/// <summary>
/// 打开设备:初始化 TSMaster 库 → 设置通道数 → 配置波特率 → 连接 → 启动 RBS → 注册监听。
/// </summary>
/// <returns>true 表示成功</returns>
public bool ()
{
if (_isOpened) return true;
if (ConnectFlag) { _isOpened = true; return true; }
try
{
// 1. 初始化 TSMaster 库
var re = Init("ACP");
if (re != 0)
{
Debug.WriteLine($"TSMaster 初始化失败,错误代码:{re}");
return false;
}
// 2. 设置通道数
TsMasterApi.tsapp_set_can_channel_count(MaxChannels);
// 3. 配置各通道波特率
float arbKbps = float.Parse(ABitBaud) / 1000f;
float dataKbps = float.Parse(DBitBaud) / 1000f;
for (int ch = 0; ch < MaxChannels; ch++)
{
TsMasterApi.tsapp_configure_baudrate_canfd(
ch, arbKbps, dataKbps,
TLIBCANFDControllerType.lfdtISOCAN,
TLIBCANFDControllerMode.lfdmNormal,
EnableTerminalResistance);
}
// 4. 连接硬件
re = Connect();
if (re != 0)
{
Debug.WriteLine($"CAN 连接失败,错误代码:{re}");
return false;
}
// 5. 注册接收监听(用于 DBC 信号解码广播)
_instanceListener = new TCANFDQueueEvent_Win32(OnCanFdReceived);
RegisterListener(_instanceListener);
_isOpened = true;
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"打开设备异常:{ex.Message}");
return false;
}
}
/// <summary>
/// 关闭 CAN 卡设备:注销监听 → 断开连接。
/// </summary>
public void CAN卡设备()
{
if (!_isOpened && !ConnectFlag) return;
try
{
if (_instanceListener != null)
{
UnRegisterListener(_instanceListener);
_instanceListener = null;
}
DisConnect();
}
catch (Exception ex)
{
Debug.WriteLine($"关闭CAN卡设备异常{ex.Message}");
}
finally
{
_isOpened = false;
}
}
/// <summary>
/// 初始化并启动指定通道配置波特率TSMaster 在 Connect 时已自动启动所有通道)。
/// </summary>
/// <param name="channel">通道号</param>
public void (uint channel)
{
if (channel >= MaxChannels) return;
try
{
float arbKbps = float.Parse(ABitBaud) / 1000f;
float dataKbps = float.Parse(DBitBaud) / 1000f;
TsMasterApi.tsapp_configure_baudrate_canfd(
(int)channel, arbKbps, dataKbps,
TLIBCANFDControllerType.lfdtISOCAN,
TLIBCANFDControllerMode.lfdmNormal,
EnableTerminalResistance);
}
catch (Exception ex)
{
Debug.WriteLine($"初始化通道 {channel} 异常:{ex.Message}");
}
}
/// <summary>
/// 加载通道 DBC 文件。
/// </summary>
/// <param name="channel">通道号</param>
/// <param name="filePath">DBC 文件路径</param>
/// <returns>true 表示加载成功</returns>
public bool DBC文件(uint channel, string filePath)
{
try
{
var re = LoadDBC(filePath, [(int)channel], out _);
return re == 0;
}
catch (Exception ex)
{
Debug.WriteLine($"加载通道 {channel} DBC 文件异常:{ex.Message}");
return false;
}
}
/// <summary>
/// CAN FD 接收回调:接收原始帧 → 查找 DBC → 解码信号 → 触发 OnDbcMessageDecoded 事件。
/// </summary>
private void OnCanFdReceived(ref int AObj, ref TLIBCANFD AData)
{
if (_disposed) return;
// 始终缓存最新报文,供界面轮询刷新
int identifier = AData.FIdentifier;
var canfdCopy = AData;
RealTimeMessages[identifier] = canfdCopy;
if (OnDbcMessageDecoded == null) return;
try
{
uint channel = AData.FIdxChn;
if (channel >= DBCParse.MsgDatabase.Count) return;
// 在 DBC 数据库中查找匹配的报文
var msgList = DBCParse.MsgDatabase[(int)channel];
var match = msgList.FirstOrDefault(m => m.msg_id == identifier);
if (match == null || match.signal_Name == null || match.signal_Name.Length == 0) return;
// 解码各信号值
var signals = new DBCSignal[match.signal_Name.Length];
for (int i = 0; i < match.signal_Name.Length; i++)
{
double value = 0;
TsMasterApi.tsdb_get_signal_value_canfd(ref canfdCopy, match.msg_name, match.signal_Name[i], ref value);
signals[i] = new DBCSignal
{
strName = Encoding.Default.GetBytes(match.signal_Name[i]),
nRawvalue = value,
nFactor = 1,
nOffset = 0
};
}
var decoded = new DBCMessage
{
nID = (uint)identifier,
nSignalCount = signals.Length,
vSignals = signals
};
OnDbcMessageDecoded?.Invoke(channel, decoded);
}
catch (Exception ex)
{
Debug.WriteLine($"DBC 信号解码异常:{ex.Message}");
}
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
CAN卡设备();
GC.SuppressFinalize(this);
}
#endregion
/// <summary>
/// 初始化TSMasterCAN
/// </summary>
/// <param name="ProjectName"></param>
/// <param name="filePath"></param>
/// <returns></returns>
[Browsable(false)]
public static int Init(string ProjectName, string? filePath = null)
{
if (string.IsNullOrEmpty(filePath))
{
return TsMasterApi.initialize_lib_tsmaster(ProjectName);
}
else
{
return TsMasterApi.initialize_lib_tsmaster_with_project(ProjectName, Path.GetFullPath(filePath));
}
}
/// <summary>
/// 释放TSMasterCAN
/// </summary>
[Browsable(false)]
public static void Release()
{
TsMasterApi.finalize_lib_tsmaster();
}
public static int obj = 0;
public static TCANFDQueueEvent_Win32 listener;
[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;
}
[Browsable(false)]
public static int UnRegisterListener(TCANFDQueueEvent_Win32 listenEvent = null)
{
if (listenEvent is not null)
{
listener = listenEvent;
}
var re = TsMasterApi.tsapp_unregister_event_canfd(ref obj, listener);
Debug.Assert(re == 0);
return re;
}
/// <summary>
/// 连接
/// </summary>
/// <returns></returns>
public static int Connect()
{
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}");
}
}
else
{
Debug.WriteLine($"CAN连接失败,错误代码:{re}");
return re;
}
return re;
}
/// <summary>
/// 断开
/// </summary>
/// <returns></returns>
public static int DisConnect()
{
var re = TsMasterApi.tsapp_disconnect();
//Debug.Assert(re == 0);
if (re == 0)
{
ConnectFlag = false;
Task.Run(() => DisConnectEvent?.Invoke());
}
else
{
Debug.WriteLine($"断开CAN连接失败,错误代码:{re}");
}
return re;
}
/// <summary>
/// 加载DBC
/// </summary>
/// <param name="filePath"></param>
/// <param name="channel"></param>
/// <param name="databaseID"></param>
/// <returns></returns>
[Browsable(false)]
public static int LoadDBC(string filePath, int[] channel, out uint databaseID)
{
databaseID = 0;
var re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID);
foreach (var item in channel)
{
DBCParse.parse(databaseID, item);
DBCParse.rbs_parse(databaseID, item);
}
return re;
}
/// <summary>
/// 卸载DBC
/// </summary>
/// <param name="databaseID"></param>
/// <returns></returns>
[Browsable(false)]
public static int UnLoadDBC(uint? databaseID = null)
{
if (databaseID == null)
{
return TsMasterApi.tsdb_unload_can_dbs();
}
else
{
return TsMasterApi.tsdb_unload_can_db(databaseID.Value);
}
}
/// <summary>
/// 开始记录日志
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static int StartLogging(string filePath)
{
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
}
/// <summary>
/// 结束记录日志
/// </summary>
/// <returns></returns>
public static int StopLogging()
{
return TsMasterApi.tsapp_stop_logging();
}
/// <summary>
/// 弹出通道映射窗口
/// </summary>
/// <param name="isWait"></param>
/// <returns></returns>
[Browsable(false)]
public static int ShowChannelMappingWindow(bool isWait = false)
{
return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait);
}
/// <summary>
/// 获取信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <returns></returns>
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);
return value;
}
/// <summary>
/// 获取信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="value"></param>
/// <returns></returns>
[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);
}
/// <summary>
/// 获取信号值
/// </summary>
/// <param name="ACANFD"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="value"></param>
/// <returns></returns>
[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);
}
/// <summary>
/// 设置信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="AValue"></param>
/// <param name="isSend"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
/// <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);
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 (isSend)
{
if (sendPeriod == 0)
{
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
}
else
{
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
}
}
return 0;
}
/// <summary>
/// 设置信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="sendPeriod"></param>
/// <returns></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);
find.ACANFD.FIdxChn = (byte)channel;
if (sendPeriod == 0)
{
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
}
else
{
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
}
}
/// <summary>
/// 发送自定义报文
/// </summary>
/// <returns></returns>
[Browsable(false)]
public static int SetMsg(TLIBCANFD msg)
{
return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
}
/// <summary>
/// 设置报文
/// </summary>
/// <param name="channel">通道</param>
/// <param name="ID">DBC数据库ID</param>
/// <param name="bytes">报文数组</param>
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));
}
}
/// <summary>
/// 发送自定义报文
/// </summary>
/// <param name="AIdxChn"></param>
/// <param name="AID"></param>
/// <param name="AIsTx"></param>
/// <param name="AIsExt"></param>
/// <param name="AIsRemote"></param>
/// <param name="ADLC"></param>
/// <param name="ADataArray"></param>
/// <param name="AIsFD"></param>
/// <param name="AIsBRS"></param>
/// <param name="isUpdateDBCDatabase"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
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;
if (sendPeriod == 0)
{
re = TsMasterApi.tsapp_transmit_canfd_async(ref send);
}
else
{
re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod);
}
if (isUpdateDBCDatabase)
{
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;
}
/// <summary>
/// 添加循环发送报文
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod)
{
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
find.ACANFD.FIdxChn = channel;
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
}
/// <summary>
/// 清除循环发送的报文
/// </summary>
/// <returns></returns>
public static int DeleteCyclicMsgs()
{
return TsMasterApi.tsapp_delete_cyclic_msgs();
}
/// <summary>
/// 获取错误提示
/// </summary>
/// <param name="errorCode">错误代码</param>
/// <returns></returns>
public static string GetErrorDescription(int errorCode)
{
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}";
}
}
}