diff --git a/ACP.sln b/ACP.sln index 34931f0..cee7287 100644 --- a/ACP.sln +++ b/ACP.sln @@ -45,6 +45,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ACP", "ACP\ACP.csproj", "{C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSMasterCAN", "TSMasterCAN\TSMasterCAN.csproj", "{B45DC94C-2CBB-4119-8787-B52A9E7EC58B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSMasterFlexRay", "TSMasterFlexRay\TSMasterFlexRay.csproj", "{68B228BA-52B4-4C01-B260-235F5444ADA7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSMasterLIN", "TSMasterLIN\TSMasterLIN.csproj", "{75F658D1-5DC4-4638-8959-2E7A87212B1D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +135,14 @@ Global {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Debug|Any CPU.Build.0 = Debug|Any CPU {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Release|Any CPU.ActiveCfg = Release|Any CPU {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Release|Any CPU.Build.0 = Release|Any CPU + {68B228BA-52B4-4C01-B260-235F5444ADA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68B228BA-52B4-4C01-B260-235F5444ADA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68B228BA-52B4-4C01-B260-235F5444ADA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68B228BA-52B4-4C01-B260-235F5444ADA7}.Release|Any CPU.Build.0 = Release|Any CPU + {75F658D1-5DC4-4638-8959-2E7A87212B1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75F658D1-5DC4-4638-8959-2E7A87212B1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75F658D1-5DC4-4638-8959-2E7A87212B1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75F658D1-5DC4-4638-8959-2E7A87212B1D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TSMasterFlexRay/FlexRay.cs b/TSMasterFlexRay/FlexRay.cs new file mode 100644 index 0000000..8d3a6a7 --- /dev/null +++ b/TSMasterFlexRay/FlexRay.cs @@ -0,0 +1,470 @@ + +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 TSMasterFlexRay +{ + + [ACPCommand] + public class FlexRay : IDisposable + { + #region 静态成员 + + public static event Action? ConnectEvent; + public static event Action? DisConnectEvent; + public static bool ConnectFlag { get; set; } = false; + + /// + /// 实时接收到的 FlexRay 报文缓存(按 SlotId 索引,保留最新帧)。 + /// 供界面轮询读取。 + /// + public static ConcurrentDictionary RealTimeMessages { get; } = new(); + + #endregion + + #region 实例字段与属性 + + /// 设备类型号(TSMaster 中作为配置参考保留) + public uint DeviceType { get; } + /// 设备索引 + public uint DeviceIndex { get; } + /// 最大通道数 + public int MaxChannels { get; } + /// 工程路径(FlexRay 需通过 TSMaster 工程文件加载 Cluster 配置) + public string ProjectPath { get; } + + /// 数据库解析器 + public FlexRayDBParse DBParser => FlexRayDBParse.Instance; + + /// + /// FlexRay 帧解码事件:当接收到帧并通过数据库解码后触发。 + /// 参数:(通道号, 解码后的报文信息) + /// + public event Action? OnFrameDecoded; + + private bool _disposed; + private bool _isOpened; + private TFlexRayQueueEvent_Win32? _instanceListener; + private TFlexRayQueueEvent_Win32? _preTxListener; + + #endregion + + #region 构造函数 + + /// 默认构造函数(使用默认工程路径) + public FlexRay() : this(43, 0, 2, "") { } + + /// + /// 带配置参数的构造函数。 + /// + /// 设备类型号 + /// 设备索引 + /// 最大通道数 + /// TSMaster 工程路径(FlexRay 必须加载工程) + public FlexRay(uint deviceType, uint deviceIndex, int maxChannels, string projectPath) + { + DeviceType = deviceType; + DeviceIndex = deviceIndex; + MaxChannels = maxChannels; + ProjectPath = projectPath; + } + + #endregion + + #region 实例方法 — 设备管理 + + /// + /// 打开设备:加载工程 → 连接 → 启动 RBS → 注册监听。 + /// FlexRay 的硬件配置(波特率、集群参数等)由 TSMaster 工程统一管理, + /// 代码中无法直接设置,需通过 ShowChannelMappingWindow 或工程本身配置。 + /// + /// true 表示成功 + public bool 打开设备() + { + if (_isOpened) return true; + if (ConnectFlag) { _isOpened = true; return true; } + + try + { + // 1. 通过工程文件初始化(FlexRay 必须依赖工程) + var re = Init("TSMasterFlexRay", ProjectPath); + if (re != 0) + { + Debug.WriteLine($"TSMaster FlexRay 工程加载失败,错误代码:{re}"); + return false; + } + + // 2. 使能接收 FIFO(可选,用于批量拉取模式) + TsMasterApi.tsfifo_enable_receive_fifo(); + + // 3. 连接硬件 + re = Connect(); + if (re != 0) + { + Debug.WriteLine($"FlexRay 连接失败,错误代码:{re}"); + return false; + } + + // 4. 注册接收监听 + _instanceListener = new TFlexRayQueueEvent_Win32(OnFlexRayReceived); + RegisterListener(_instanceListener); + + _isOpened = true; + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"打开 FlexRay 设备异常:{ex.Message}"); + return false; + } + } + + /// + /// 关闭 FlexRay 设备:注销监听 → 停止 RBS → 断开连接。 + /// + public void 关闭FlexRay设备() + { + if (!_isOpened && !ConnectFlag) return; + + try + { + if (_preTxListener != null) + { + UnRegisterPreTxListener(_preTxListener); + _preTxListener = null; + } + if (_instanceListener != null) + { + UnRegisterListener(_instanceListener); + _instanceListener = null; + } + DisConnect(); + } + catch (Exception ex) + { + Debug.WriteLine($"关闭FlexRay设备异常:{ex.Message}"); + } + finally + { + _isOpened = false; + } + } + + /// + /// FlexRay 接收回调:接收原始帧 → 缓存 → 触发 OnFrameDecoded 事件。 + /// + private void OnFlexRayReceived(ref int AObj, ref TLIBFlexRay AData) + { + if (_disposed) return; + + // 始终缓存最新帧(按 SlotId) + RealTimeMessages[AData.FSlotId] = AData; + + if (OnFrameDecoded == null) return; + + try + { + var decoded = new FlexRayDBMessage + { + nSlotId = AData.FSlotId, + nCycleNumber = AData.FCycleNumber, + nChannelMask = AData.FChannelMask, + nDataLength = AData.FActualPayloadLength, + Data = new byte[AData.FActualPayloadLength] + }; + Array.Copy(AData.FData, decoded.Data, AData.FActualPayloadLength); + + OnFrameDecoded?.Invoke(AData.FIdxChn, decoded); + } + catch (Exception ex) + { + Debug.WriteLine($"FlexRay 帧解码异常:{ex.Message}"); + } + } + + #endregion + + #region IDisposable + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + 关闭FlexRay设备(); + GC.SuppressFinalize(this); + } + + #endregion + + /// + /// 初始化 TSMaster FlexRay(必须加载工程文件) + /// + /// 应用名 + /// 工程路径,为空则仅初始化库 + /// + [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)); + } + } + + /// + /// 释放 TSMaster + /// + [Browsable(false)] + public static void Release() + { + TsMasterApi.finalize_lib_tsmaster(); + } + + [Browsable(false)] + public static int RegisterListener(TFlexRayQueueEvent_Win32 listenEvent) + { + int obj = 0; + var re = TsMasterApi.tsapp_register_event_flexray(ref obj, listenEvent); + Debug.Assert(re == 0); + return re; + } + + [Browsable(false)] + public static int UnRegisterListener(TFlexRayQueueEvent_Win32 listenEvent) + { + int obj = 0; + var re = TsMasterApi.tsapp_unregister_event_flexray(ref obj, listenEvent); + Debug.Assert(re == 0); + return re; + } + + /// + /// 注册发送前回调(FlexRay 特有,可用于发送前修改帧内容) + /// + /// + /// + [Browsable(false)] + public static int RegisterPreTxListener(TFlexRayQueueEvent_Win32 preTxEvent) + { + int obj = 0; + return TsMasterApi.tsapp_register_pretx_event_flexray(ref obj, preTxEvent); + } + + [Browsable(false)] + public static int UnRegisterPreTxListener(TFlexRayQueueEvent_Win32 preTxEvent) + { + int obj = 0; + return TsMasterApi.tsapp_unregister_pretx_event_flexray(ref obj, preTxEvent); + } + + /// + /// 连接(含 RBS 启动) + /// + /// + public static int Connect() + { + var re = TsMasterApi.tsapp_connect(); + if (re == 0) + { + // FlexRay RBS 需先 enable 再 start + TsMasterApi.tscom_flexray_rbs_enable(true); + re = TsMasterApi.tscom_flexray_rbs_start(); + if (re == 0) + { + ConnectFlag = true; + Task.Run(() => ConnectEvent?.Invoke()); + } + else + { + Debug.WriteLine($"FlexRay RBS 启动失败,错误代码:{re}"); + } + } + else + { + Debug.WriteLine($"FlexRay 连接失败,错误代码:{re}"); + } + return re; + } + + /// + /// 断开(含 RBS 停止) + /// + /// + public static int DisConnect() + { + var re = TsMasterApi.tsapp_disconnect(); + if (re == 0) + { + TsMasterApi.tscom_flexray_rbs_enable(false); + TsMasterApi.tscom_flexray_rbs_stop(); + ConnectFlag = false; + Task.Run(() => DisConnectEvent?.Invoke()); + } + else + { + Debug.WriteLine($"断开 FlexRay 连接失败,错误代码:{re}"); + } + return re; + } + + /// + /// 获取工程中 FlexRay 数据库数量 + /// + /// + public static int GetDBCount() + { + int count = 0; + TsMasterApi.tsdb_get_flexray_db_count(ref count); + return count; + } + + /// + /// 弹出通道映射窗口(FlexRay 硬件配置推荐通过此窗口完成) + /// + /// + /// + [Browsable(false)] + public static int ShowChannelMappingWindow(bool isWait = false) + { + return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait); + } + + /// + /// 发送一帧 FlexRay(异步) + /// + /// + /// + [Browsable(false)] + public static int SendMsg(TLIBFlexRay msg) + { + return tsapp_transmit_flexray_async(ref msg); + } + + /// + /// 发送一帧 FlexRay(按参数构造) + /// + /// 通道索引(0-based) + /// 通道掩码:1=A, 2=B, 3=AB + /// 有效负载长度 + /// 基准循环号 + /// 时隙号 + /// 数据数组 + /// + public static int SendMsg(byte AIdxChn, byte AChannelMask, byte ADLC, + byte ABaseCycle, ushort ASlotId, byte[] AData) + { + var send = new TLIBFlexRay(AIdxChn, AChannelMask, ADLC, ABaseCycle, ASlotId, AData); + return tsapp_transmit_flexray_async(ref send); + } + + /// + /// 通过 FIFO 批量拉取已接收的 FlexRay 消息 + /// + /// 缓冲区大小 + /// 通道号(0-based) + /// true=TX, false=RX + /// + public static TLIBFlexRay[] ReceiveMsgs(ref int bufferSize, byte AChn, bool ATxRx) + { + TLIBFlexRay[] buffer = new TLIBFlexRay[bufferSize]; + TsMasterApi.tsfifo_receive_flexray_msgs_list(ref buffer, ref bufferSize, AChn, ATxRx); + return buffer; + } + + /// + /// 通过 RBS 地址获取信号值(地址格式:通道/Cluster/ECU/Frame/Signal) + /// + /// + /// + public static double GetSignalValue(string address) + { + double value = 0; + TsMasterApi.tscom_flexray_rbs_get_signal_value_by_address(address, ref value); + return value; + } + + /// + /// 通过 RBS 地址设置信号值 + /// + /// + /// + /// + public static int SetSignalValue(string address, double value) + { + TsMasterApi.tscom_flexray_rbs_set_signal_value_by_address(address, value); + return 0; + } + + /// + /// 按名称激活 Cluster(RBS 仿真) + /// + /// 通道索引 + /// true=激活 + /// Cluster 名称 + /// + public static int ActivateCluster(int chnIdx, bool enable, string clusterName) + { + return TsMasterApi.tscom_flexray_rbs_activate_cluster_by_name(chnIdx, enable, clusterName, false); + } + + /// + /// 按名称激活 ECU(RBS 仿真) + /// + public static int ActivateECU(int chnIdx, bool enable, string clusterName, string ecuName) + { + return TsMasterApi.tscom_flexray_rbs_activate_ecu_by_name(chnIdx, enable, clusterName, ecuName, false); + } + + /// + /// 按名称激活 Frame(RBS 仿真) + /// + public static int ActivateFrame(int chnIdx, bool enable, string clusterName, string ecuName, string frameName) + { + return TsMasterApi.tscom_flexray_rbs_activate_frame_by_name(chnIdx, enable, clusterName, ecuName, frameName); + } + + /// + /// 开始记录日志 + /// + public static int StartLogging(string filePath) + { + return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath)); + } + + /// + /// 结束记录日志 + /// + public static int StopLogging() + { + return TsMasterApi.tsapp_stop_logging(); + } + + /// + /// 获取错误提示 + /// + public static string GetErrorDescription(int errorCode) + { + IntPtr ADesc = IntPtr.Zero; + TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc); + if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}"; + string? description = Marshal.PtrToStringAnsi(ADesc); + return description ?? $"未知错误代码: {errorCode}"; + } + + // FlexRay 发送函数(TSMaster.dll 中 StdCall 导出,需手动声明) + [DllImport(".\\TSMaster.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] + public static extern int tsapp_transmit_flexray_async(ref TLIBFlexRay AMsg); + } +} diff --git a/TSMasterFlexRay/FlexRayDBParse.cs b/TSMasterFlexRay/FlexRayDBParse.cs new file mode 100644 index 0000000..5e4300f --- /dev/null +++ b/TSMasterFlexRay/FlexRayDBParse.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json.Serialization; +using TSMaster; + +namespace TSMasterFlexRay +{ + /// + /// FlexRay 网络(Cluster) + /// + public struct flexray_network + { + [JsonInclude] + public string network_name; + [JsonInclude] + public flexray_node[] flexray_nodes; + } + + /// + /// FlexRay 节点(ECU) + /// + public struct flexray_node + { + [JsonInclude] + public string node_name; + [JsonInclude] + public flexray_message[] tx_flexray_Messages; + [JsonInclude] + public flexray_message[] rx_flexray_Messages; + } + + /// + /// FlexRay 帧(Frame) + /// + public struct flexray_message + { + [JsonInclude] + public string message_name; + [JsonInclude] + public int slot_id; + [JsonInclude] + public int channel_mask; + [JsonInclude] + public int base_cycle; + [JsonInclude] + public int rep_cycle; + [JsonInclude] + public bool is_startup_frame; + [JsonInclude] + public int dlc; + [JsonInclude] + public string[] signals; + } + + /// + /// FlexRay 解码后的信号信息 + /// + public struct FlexRaySignal + { + /// 信号名称 + public string name; + /// 起始位 + public int start_bit; + /// 长度(bit) + public int length; + /// 因子 + public double factor; + /// 偏移 + public double offset; + /// 初始值 + public double init_value; + /// 信号类型 + public TSignalType signal_type; + /// 字节序(true=小端) + public bool is_intel; + } + + /// + /// FlexRay 解码后的报文信息 + /// + public class FlexRayDBMessage + { + /// 时隙号 + public int nSlotId; + /// 周期号 + public int nCycleNumber; + /// 通道掩码 + public int nChannelMask; + /// 有效负载长度 + public int nDataLength; + /// 数据 + public byte[] Data = Array.Empty(); + } + + public class FlexRayDBParse + { + private static FlexRayDBParse? _instance; + /// 单例实例 + public static FlexRayDBParse Instance => _instance ??= new FlexRayDBParse(); + + /// 最大通道数(等于 MsgDatabase 的维度) + public int MaxChannels => MsgDatabase.Count; + + /// + /// FlexRay 报文数据库(按通道索引,每个通道包含该通道上所有帧)。 + /// + public static List> MsgDatabase { get; set; } = + [.. Enumerable.Range(0, 12).Select(_ => new List())]; + + /// FlexRay 网络拓扑(Cluster/ECU/Frame/Signal) + public static flexray_network Network; + + /// + /// 解析指定索引的 FlexRay 数据库,填充 MsgDatabase 和 Network。 + /// FlexRay 数据库由工程加载,通过 tsdb_get_flexray_db_count 获取数量。 + /// + /// 数据库索引(0-based) + /// 映射的通道号 + public static void parse(int dbIndex, int channel) + { + int ASignalCount = 0; + int AFrameCount = 0; + int AECUcount = 0; + long ASupportMask = 0; + long AFlags = 0; + IntPtr temp1 = IntPtr.Zero; + IntPtr temp2 = IntPtr.Zero; + + // 1. 获取 Cluster 属性 + int ret = TsMasterApi.tsdb_get_flexray_db_properties_by_index_verbose( + dbIndex, ref ASignalCount, ref AFrameCount, ref AECUcount, + ref ASupportMask, ref AFlags, ref temp1, ref temp2); + + string clusterName = Marshal.PtrToStringAnsi(temp1) ?? ""; + Network = new flexray_network + { + network_name = clusterName, + flexray_nodes = new flexray_node[AECUcount] + }; + + // 2. 遍历所有 ECU + for (int i = 0; i < AECUcount; i++) + { + int ATXFramecount = 0; + int ARXFramecount = 0; + + TsMasterApi.tsdb_get_flexray_ecu_properties_by_index_verbose( + dbIndex, i, ref ATXFramecount, ref ARXFramecount, ref temp1, ref temp2); + + string ecuName = Marshal.PtrToStringAnsi(temp1) ?? ""; + Network.flexray_nodes[i].node_name = ecuName; + Network.flexray_nodes[i].tx_flexray_Messages = new flexray_message[ATXFramecount]; + Network.flexray_nodes[i].rx_flexray_Messages = new flexray_message[ARXFramecount]; + + // TX Frame + for (int tx = 0; tx < ATXFramecount; tx++) + { + var msg = ParseFrame(dbIndex, i, tx, true); + Network.flexray_nodes[i].tx_flexray_Messages[tx] = msg; + MsgDatabase[channel].Add(msg); + } + + // RX Frame + for (int rx = 0; rx < ARXFramecount; rx++) + { + var msg = ParseFrame(dbIndex, i, rx, false); + Network.flexray_nodes[i].rx_flexray_Messages[rx] = msg; + } + } + + MsgDatabase[channel].Sort((a, b) => a.slot_id.CompareTo(b.slot_id)); + } + + /// + /// 解析单帧属性(TX 或 RX) + /// + private static flexray_message ParseFrame(int dbIndex, int ecuIndex, int frameIndex, bool isTx) + { + int AFRChannelMask = 0; + int AFRBaseCycle = 0; + int ARepCycle = 0; + bool isStartupFrame = false; + int ASlotID = 0; + long CycleMask = 0; + int AFRSignalCount = 0; + int AFRDLC = 0; + IntPtr temp1 = IntPtr.Zero; + IntPtr temp2 = IntPtr.Zero; + + TsMasterApi.tsdb_get_flexray_frame_properties_by_index_verbose( + dbIndex, ecuIndex, frameIndex, isTx, + ref AFRChannelMask, ref AFRBaseCycle, ref ARepCycle, ref isStartupFrame, + ref ASlotID, ref CycleMask, ref AFRSignalCount, ref AFRDLC, + ref temp1, ref temp2); + + string frameName = Marshal.PtrToStringAnsi(temp1) ?? ""; + var msg = new flexray_message + { + message_name = frameName, + slot_id = ASlotID, + channel_mask = AFRChannelMask, + base_cycle = AFRBaseCycle, + rep_cycle = ARepCycle, + is_startup_frame = isStartupFrame, + dlc = AFRDLC, + signals = new string[AFRSignalCount] + }; + + // 遍历信号 + for (int s = 0; s < AFRSignalCount; s++) + { + TSignalType Asignaltype = (TSignalType)0; + TFlexRayCompuMethod ACompuMethod = (TFlexRayCompuMethod)0; + bool AIsIntel = false; + int AStartBit = 0; + int AUpdateBit = 0; + int ALength = 0; + double AFactor = 0; + double AOffset = 0; + double AInitValue = 0; + + TsMasterApi.tsdb_get_flexray_signal_properties_by_index_verbose( + dbIndex, ecuIndex, frameIndex, s, isTx, + ref Asignaltype, ref ACompuMethod, ref AIsIntel, + ref AStartBit, ref AUpdateBit, ref ALength, + ref AFactor, ref AOffset, ref AInitValue, + ref temp1, ref temp2); + + string sigName = Marshal.PtrToStringAnsi(temp1) ?? ""; + msg.signals[s] = sigName; + } + + return msg; + } + + /// + /// 解析网络拓扑(仅填充 Network,不写入 MsgDatabase) + /// + public static void rbs_parse(int dbIndex, int channel) + { + // parse 已同时填充 Network 和 MsgDatabase,此处保持与 CAN 一致的接口 + parse(dbIndex, channel); + } + } +} diff --git a/TSMasterFlexRay/Interop.TSMasterAPI.dll b/TSMasterFlexRay/Interop.TSMasterAPI.dll new file mode 100644 index 0000000..1d1400b Binary files /dev/null and b/TSMasterFlexRay/Interop.TSMasterAPI.dll differ diff --git a/TSMasterFlexRay/TSMaster.dll b/TSMasterFlexRay/TSMaster.dll new file mode 100644 index 0000000..a34dd33 Binary files /dev/null and b/TSMasterFlexRay/TSMaster.dll differ diff --git a/TSMasterFlexRay/TSMasterFlexRay.csproj b/TSMasterFlexRay/TSMasterFlexRay.csproj new file mode 100644 index 0000000..3c6396c --- /dev/null +++ b/TSMasterFlexRay/TSMasterFlexRay.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + Interop.TSMasterAPI.dll + + + + diff --git a/TSMasterLIN/Interop.TSMasterAPI.dll b/TSMasterLIN/Interop.TSMasterAPI.dll new file mode 100644 index 0000000..1d1400b Binary files /dev/null and b/TSMasterLIN/Interop.TSMasterAPI.dll differ diff --git a/TSMasterLIN/LIN.cs b/TSMasterLIN/LIN.cs new file mode 100644 index 0000000..3094a42 --- /dev/null +++ b/TSMasterLIN/LIN.cs @@ -0,0 +1,382 @@ + +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 TSMasterLIN +{ + [ACPCommand] + public class LIN : IDisposable + { + #region 静态成员 + + public static event Action? ConnectEvent; + public static event Action? DisConnectEvent; + public static bool ConnectFlag { get; set; } = false; + + /// + /// 实时接收到的 LIN 报文缓存(按帧 ID 索引,保留最新帧)。 + /// + public static ConcurrentDictionary RealTimeMessages { get; } = new(); + + #endregion + + #region 实例字段与属性 + + /// 设备类型号 + public uint DeviceType { get; } + /// 设备索引 + public uint DeviceIndex { get; } + /// 最大通道数 + public int MaxChannels { get; } + /// 波特率(LIN 常用 19.2 kbps) + public float BaudRate { get; } + /// LIN 协议版本 + public LIN_PROTOCOL Protocol { get; } + + /// + /// LIN 帧解码事件:当接收到帧后触发。 + /// 参数:(通道号, 帧信息) + /// + public event Action? OnLinFrameReceived; + + private bool _disposed; + private bool _isOpened; + private TLINQueueEvent_Win32? _instanceListener; + + #endregion + + #region 构造函数 + + /// 默认构造函数(1 通道、19.2 kbps、LIN 2.1) + public LIN() : this(43, 0, 1, 19.2f, LIN_PROTOCOL.LIN_PROTOCOL_21) { } + + /// + /// 带配置参数的构造函数。 + /// + /// 设备类型号 + /// 设备索引 + /// 最大通道数 + /// 波特率(常用 19.2) + /// LIN 协议版本 + public LIN(uint deviceType, uint deviceIndex, int maxChannels, float baudRate, LIN_PROTOCOL protocol) + { + DeviceType = deviceType; + DeviceIndex = deviceIndex; + MaxChannels = maxChannels; + BaudRate = baudRate; + Protocol = protocol; + } + + #endregion + + #region 实例方法 — 设备管理 + + /// + /// 打开设备:初始化 TSMaster 库 → 设置通道数 → 配置波特率与协议 → 连接 → 注册监听。 + /// + /// true 表示成功 + public bool 打开设备() + { + if (_isOpened) return true; + if (ConnectFlag) { _isOpened = true; return true; } + + try + { + // 1. 初始化 TSMaster 库 + var re = Init("TSMasterLIN"); + if (re != 0) + { + Debug.WriteLine($"TSMaster LIN 初始化失败,错误代码:{re}"); + return false; + } + + // 2. 设置通道数 + TsMasterApi.tsapp_set_lin_channel_count(MaxChannels); + + // 3. 配置各通道波特率与协议 + for (int ch = 0; ch < MaxChannels; ch++) + { + TsMasterApi.tsapp_configure_baudrate_lin(ch, BaudRate, (int)Protocol); + } + + // 4. 连接硬件 + re = Connect(); + if (re != 0) + { + Debug.WriteLine($"LIN 连接失败,错误代码:{re}"); + return false; + } + + // 5. 注册接收监听 + _instanceListener = new TLINQueueEvent_Win32(OnLinReceived); + RegisterListener(_instanceListener); + + _isOpened = true; + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"打开 LIN 设备异常:{ex.Message}"); + return false; + } + } + + /// + /// 关闭 LIN 设备:注销监听 → 断开连接。 + /// + public void 关闭LIN设备() + { + if (!_isOpened && !ConnectFlag) return; + + try + { + if (_instanceListener != null) + { + UnRegisterListener(_instanceListener); + _instanceListener = null; + } + DisConnect(); + } + catch (Exception ex) + { + Debug.WriteLine($"关闭LIN设备异常:{ex.Message}"); + } + finally + { + _isOpened = false; + } + } + + /// + /// LIN 接收回调:缓存最新帧 → 触发 OnLinFrameReceived 事件。 + /// + private void OnLinReceived(ref int AObj, ref TLIBLIN AData) + { + if (_disposed) return; + + // 始终缓存最新报文(按 ID) + RealTimeMessages[AData.FIdentifier] = AData; + + OnLinFrameReceived?.Invoke(AData.FIdxChn, AData); + } + + #endregion + + #region IDisposable + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + 关闭LIN设备(); + GC.SuppressFinalize(this); + } + + #endregion + + /// + /// 初始化 TSMasterLIN + /// + /// + /// 工程路径,为空则仅初始化库 + /// + [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)); + } + } + + /// + /// 释放 TSMaster + /// + [Browsable(false)] + public static void Release() + { + TsMasterApi.finalize_lib_tsmaster(); + } + + public static int obj = 0; + public static TLINQueueEvent_Win32? listener; + + [Browsable(false)] + public static int RegisterListener(TLINQueueEvent_Win32 listenEvent) + { + listener = listenEvent; + var re = TsMasterApi.tsapp_register_event_lin(ref obj, listener); + Debug.Assert(re == 0); + return re; + } + + [Browsable(false)] + public static int UnRegisterListener(TLINQueueEvent_Win32? listenEvent = null) + { + if (listenEvent is not null) + { + listener = listenEvent; + } + var re = TsMasterApi.tsapp_unregister_event_lin(ref obj, listener); + Debug.Assert(re == 0); + return re; + } + + /// + /// 连接 + /// + /// + public static int Connect() + { + var re = TsMasterApi.tsapp_connect(); + if (re == 0) + { + ConnectFlag = true; + Task.Run(() => ConnectEvent?.Invoke()); + } + else + { + Debug.WriteLine($"LIN 连接失败,错误代码:{re}"); + } + return re; + } + + /// + /// 断开 + /// + /// + public static int DisConnect() + { + var re = TsMasterApi.tsapp_disconnect(); + if (re == 0) + { + ConnectFlag = false; + Task.Run(() => DisConnectEvent?.Invoke()); + } + else + { + Debug.WriteLine($"断开 LIN 连接失败,错误代码:{re}"); + } + return re; + } + + /// + /// 设置节点角色(LIN 特有:主节点/从节点) + /// LIN 是单主多从总线,只有 Master 能发起帧头调度。 + /// + /// 通道号(0-based) + /// 节点类型:T_MasterNode / T_SlaveNode + /// + public static int SetNodeType(byte channel, TLINNodeType nodeType) + { + return TsMasterApi.tslin_set_node_functiontype(channel, nodeType); + } + + /// + /// 设置当前通道为主节点 + /// + public static int SetMaster(byte channel) + { + return TsMasterApi.tslin_set_node_functiontype(channel, TLINNodeType.T_MasterNode); + } + + /// + /// 设置当前通道为从节点 + /// + public static int SetSlave(byte channel) + { + return TsMasterApi.tslin_set_node_functiontype(channel, TLINNodeType.T_SlaveNode); + } + + /// + /// 弹出通道映射窗口 + /// + [Browsable(false)] + public static int ShowChannelMappingWindow(bool isWait = false) + { + return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait); + } + + /// + /// 发送一帧 LIN(异步)。 + /// LIN 发送语义:Master 发帧头(Header)后由指定节点应答数据。 + /// + /// + /// + [Browsable(false)] + public static int SendMsg(TLIBLIN msg) + { + return TsMasterApi.tsapp_transmit_lin_async(ref msg); + } + + /// + /// 发送一帧 LIN(按参数构造)。 + /// + /// 通道索引(0-based) + /// 帧 ID(6 位有效) + /// 数据长度(1~8 字节) + /// true=Master 同时发送数据(Master 发送帧+数据);false=仅发帧头,等待从节点应答 + /// + public static int SendMsg(byte AIdxChn, int AID, byte ADLC, bool AIsTx) + { + var send = new TLIBLIN((APP_CHANNEL)AIdxChn, (byte)AID, ADLC, AIsTx); + return TsMasterApi.tsapp_transmit_lin_async(ref send); + } + + /// + /// 发送一帧 LIN(携带数据)。 + /// + /// 通道索引 + /// 帧 ID + /// 数据数组(最多 8 字节) + /// true=Master 发送数据;false=仅发帧头 + /// + public static int SendMsg(byte AIdxChn, int AID, byte[] AData, bool AIsTx = true) + { + byte dlc = (byte)Math.Min(AData.Length, 8); + var send = new TLIBLIN((APP_CHANNEL)AIdxChn, (byte)AID, dlc, AIsTx); + Array.Copy(AData, 0, send.FData, 0, dlc); + return TsMasterApi.tsapp_transmit_lin_async(ref send); + } + + /// + /// 开始记录日志 + /// + public static int StartLogging(string filePath) + { + return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath)); + } + + /// + /// 结束记录日志 + /// + public static int StopLogging() + { + return TsMasterApi.tsapp_stop_logging(); + } + + /// + /// 获取错误提示 + /// + public static string GetErrorDescription(int errorCode) + { + IntPtr ADesc = IntPtr.Zero; + TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc); + if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}"; + string? description = Marshal.PtrToStringAnsi(ADesc); + return description ?? $"未知错误代码: {errorCode}"; + } + } +} diff --git a/TSMasterLIN/TSMaster.dll b/TSMasterLIN/TSMaster.dll new file mode 100644 index 0000000..a34dd33 Binary files /dev/null and b/TSMasterLIN/TSMaster.dll differ diff --git a/TSMasterLIN/TSMasterLIN.csproj b/TSMasterLIN/TSMasterLIN.csproj new file mode 100644 index 0000000..3c6396c --- /dev/null +++ b/TSMasterLIN/TSMasterLIN.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + Interop.TSMasterAPI.dll + + + + diff --git a/UIShare/UIShare.csproj b/UIShare/UIShare.csproj index 0bb28bb..3167d6d 100644 --- a/UIShare/UIShare.csproj +++ b/UIShare/UIShare.csproj @@ -25,5 +25,7 @@ + +