Files
ACP/TSMasterLIN/LIN.cs
T
2026-08-05 11:53:51 +08:00

383 lines
12 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 TSMasterLIN
{
[ACPCommand]
public class LIN : IDisposable
{
#region 静态成员
public static event Action? ConnectEvent;
public static event Action? DisConnectEvent;
public static bool ConnectFlag { get; set; } = false;
/// <summary>
/// 实时接收到的 LIN 报文缓存(按帧 ID 索引,保留最新帧)。
/// </summary>
public static ConcurrentDictionary<int, TLIBLIN> RealTimeMessages { get; } = new();
#endregion
#region 实例字段与属性
/// <summary>设备类型号</summary>
public uint DeviceType { get; }
/// <summary>设备索引</summary>
public uint DeviceIndex { get; }
/// <summary>最大通道数</summary>
public int MaxChannels { get; }
/// <summary>波特率(LIN 常用 19.2 kbps</summary>
public float BaudRate { get; }
/// <summary>LIN 协议版本</summary>
public LIN_PROTOCOL Protocol { get; }
/// <summary>
/// LIN 帧解码事件:当接收到帧后触发。
/// 参数:(通道号, 帧信息)
/// </summary>
public event Action<uint, TLIBLIN>? OnLinFrameReceived;
private bool _disposed;
private bool _isOpened;
private TLINQueueEvent_Win32? _instanceListener;
#endregion
#region 构造函数
/// <summary>默认构造函数(1 通道、19.2 kbps、LIN 2.1</summary>
public LIN() : this(43, 0, 1, 19.2f, LIN_PROTOCOL.LIN_PROTOCOL_21) { }
/// <summary>
/// 带配置参数的构造函数。
/// </summary>
/// <param name="deviceType">设备类型号</param>
/// <param name="deviceIndex">设备索引</param>
/// <param name="maxChannels">最大通道数</param>
/// <param name="baudRate">波特率(常用 19.2</param>
/// <param name="protocol">LIN 协议版本</param>
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 实例方法 设备管理
/// <summary>
/// 打开设备:初始化 TSMaster 库 → 设置通道数 → 配置波特率与协议 → 连接 → 注册监听。
/// </summary>
/// <returns>true 表示成功</returns>
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;
}
}
/// <summary>
/// 关闭 LIN 设备:注销监听 → 断开连接。
/// </summary>
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;
}
}
/// <summary>
/// LIN 接收回调:缓存最新帧 → 触发 OnLinFrameReceived 事件。
/// </summary>
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
/// <summary>
/// 初始化 TSMasterLIN
/// </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>
/// 释放 TSMaster
/// </summary>
[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;
}
/// <summary>
/// 连接
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 断开
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 设置节点角色(LIN 特有:主节点/从节点)
/// LIN 是单主多从总线,只有 Master 能发起帧头调度。
/// </summary>
/// <param name="channel">通道号(0-based</param>
/// <param name="nodeType">节点类型:T_MasterNode / T_SlaveNode</param>
/// <returns></returns>
public static int SetNodeType(byte channel, TLINNodeType nodeType)
{
return TsMasterApi.tslin_set_node_functiontype(channel, nodeType);
}
/// <summary>
/// 设置当前通道为主节点
/// </summary>
public static int SetMaster(byte channel)
{
return TsMasterApi.tslin_set_node_functiontype(channel, TLINNodeType.T_MasterNode);
}
/// <summary>
/// 设置当前通道为从节点
/// </summary>
public static int SetSlave(byte channel)
{
return TsMasterApi.tslin_set_node_functiontype(channel, TLINNodeType.T_SlaveNode);
}
/// <summary>
/// 弹出通道映射窗口
/// </summary>
[Browsable(false)]
public static int ShowChannelMappingWindow(bool isWait = false)
{
return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait);
}
/// <summary>
/// 发送一帧 LIN(异步)。
/// LIN 发送语义:Master 发帧头(Header)后由指定节点应答数据。
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
[Browsable(false)]
public static int SendMsg(TLIBLIN msg)
{
return TsMasterApi.tsapp_transmit_lin_async(ref msg);
}
/// <summary>
/// 发送一帧 LIN(按参数构造)。
/// </summary>
/// <param name="AIdxChn">通道索引(0-based</param>
/// <param name="AID">帧 ID6 位有效)</param>
/// <param name="ADLC">数据长度(1~8 字节)</param>
/// <param name="AIsTx">true=Master 同时发送数据(Master 发送帧+数据);false=仅发帧头,等待从节点应答</param>
/// <returns></returns>
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);
}
/// <summary>
/// 发送一帧 LIN(携带数据)。
/// </summary>
/// <param name="AIdxChn">通道索引</param>
/// <param name="AID">帧 ID</param>
/// <param name="AData">数据数组(最多 8 字节)</param>
/// <param name="AIsTx">true=Master 发送数据;false=仅发帧头</param>
/// <returns></returns>
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);
}
/// <summary>
/// 开始记录日志
/// </summary>
public static int StartLogging(string filePath)
{
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
}
/// <summary>
/// 结束记录日志
/// </summary>
public static int StopLogging()
{
return TsMasterApi.tsapp_stop_logging();
}
/// <summary>
/// 获取错误提示
/// </summary>
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}";
}
}
}