Initial commit from MyParking project

This commit is contained in:
2026-08-04 10:29:21 +08:00
commit d9432bd529
138 changed files with 16957 additions and 0 deletions
+928
View File
@@ -0,0 +1,928 @@
// C#调用MCU通信桥
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace MCUSerialBridgeCLR
{
/// <summary>
/// 辅助方法与内部 C 结构体封装,用于 P/Invoke 交互
/// </summary>
internal static class PortStructHelper
{
/// <summary>
/// 将任意结构体序列化为字节数组
/// </summary>
/// <typeparam name="T">结构体类型</typeparam>
/// <param name="str">要序列化的结构体</param>
/// <returns>返回结构体对应的字节数组</returns>
/// <remarks>使用 Marshal 分配内存并复制内容</remarks>
public static byte[] StructToBytes<T>(T str)
where T : struct
{
int size = Marshal.SizeOf<T>();
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(str, ptr, false);
Marshal.Copy(ptr, arr, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return arr;
}
/// <summary>
/// 串口端口配置的原生结构体(与 MCU C 层对应)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SerialPortConfigC
{
/// <summary>端口类型(0x01 = Serial</summary>
public byte port_type;
/// <summary>波特率</summary>
public uint baud;
/// <summary>接收帧时间间隔</summary>
public uint receive_frame_ms;
/// <summary>保留字节,填 0</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public byte[] reserved;
}
/// <summary>
/// CAN 端口配置的原生结构体(与 MCU C 层对应)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CANPortConfigC
{
/// <summary>端口类型(0x02 = CAN</summary>
public byte port_type;
/// <summary>波特率</summary>
public uint baud;
/// <summary>最大重发时间</summary>
public uint retry_time_ms;
/// <summary>保留字节,填 0</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public byte[] reserved;
}
}
/// <summary>
/// MCU 固件版本信息结构体
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct VersionInfo
{
/// <summary>产品型号</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string ProductionName;
/// <summary>Git 标签</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string GitTag;
/// <summary>Git commit 哈希值</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string GitCommit;
/// <summary>编译时间(字符串)</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
public string BuildTime;
/// <summary>
/// 转换为可读字符串
/// </summary>
/// <returns>返回包含产品、Tag、Commit、BuildTime 的字符串</returns>
// M层MCU适配:格式化固件版本信息便于日志显示。
public override string ToString()
{
return $"Product: {ProductionName}, Tag: {GitTag}, Commit: {GitCommit}, Built: {BuildTime}";
}
}
/// <summary>
/// MCU 当前运行状态
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct MCUState
{
/// <summary>原始 32 位状态值</summary>
[FieldOffset(0)]
public uint RawValue;
/// <summary>状态子字段 0</summary>
[FieldOffset(0)]
public byte Substate0;
/// <summary>状态子字段 1</summary>
[FieldOffset(1)]
public byte Substate1;
/// <summary>状态子字段 2</summary>
[FieldOffset(2)]
public byte Substate2;
/// <summary>高字节模式标志</summary>
[FieldOffset(3)]
public byte Mode;
/// <summary>是否处于 Bridge 模式</summary>
public bool IsBridge => (Mode & 0x80) == 0;
/// <summary>是否处于 DIVER 模式</summary>
public bool IsDIVER => (Mode & 0x80) != 0;
/// <summary>
/// 返回可读的状态字符串
/// </summary>
/// <returns>例如 "Bridge: Running" 或 "DIVER: Error"</returns>
// M层MCU适配:格式化MCU运行状态便于日志显示。
public override string ToString()
{
string modeStr = IsBridge ? "Bridge" : "DIVER";
uint substate = (uint)(Substate0 | (Substate1 << 8) | (Substate2 << 16));
string subStr = substate switch
{
0x00000000 => "Idle",
0x0000000F => "Running",
0x000000FF => "Error",
0x00000001 => "Configured", // DIVER specific
0x8000000F => "Running",
0x800000FF => "Error",
_ => $"Unknown (0x{substate:X6})",
};
return $"{modeStr}: {subStr}";
}
}
/// <summary>
/// 抽象端口配置基类
/// </summary>
public abstract class PortConfig
{
/// <summary>端口类型(由子类实现)</summary>
public abstract byte PortType { get; }
/// <summary>序列化端口配置为字节数组(供 P/Invoke 使用)</summary>
/// <returns>返回固定长度字节数组(16 bytes</returns>
// M层MCU适配:将端口配置序列化为原生接口字节。
public abstract byte[] ToBytes();
}
/// <summary>
/// 串口配置
/// </summary>
/// <remarks>
/// 构造函数
/// </remarks>
/// <param name="baud">波特率</param>
/// <param name="receiveFrameMs">接收帧间隔</param>
// M层MCU适配:创建串口通信参数配置。
public class SerialPortConfig(uint baud, uint receiveFrameMs) : PortConfig
{
/// <summary>Serial 类型</summary>
public override byte PortType => 0x01;
/// <summary>波特率</summary>
public uint Baud { get; set; } = baud;
/// <summary>接收帧间隔</summary>
public uint ReceiveFrameMs { get; set; } = receiveFrameMs;
/// <summary>
/// 转换为字节数组
/// </summary>
/// <returns>16 字节数组</returns>
// M层MCU适配:序列化串口波特率和组帧时间。
public override byte[] ToBytes()
{
var c = new PortStructHelper.SerialPortConfigC
{
port_type = PortType,
baud = Baud,
receive_frame_ms = ReceiveFrameMs,
reserved = new byte[7],
};
return PortStructHelper.StructToBytes(c);
}
}
/// <summary>
/// CAN 端口配置
/// </summary>
/// <remarks>
/// 构造函数
/// </remarks>
/// <param name="baud">波特率</param>
/// <param name="retryTimeMs">重发间隔</param>
// M层MCU适配:创建CAN通信参数配置。
public class CANPortConfig(uint baud, uint retryTimeMs) : PortConfig
{
/// <summary>CAN 类型</summary>
public override byte PortType => 0x02;
/// <summary>波特率</summary>
public uint Baud { get; set; } = baud;
/// <summary>重发间隔</summary>
public uint RetryTimeMs { get; set; } = retryTimeMs;
/// <summary>
/// 转换为字节数组
/// </summary>
/// <returns>16 字节数组</returns>
// M层MCU适配:序列化CAN波特率和重试时间。
public override byte[] ToBytes()
{
var c = new PortStructHelper.CANPortConfigC
{
port_type = PortType,
baud = Baud,
retry_time_ms = RetryTimeMs,
reserved = new byte[7],
};
return PortStructHelper.StructToBytes(c);
}
}
/// <summary>
/// CAN 帧结构(标准帧 11-bit ID + 1-bit RTR + 4-bit DLC + Payload
/// </summary>
public class CANMessage
{
/// <summary>标准帧 ID0~0x7FF11 位)</summary>
public ushort ID { get; set; }
/// <summary>远程帧标志:false = 数据帧,true = 远程帧</summary>
public bool RTR { get; set; }
/// <summary>数据长度码:0~8</summary>
public byte DLC { get; set; }
/// <summary>数据负载,长度必须严格等于 DLCDLC=0 时可为 null</summary>
public byte[] Payload { get; set; }
/// <summary>
/// 序列化为 MCU 协议字节流
/// </summary>
/// <returns>返回字节数组:2 bytes header + Payload</returns>
/// <exception cref="ArgumentOutOfRangeException">如果 DLC > 8</exception>
/// <exception cref="ArgumentException">如果 Payload 长度 != DLC</exception>
// M层CAN适配:将标准或扩展CAN帧序列化为原生布局。
public byte[] ToBytes()
{
if (DLC > 8)
throw new ArgumentOutOfRangeException(nameof(DLC), "DLC must be 0-8");
if (DLC > 0 && (Payload == null || Payload.Length != DLC))
throw new ArgumentException("Payload length must equal DLC");
// 构造 2 字节 header
ushort header = 0;
header |= (ushort)(ID & 0x7FF); // bits 0-10
if (RTR)
header |= (1 << 11); // bit 11
header |= (ushort)((DLC & 0xF) << 12); // bits 12-15
byte[] result = new byte[2 + DLC];
byte[] headerBytes = BitConverter.GetBytes(header); // 小端序
result[0] = headerBytes[0];
result[1] = headerBytes[1];
if (DLC > 0)
Buffer.BlockCopy(Payload, 0, result, 2, DLC);
return result;
}
/// <summary>
/// 反序列化 MCU 协议字节流为 CANMessage
/// </summary>
/// <param name="data">原始字节数组</param>
/// <param name="length">实际有效长度</param>
/// <returns>CANMessage 实例</returns>
/// <exception cref="ArgumentException">数据长度错误</exception>
// M层CAN适配:从原生缓冲区还原CAN消息。
public static CANMessage FromBytes(byte[] data, uint length)
{
if (data == null || length > data.Length || length < 2)
throw new ArgumentException("Data must be at least 2 bytes");
ushort header = BitConverter.ToUInt16(data, 0);
var msg = new CANMessage
{
ID = (ushort)(header & 0x7FF),
RTR = (header & (1 << 11)) != 0,
DLC = (byte)((header >> 12) & 0xF),
};
if (msg.DLC > 0)
{
if (length < 2 + msg.DLC)
throw new ArgumentException("Data length less than DLC");
msg.Payload = new byte[msg.DLC];
Buffer.BlockCopy(data, 2, msg.Payload, 0, msg.DLC);
}
else
{
msg.Payload = Array.Empty<byte>();
}
return msg;
}
// M层CAN诊断:格式化CAN标识符和数据内容。
public override string ToString()
{
string payloadStr =
(Payload == null || Payload.Length == 0)
? "[]"
: "0x[" + string.Join(" ", Payload.Select(b => $"{b:X2}")) + "]";
return $"CANMessage(ID=0x{ID:X3}, RTR={RTR}, DLC={DLC}, Payload={payloadStr})";
}
}
/// <summary>
/// 内部 P/Invoke 层,直接映射 C DLL 函数
/// </summary>
internal static class MCUSerialBridgeCoreAPI
{
private const string DLL = @"mcu_serial_bridge.dll";
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:打开MCU串口桥设备。
internal static extern MCUSerialBridgeError msb_open(
out IntPtr handle,
[MarshalAs(UnmanagedType.LPStr)] string port,
uint baud
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:关闭MCU串口桥设备。
internal static extern MCUSerialBridgeError msb_close(IntPtr handle);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:复位MCU串口桥。
internal static extern MCUSerialBridgeError msb_reset(IntPtr handle, uint timeout_ms);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:读取MCU固件版本。
public static extern MCUSerialBridgeError msb_version(
IntPtr handle,
out VersionInfo version,
uint timeout_ms
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:读取MCU当前运行状态。
public static extern MCUSerialBridgeError mcu_state(
IntPtr handle,
out MCUState state,
uint timeout
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:下发串口桥端口配置。
internal static extern MCUSerialBridgeError msb_configure(
IntPtr handle,
uint num_ports,
IntPtr ports,
uint timeout
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:读取MCU数字输入。
internal static extern MCUSerialBridgeError msb_read_input(
IntPtr handle,
[Out] byte[] inputs,
uint timeout_ms
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:写入MCU数字输出。
internal static extern MCUSerialBridgeError msb_write_output(
IntPtr handle,
[In] byte[] outputs,
uint timeout_ms
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:从指定串口或CAN端口读取数据。
internal static extern MCUSerialBridgeError msb_read_port(
IntPtr handle,
byte port_index,
[Out] byte[] dst_data,
uint dst_capacity,
out uint out_length,
uint timeout_ms
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
// M层硬件桥接:定义串口或CAN端口收到原生数据时的回调签名。
internal delegate void msb_on_port_data_callback_function_t(
IntPtr dst_data,
uint dst_data_size,
IntPtr user_ctx
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:注册端口数据到达回调。
internal static extern MCUSerialBridgeError msb_register_port_data_callback(
IntPtr handle,
byte port_index,
msb_on_port_data_callback_function_t callback,
IntPtr user_ctx
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:向指定串口或CAN端口写入数据。
internal static extern MCUSerialBridgeError msb_write_port(
IntPtr handle,
byte port_index,
[In] byte[] src_data,
uint src_data_len,
uint timeout_ms
);
}
/// <summary>
/// MCU 串口/端口操作托管封装类
/// 实现 IDisposable 管理底层句柄生命周期
/// </summary>
public class MCUSerialBridge : IDisposable
{
public static uint MaxPortNumber = 16;
private IntPtr nativeHandle = IntPtr.Zero;
/// <summary>判断是否已打开</summary>
public bool IsOpen => nativeHandle != IntPtr.Zero;
/// <summary>构造函数,初始化对象</summary>
// M层MCU适配:创建串口桥包装器并固定原生回调委托。
public MCUSerialBridge()
{
nativeHandle = IntPtr.Zero;
}
/// <summary>析构函数</summary>
// M层MCU适配:对象回收时兜底释放原生串口桥句柄。
~MCUSerialBridge()
{
Dispose(false);
}
/// <summary>显式释放资源</summary>
// M层MCU适配:释放串口桥句柄和非托管资源。
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>内部释放资源方法</summary>
/// <param name="disposing">true 表示手动释放,false 表示析构释放</param>
// M层MCU适配:按托管或终结路径关闭原生句柄。
private void Dispose(bool disposing)
{
if (nativeHandle != IntPtr.Zero)
{
MCUSerialBridgeCoreAPI.msb_close(nativeHandle);
nativeHandle = IntPtr.Zero;
}
}
/// <summary>打开串口</summary>
/// <param name="portName">串口名,如 "COM3"</param>
/// <param name="baud">波特率</param>
/// <returns>错误码</returns>
// M层单车通信:按端口名和波特率连接MCU串口桥。
public MCUSerialBridgeError Open(string portName, uint baud)
{
return MCUSerialBridgeCoreAPI.msb_open(out nativeHandle, portName, baud);
}
/// <summary>关闭串口</summary>
/// <returns>错误码</returns>
// M层单车通信:关闭当前MCU串口桥连接。
public MCUSerialBridgeError Close()
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
MCUSerialBridgeError error = MCUSerialBridgeCoreAPI.msb_close(nativeHandle);
nativeHandle = IntPtr.Zero;
return error;
}
/// <summary>MCU 复位</summary>
/// <returns>错误码</returns>
// M层单车通信:请求MCU复位并等待结果。
public MCUSerialBridgeError Reset(uint timeout = 200)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_reset(nativeHandle, timeout);
}
/// <summary>获取固件版本</summary>
/// <param name="version">输出版本信息</param>
/// <param name="timeout">超时时间(ms</param>
/// <returns>错误码</returns>
// M层MCU诊断:读取串口桥固件版本。
public MCUSerialBridgeError GetVersion(out VersionInfo version, uint timeout = 200)
{
version = new VersionInfo();
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_version(nativeHandle, out version, timeout);
}
/// <summary>获取 MCU 当前状态</summary>
/// <param name="state">输出状态</param>
/// <param name="timeout">超时时间(ms</param>
/// <returns>错误码</returns>
// M层MCU诊断:读取串口桥运行状态。
public MCUSerialBridgeError GetState(out MCUState state, uint timeout = 200)
{
state = new MCUState();
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.mcu_state(nativeHandle, out state, timeout);
}
/// <summary>配置端口</summary>
/// <param name="ports">端口集合</param>
/// <param name="timeout">超时时间(ms</param>
/// <returns>错误码</returns>
// M层MCU适配:批量配置CAN和串口通道参数。
public MCUSerialBridgeError Configure(IEnumerable<PortConfig> ports, uint timeout = 200)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
if (ports == null)
return MCUSerialBridgeError.Win_InvalidParam;
// 转换成数组
PortConfig[] portArray = ports as PortConfig[] ?? ports.ToArray();
int count = portArray.Length;
// 分配连续原生内存
int structSize = 16; // 每个 PortConfig 固定 16 字节
IntPtr nativePorts = Marshal.AllocHGlobal(structSize * count);
try
{
for (int i = 0; i < count; i++)
{
byte[] bytes = portArray[i].ToBytes();
if (bytes.Length != structSize)
return MCUSerialBridgeError.Win_InvalidParam;
Marshal.Copy(bytes, 0, nativePorts + i * structSize, structSize);
}
// 调用底层 API
return MCUSerialBridgeCoreAPI.msb_configure(
nativeHandle,
(uint)count,
nativePorts,
timeout
);
}
catch
{
return MCUSerialBridgeError.Win_InvalidParam;
}
finally
{
Marshal.FreeHGlobal(nativePorts);
}
}
/// <summary>读取输入(4 字节)</summary>
/// <param name="inputs">输出数组</param>
/// <param name="timeout">超时(ms</param>
/// <returns>错误码</returns>
// M层单车IO:读取MCU数字输入状态。
public MCUSerialBridgeError ReadInput(out byte[] inputs, uint timeout = 100)
{
inputs = new byte[4];
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_read_input(nativeHandle, inputs, timeout);
}
/// <summary>写输出(4 字节)</summary>
/// <param name="outputs">数据数组</param>
/// <param name="timeout">超时(ms</param>
/// <returns>错误码</returns>
// M层单车IO:写入继电器、灯光等数字输出状态。
public MCUSerialBridgeError WriteOutput(byte[] outputs, uint timeout = 100)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_write_output(nativeHandle, outputs, timeout);
}
/// <summary>
/// 读取 Serial 端口的一帧数据。
/// Serial 上报的数据按帧入队;本接口每次调用最多读取一帧。
/// 注意,如果不及时调用该接口,数据可能会丢失。
/// </summary>
/// <param name="portIndex">Serial 端口索引</param>
/// <param name="buffer">接收到的数据</param>
/// <param name="timeout">
/// 超时时间(毫秒)
/// - 0:不等待,有数据立即返回,没有数据立即返回 MSB_Error_NoData
/// - >0:若当前无数据,最多等待 timeout,期间有新帧到达则立即返回
/// </param>
/// <remarks>
/// 如果已经注册回调,本函数将始终返回 MSB_Error_NoData
/// </remarks>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功读取一帧
/// - NoData 当前无可读数据(仅在 timeout == 0 或等待超时)
/// - Win_InvalidParam 参数错误
/// </returns>
// M层串口通信:同步读取指定MCU串口的数据。
public MCUSerialBridgeError ReadSerial(byte portIndex, out byte[] buffer, uint timeout)
{
buffer = Array.Empty<byte>();
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
const int MAX_PORT_FRAME = 2048;
byte[] tmp = new byte[MAX_PORT_FRAME];
var err = MCUSerialBridgeCoreAPI.msb_read_port(
nativeHandle,
portIndex,
tmp,
(uint)tmp.Length,
out uint outLen,
timeout
);
if (err != MCUSerialBridgeError.OK)
return err;
buffer = new byte[outLen];
Buffer.BlockCopy(tmp, 0, buffer, 0, (int)outLen);
return MCUSerialBridgeError.OK;
}
/// <summary>
/// 写 Serial 端口数据。
/// 注意:对于单个Serial端口,不支持多线程并行发送,不要在上一条数据没有发送完成之前调用该函数,否则有可能导致数据错误。
/// 注意:超时时间一定要大于波特率和数据长度综合得出的帧时间
/// </summary>
/// <param name="portIndex">Serial 端口索引</param>
/// <param name="data">待发送数据</param>
/// <param name="timeout">超时时间(毫秒)</param>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功发送
/// - 其他错误请查看 MCUSerialBridgeError
/// </returns>
// M层串口通信:向指定MCU串口发送数据。
public MCUSerialBridgeError WriteSerial(byte portIndex, byte[] data, uint timeout)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
if (data == null || data.Length == 0)
return MCUSerialBridgeError.Win_InvalidParam;
return MCUSerialBridgeCoreAPI.msb_write_port(
nativeHandle,
portIndex,
data,
(uint)data.Length,
timeout
);
}
/// <summary>
/// 读取 CAN 端口的一帧数据。
/// CAN 上报的数据按帧入队;本接口每次调用最多读取一帧。
/// 注意,如果不及时调用该接口,数据可能会丢失。
/// </summary>
/// <param name="portIndex">CAN 端口索引</param>
/// <param name="message">输出 CAN 消息对象</param>
/// <param name="timeout">超时时间(毫秒)</param>
/// <remarks>
/// 如果已经注册回调,本函数将始终返回 MSB_Error_NoData
/// </remarks>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功读取一帧
/// - NoData 当前无可读数据(仅在 timeout == 0 或等待超时)
/// - Win_InvalidParam 参数错误
/// - CAN_DataError CAN数据错误
/// - Win_HandleNotFound 句柄无效
/// </returns>
// M层CAN通信:同步读取指定CAN通道的一帧消息。
public MCUSerialBridgeError ReadCAN(byte portIndex, out CANMessage message, uint timeout)
{
message = null;
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
const int MAX_FRAME = 16;
byte[] tmp = new byte[MAX_FRAME];
var err = MCUSerialBridgeCoreAPI.msb_read_port(
nativeHandle,
portIndex,
tmp,
(uint)tmp.Length,
out uint outLen,
timeout
);
if (err != MCUSerialBridgeError.OK)
return err;
try
{
message = CANMessage.FromBytes(tmp, outLen);
}
catch
{
return MCUSerialBridgeError.CAN_DataError;
}
return MCUSerialBridgeError.OK;
}
/// <summary>
/// 写 CAN 端口数据。
/// 注意:CAN 支持多线程发送,最多可同时发送 16 个消息。
/// 但是,多个CAN消息会进入排队队列等待发送,如果消息太多,可能引起超时。
/// </summary>
/// <param name="portIndex">CAN 端口索引</param>
/// <param name="message">待发送 CAN 消息对象</param>
/// <param name="timeout">超时时间(毫秒),默认 500ms</param>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功发送
/// - Win_InvalidParam 参数错误
/// - CAN_DataError CAN 数据错误
/// - Win_HandleNotFound 句柄无效
/// </returns>
// M层CAN通信:向指定CAN通道发送一帧消息。
public MCUSerialBridgeError WriteCAN(byte portIndex, CANMessage message, uint timeout)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
if (message == null)
return MCUSerialBridgeError.Win_InvalidParam;
try
{
byte[] buffer = message.ToBytes();
return MCUSerialBridgeCoreAPI.msb_write_port(
nativeHandle,
portIndex,
buffer,
(uint)buffer.Length,
timeout
);
}
catch
{
return MCUSerialBridgeError.CAN_DataError;
}
}
private readonly Dictionary<
byte,
MCUSerialBridgeCoreAPI.msb_on_port_data_callback_function_t
> _portCallbacks = [];
/// <summary>
/// 注册指定端口(Serial)的回调函数
/// </summary>
/// <param name="portIndex">端口索引</param>
/// <param name="callback">接收数据回调,byte[] 为接收到的原始数据</param>
/// <returns>错误码</returns>
/// <remarks>
/// 注意事项:
/// 1. 回调会在底层 C 层线程中直接调用,请**不要在回调内阻塞**,例如等待 I/O 或 Sleep。
/// 2. 回调内**不能调用 WriteSerial/WriteCAN 等发送函数**,否则可能导致死锁或丢帧。
/// 3. 回调内只能做轻量级操作,例如简单解析、统计或打标记。
/// 4. 若需要复杂处理(例如长时间解析、解码、存储数据库等),请**将数据入队到另一个线程**,再在后台处理。
/// 5. 数据可能随时到来,请保证回调尽快返回,避免影响后续帧接收。
/// 6. 不要把其他类型的端口注册到这个接口,接口不对 portIndex 做类型检查。
/// </remarks>
// M层串口通信:注册指定串口的异步接收回调。
public MCUSerialBridgeError RegisterSerialPortCallback(
byte portIndex,
Action<byte[]> callback
)
{
if (callback == null)
return MCUSerialBridgeError.Win_InvalidParam;
if (portIndex > MaxPortNumber)
return MCUSerialBridgeError.Config_PortNumOver;
// 包装 C# 回调为 P/Invoke 委托
// M层串口回调:复制原生缓存并转交托管回调处理。
void del(IntPtr dst_data, uint dst_data_size, IntPtr user_ctx)
{
byte[] data = new byte[dst_data_size];
Marshal.Copy(dst_data, data, 0, (int)dst_data_size);
callback(data);
}
// 保存引用,防止 GC 回收
_portCallbacks[portIndex] = del;
// 调用 C 层注册
return MCUSerialBridgeCoreAPI.msb_register_port_data_callback(
nativeHandle,
portIndex,
_portCallbacks[portIndex],
IntPtr.Zero
);
}
/// <summary>
/// 注册指定端口(CAN)的回调函数
/// </summary>
/// <param name="portIndex">端口索引</param>
/// <param name="callback">接收数据回调,CANMessage 为接收到的原始数据</param>
/// <returns>错误码</returns>
/// <remarks>
/// 注意事项:
/// 1. 回调会在底层 C 层线程中直接调用,请**不要在回调内阻塞**,例如等待 I/O 或 Sleep。
/// 2. 回调内**不能调用 WriteSerial/WriteCAN 等发送函数**,否则可能导致死锁或丢帧。
/// 3. 回调内只能做轻量级操作,例如简单解析、统计或打标记。
/// 4. 若需要复杂处理(例如长时间解析、解码、存储数据库等),请**将数据入队到另一个线程**,再在后台处理。
/// 5. 数据可能随时到来,请保证回调尽快返回,避免影响后续帧接收。
/// 6. 不要把其他类型的端口注册到这个接口,接口不对 portIndex 做类型检查。
/// </remarks>
// M层CAN通信:注册指定CAN通道的异步接收回调。
public MCUSerialBridgeError RegisterCANPortCallback(
byte portIndex,
Action<CANMessage> callback
)
{
if (callback == null)
return MCUSerialBridgeError.Win_InvalidParam;
if (portIndex > MaxPortNumber)
return MCUSerialBridgeError.Config_PortNumOver;
// 包装 C# 回调为 P/Invoke 委托
// M层CAN回调:还原原生CAN帧并转交托管回调处理。
void del(IntPtr dst_data, uint dst_data_size, IntPtr user_ctx)
{
try
{
byte[] data = new byte[dst_data_size];
Marshal.Copy(dst_data, data, 0, (int)dst_data_size);
CANMessage msg = CANMessage.FromBytes(data, dst_data_size);
callback(msg);
}
catch
{
// 解析失败直接忽略,保证回调不会抛异常阻塞 C 层线程
}
}
// 保存引用,防止 GC 回收
_portCallbacks[portIndex] = del;
// 调用 C 层注册
return MCUSerialBridgeCoreAPI.msb_register_port_data_callback(
nativeHandle,
portIndex,
_portCallbacks[portIndex],
IntPtr.Zero
);
}
}
}