471 lines
16 KiB
C#
471 lines
16 KiB
C#
|
||
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;
|
||
|
||
/// <summary>
|
||
/// 实时接收到的 FlexRay 报文缓存(按 SlotId 索引,保留最新帧)。
|
||
/// 供界面轮询读取。
|
||
/// </summary>
|
||
public static ConcurrentDictionary<int, TLIBFlexRay> RealTimeMessages { get; } = new();
|
||
|
||
#endregion
|
||
|
||
#region 实例字段与属性
|
||
|
||
/// <summary>设备类型号(TSMaster 中作为配置参考保留)</summary>
|
||
public uint DeviceType { get; }
|
||
/// <summary>设备索引</summary>
|
||
public uint DeviceIndex { get; }
|
||
/// <summary>最大通道数</summary>
|
||
public int MaxChannels { get; }
|
||
/// <summary>工程路径(FlexRay 需通过 TSMaster 工程文件加载 Cluster 配置)</summary>
|
||
public string ProjectPath { get; }
|
||
|
||
/// <summary>数据库解析器</summary>
|
||
public FlexRayDBParse DBParser => FlexRayDBParse.Instance;
|
||
|
||
/// <summary>
|
||
/// FlexRay 帧解码事件:当接收到帧并通过数据库解码后触发。
|
||
/// 参数:(通道号, 解码后的报文信息)
|
||
/// </summary>
|
||
public event Action<uint, FlexRayDBMessage>? OnFrameDecoded;
|
||
|
||
private bool _disposed;
|
||
private bool _isOpened;
|
||
private TFlexRayQueueEvent_Win32? _instanceListener;
|
||
private TFlexRayQueueEvent_Win32? _preTxListener;
|
||
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
|
||
/// <summary>默认构造函数(使用默认工程路径)</summary>
|
||
public FlexRay() : this(43, 0, 2, "") { }
|
||
|
||
/// <summary>
|
||
/// 带配置参数的构造函数。
|
||
/// </summary>
|
||
/// <param name="deviceType">设备类型号</param>
|
||
/// <param name="deviceIndex">设备索引</param>
|
||
/// <param name="maxChannels">最大通道数</param>
|
||
/// <param name="projectPath">TSMaster 工程路径(FlexRay 必须加载工程)</param>
|
||
public FlexRay(uint deviceType, uint deviceIndex, int maxChannels, string projectPath)
|
||
{
|
||
DeviceType = deviceType;
|
||
DeviceIndex = deviceIndex;
|
||
MaxChannels = maxChannels;
|
||
ProjectPath = projectPath;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 实例方法 — 设备管理
|
||
|
||
/// <summary>
|
||
/// 打开设备:加载工程 → 连接 → 启动 RBS → 注册监听。
|
||
/// FlexRay 的硬件配置(波特率、集群参数等)由 TSMaster 工程统一管理,
|
||
/// 代码中无法直接设置,需通过 ShowChannelMappingWindow 或工程本身配置。
|
||
/// </summary>
|
||
/// <returns>true 表示成功</returns>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭 FlexRay 设备:注销监听 → 停止 RBS → 断开连接。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// FlexRay 接收回调:接收原始帧 → 缓存 → 触发 OnFrameDecoded 事件。
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 初始化 TSMaster FlexRay(必须加载工程文件)
|
||
/// </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();
|
||
}
|
||
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注册发送前回调(FlexRay 特有,可用于发送前修改帧内容)
|
||
/// </summary>
|
||
/// <param name="preTxEvent"></param>
|
||
/// <returns></returns>
|
||
[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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接(含 RBS 启动)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断开(含 RBS 停止)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取工程中 FlexRay 数据库数量
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public static int GetDBCount()
|
||
{
|
||
int count = 0;
|
||
TsMasterApi.tsdb_get_flexray_db_count(ref count);
|
||
return count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 弹出通道映射窗口(FlexRay 硬件配置推荐通过此窗口完成)
|
||
/// </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>
|
||
/// 发送一帧 FlexRay(异步)
|
||
/// </summary>
|
||
/// <param name="msg"></param>
|
||
/// <returns></returns>
|
||
[Browsable(false)]
|
||
public static int SendMsg(TLIBFlexRay msg)
|
||
{
|
||
return tsapp_transmit_flexray_async(ref msg);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送一帧 FlexRay(按参数构造)
|
||
/// </summary>
|
||
/// <param name="AIdxChn">通道索引(0-based)</param>
|
||
/// <param name="AChannelMask">通道掩码:1=A, 2=B, 3=AB</param>
|
||
/// <param name="ADLC">有效负载长度</param>
|
||
/// <param name="ABaseCycle">基准循环号</param>
|
||
/// <param name="ASlotId">时隙号</param>
|
||
/// <param name="AData">数据数组</param>
|
||
/// <returns></returns>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 FIFO 批量拉取已接收的 FlexRay 消息
|
||
/// </summary>
|
||
/// <param name="bufferSize">缓冲区大小</param>
|
||
/// <param name="AChn">通道号(0-based)</param>
|
||
/// <param name="ATxRx">true=TX, false=RX</param>
|
||
/// <returns></returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 RBS 地址获取信号值(地址格式:通道/Cluster/ECU/Frame/Signal)
|
||
/// </summary>
|
||
/// <param name="address"></param>
|
||
/// <returns></returns>
|
||
public static double GetSignalValue(string address)
|
||
{
|
||
double value = 0;
|
||
TsMasterApi.tscom_flexray_rbs_get_signal_value_by_address(address, ref value);
|
||
return value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 RBS 地址设置信号值
|
||
/// </summary>
|
||
/// <param name="address"></param>
|
||
/// <param name="value"></param>
|
||
/// <returns></returns>
|
||
public static int SetSignalValue(string address, double value)
|
||
{
|
||
TsMasterApi.tscom_flexray_rbs_set_signal_value_by_address(address, value);
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按名称激活 Cluster(RBS 仿真)
|
||
/// </summary>
|
||
/// <param name="chnIdx">通道索引</param>
|
||
/// <param name="enable">true=激活</param>
|
||
/// <param name="clusterName">Cluster 名称</param>
|
||
/// <returns></returns>
|
||
public static int ActivateCluster(int chnIdx, bool enable, string clusterName)
|
||
{
|
||
return TsMasterApi.tscom_flexray_rbs_activate_cluster_by_name(chnIdx, enable, clusterName, false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按名称激活 ECU(RBS 仿真)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按名称激活 Frame(RBS 仿真)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <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}";
|
||
}
|
||
|
||
// FlexRay 发送函数(TSMaster.dll 中 StdCall 导出,需手动声明)
|
||
[DllImport(".\\TSMaster.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
|
||
public static extern int tsapp_transmit_flexray_async(ref TLIBFlexRay AMsg);
|
||
}
|
||
}
|