Initial commit: BMS CAN monitor
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
namespace BmsMonitor.App.Can;
|
||||
|
||||
public sealed class ControlCanReceiver : ICanReceiver
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
private Task? _receiveTask;
|
||||
private uint _deviceIndex;
|
||||
private uint _channelIndex;
|
||||
|
||||
public bool IsOpen { get; private set; }
|
||||
public event EventHandler<CanFrame>? FrameReceived;
|
||||
public event EventHandler<string>? ReceiveError;
|
||||
|
||||
public void Open(uint deviceIndex, uint channelIndex, CanBaudRate baudRate = CanBaudRate.Kbps250)
|
||||
{
|
||||
if (IsOpen) return;
|
||||
|
||||
_deviceIndex = deviceIndex;
|
||||
_channelIndex = channelIndex;
|
||||
if (ControlCanNative.VCI_OpenDevice(ControlCanNative.VciUsbCan2, deviceIndex, 0) != ControlCanNative.StatusOk)
|
||||
throw new InvalidOperationException("打开 USBCAN-II 失败,请检查驱动、设备索引及 ControlCAN.dll。");
|
||||
|
||||
var (timing0, timing1) = baudRate.ToTiming();
|
||||
var config = new ControlCanNative.VciInitConfig
|
||||
{
|
||||
AccCode = 0,
|
||||
AccMask = 0xFFFFFFFF,
|
||||
Filter = 1,
|
||||
Timing0 = timing0,
|
||||
Timing1 = timing1,
|
||||
Mode = 1 // 硬件只听模式:仅接收,不发送 ACK
|
||||
};
|
||||
|
||||
var initResult = ControlCanNative.VCI_InitCAN(ControlCanNative.VciUsbCan2, deviceIndex, channelIndex, ref config);
|
||||
if (initResult != ControlCanNative.StatusOk)
|
||||
{
|
||||
ControlCanNative.VCI_CloseDevice(ControlCanNative.VciUsbCan2, deviceIndex);
|
||||
throw new InvalidOperationException($"VCI_InitCAN 失败(返回 {initResult},通道 CAN{channelIndex + 1},{baudRate.ToDisplayText()},只听模式)。");
|
||||
}
|
||||
|
||||
var startResult = ControlCanNative.VCI_StartCAN(ControlCanNative.VciUsbCan2, deviceIndex, channelIndex);
|
||||
if (startResult != ControlCanNative.StatusOk)
|
||||
{
|
||||
ControlCanNative.VCI_ResetCAN(ControlCanNative.VciUsbCan2, deviceIndex, channelIndex);
|
||||
ControlCanNative.VCI_CloseDevice(ControlCanNative.VciUsbCan2, deviceIndex);
|
||||
throw new InvalidOperationException($"VCI_StartCAN 失败(返回 {startResult},通道 CAN{channelIndex + 1})。");
|
||||
}
|
||||
|
||||
IsOpen = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
_receiveTask = Task.Run(() => ReceiveLoop(_cts.Token));
|
||||
}
|
||||
|
||||
private void ReceiveLoop(CancellationToken token)
|
||||
{
|
||||
var buffer = Enumerable.Range(0, 100).Select(_ => new ControlCanNative.VciCanObj
|
||||
{
|
||||
Data = new byte[8], Reserved = new byte[3]
|
||||
}).ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
var count = ControlCanNative.VCI_Receive(ControlCanNative.VciUsbCan2, _deviceIndex, _channelIndex,
|
||||
buffer, (uint)buffer.Length, 50);
|
||||
if (count == 0 || count == uint.MaxValue) continue;
|
||||
|
||||
for (var i = 0; i < Math.Min(count, (uint)buffer.Length); i++)
|
||||
{
|
||||
var item = buffer[i];
|
||||
if (item.RemoteFlag != 0) continue;
|
||||
var length = Math.Min(item.DataLen, (byte)8);
|
||||
FrameReceived?.Invoke(this, new CanFrame(item.Id, item.ExternFlag != 0,
|
||||
item.Data.Take(length).ToArray(), DateTime.Now));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!token.IsCancellationRequested)
|
||||
{
|
||||
ReceiveError?.Invoke(this, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!IsOpen) return;
|
||||
_cts?.Cancel();
|
||||
_receiveTask?.Wait(300);
|
||||
ControlCanNative.VCI_ResetCAN(ControlCanNative.VciUsbCan2, _deviceIndex, _channelIndex);
|
||||
ControlCanNative.VCI_CloseDevice(ControlCanNative.VciUsbCan2, _deviceIndex);
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public void Dispose() => Close();
|
||||
}
|
||||
Reference in New Issue
Block a user