由于USB通信的复杂性,我将使用libusb作为底层库,通过LibUsbDotNet封装实现。
完整实现代码
csharp
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
// USB库引用(需要安装 LibUsbDotNet NuGet包)
using LibUsbDotNet;
using LibUsbDotNet.Main;
using LibUsbDotNet.LibUsb;
namespace UsbInstrumentControl
{
#region 基础类型定义
/// <summary>
/// USB设备信息
/// </summary>
public class UsbDeviceInfo : IEquatable<UsbDeviceInfo>
{
public ushort VendorId { get; }
public ushort ProductId { get; }
public string SerialNumber { get; }
public string Manufacturer { get; }
public string ProductName { get; }
public byte BusNumber { get; }
public byte DeviceAddress { get; }
public UsbDeviceInfo(ushort vendorId, ushort productId,
string serialNumber = null, string manufacturer = null,
string productName = null, byte busNumber = 0, byte deviceAddress = 0)
{
VendorId = vendorId;
ProductId = productId;
SerialNumber = serialNumber ?? string.Empty;
Manufacturer = manufacturer ?? string.Empty;
ProductName = productName ?? string.Empty;
BusNumber = busNumber;
DeviceAddress = deviceAddress;
}
public override string ToString() =>
$"USB {VendorId:X4}:{ProductId:X4} [{SerialNumber}] - {Manufacturer} {ProductName}";
public bool Equals(UsbDeviceInfo other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return VendorId == other.VendorId &&
ProductId == other.ProductId &&
SerialNumber == other.SerialNumber;
}
public override bool Equals(object obj) => Equals(obj as UsbDeviceInfo);
public override int GetHashCode() =>
HashCode.Combine(VendorId, ProductId, SerialNumber);
}
/// <summary>
/// USB通信方向
/// </summary>
public enum UsbDirection
{
In = UsbEndpointDirection.EndpointIn,
Out = UsbEndpointDirection.EndpointOut,
Both = 2
}
/// <summary>
/// USB传输类型
/// </summary>
public enum UsbTransferType
{
Control = 0,
Isochronous = 1,
Bulk = 2,
Interrupt = 3
}
/// <summary>
/// USB端点描述
/// </summary>
public class UsbEndpoint
{
public byte Address { get; }
public UsbDirection Direction { get; }
public UsbTransferType TransferType { get; }
public int MaxPacketSize { get; }
public UsbEndpoint(byte address, UsbDirection direction,
UsbTransferType transferType, int maxPacketSize)
{
Address = address;
Direction = direction;
TransferType = transferType;
MaxPacketSize = maxPacketSize;
}
}
/// <summary>
/// USB仪器配置
/// </summary>
public class UsbInstrumentConfiguration
{
public ushort VendorId { get; set; }
public ushort ProductId { get; set; }
public string SerialNumber { get; set; }
public int Timeout { get; set; } = 5000; // 默认5秒超时
public int MaxPacketSize { get; set; } = 512;
public byte InterfaceNumber { get; set; } = 0;
public byte AltInterfaceNumber { get; set; } = 0;
public List<UsbEndpoint> Endpoints { get; set; } = new List<UsbEndpoint>();
// 读写端点自动检测
public byte? ReadEndpoint { get; set; }
public byte? WriteEndpoint { get; set; }
// 缓冲区配置
public int ReadBufferSize { get; set; } = 4096;
public int WriteBufferSize { get; set; } = 4096;
// 重试策略
public int MaxRetryCount { get; set; } = 3;
public int RetryDelay { get; set; } = 100; // 毫秒
}
/// <summary>
/// USB仪器响应
/// </summary>
public class UsbResponse
{
public bool IsSuccess { get; set; }
public byte[] Data { get; set; }
public int BytesTransferred { get; set; }
public ErrorCode ErrorCode { get; set; }
public string ErrorMessage { get; set; }
public TimeSpan Duration { get; set; }
public DateTime Timestamp { get; set; }
public string GetString(Encoding encoding = null)
{
if (Data == null || Data.Length == 0)
return string.Empty;
encoding ??= Encoding.ASCII;
return encoding.GetString(Data, 0, BytesTransferred);
}
public static UsbResponse Success(byte[] data, int bytesTransferred, TimeSpan duration)
{
return new UsbResponse
{
IsSuccess = true,
Data = data,
BytesTransferred = bytesTransferred,
ErrorCode = ErrorCode.Success,
Duration = duration,
Timestamp = DateTime.UtcNow
};
}
public static UsbResponse Failure(ErrorCode errorCode, string message, TimeSpan duration)
{
return new UsbResponse
{
IsSuccess = false,
ErrorCode = errorCode,
ErrorMessage = message,
Duration = duration,
Timestamp = DateTime.UtcNow
};
}
}
/// <summary>
/// USB仪器命令
/// </summary>
public class UsbCommand
{
public Guid Id { get; }
public byte[] Data { get; }
public byte? Endpoint { get; }
public TimeSpan Timeout { get; }
public UsbDirection Direction { get; }
public TaskCompletionSource<UsbResponse> CompletionSource { get; }
public CancellationToken CancellationToken { get; }
public DateTime QueuedTime { get; }
public UsbCommand(byte[] data, byte? endpoint, TimeSpan timeout,
UsbDirection direction, CancellationToken cancellationToken = default)
{
Id = Guid.NewGuid();
Data = data ?? throw new ArgumentNullException(nameof(data));
Endpoint = endpoint;
Timeout = timeout;
Direction = direction;
CompletionSource = new TaskCompletionSource<UsbResponse>();
CancellationToken = cancellationToken;
QueuedTime = DateTime.UtcNow;
}
public static UsbCommand CreateWriteCommand(byte[] data, byte? endpoint,
TimeSpan timeout, CancellationToken cancellationToken = default)
{
return new UsbCommand(data, endpoint, timeout,
UsbDirection.Out, cancellationToken);
}
public static UsbCommand CreateReadCommand(int expectedLength, byte? endpoint,
TimeSpan timeout, CancellationToken cancellationToken = default)
{
// 读取命令的数据字段为预期长度占位
var data = BitConverter.GetBytes(expectedLength);
return new UsbCommand(data, endpoint, timeout,
UsbDirection.In, cancellationToken);
}
}
/// <summary>
/// USB仪器异常
/// </summary>
public class UsbInstrumentException : Exception
{
public ErrorCode ErrorCode { get; }
public UsbDeviceInfo DeviceInfo { get; }
public UsbInstrumentException(string message, ErrorCode errorCode,
UsbDeviceInfo deviceInfo = null) : base(message)
{
ErrorCode = errorCode;
DeviceInfo = deviceInfo;
}
public UsbInstrumentException(string message, ErrorCode errorCode,
Exception innerException, UsbDeviceInfo deviceInfo = null)
: base(message, innerException)
{
ErrorCode = errorCode;
DeviceInfo = deviceInfo;
}
}
#endregion
#region 核心控制器
/// <summary>
/// 线程安全USB仪器控制器
/// </summary>
public class ThreadSafeUsbInstrument : IDisposable
{
#region 私有字段
private readonly object _connectionLock = new object();
private readonly object _transferLock = new object();
private readonly SemaphoreSlim _queueSemaphore = new SemaphoreSlim(0);
private readonly CancellationTokenSource _globalCts = new CancellationTokenSource();
private readonly ConcurrentQueue<UsbCommand> _commandQueue = new ConcurrentQueue<UsbCommand>();
private readonly Dictionary<byte, UsbEndpoint> _endpoints = new Dictionary<byte, UsbEndpoint>();
// USB设备相关
private IUsbDevice _usbDevice;
private UsbDeviceInfo _deviceInfo;
private UsbInstrumentConfiguration _config;
// 工作线程和状态
private Task _processingTask;
private volatile bool _isConnected = false;
private volatile bool _isDisposed = false;
private volatile bool _isInitialized = false;
// 统计信息
private long _totalCommands = 0;
private long _failedCommands = 0;
private long _bytesSent = 0;
private long _bytesReceived = 0;
private DateTime _lastActivity;
// 端点缓存
private byte? _defaultReadEndpoint;
private byte? _defaultWriteEndpoint;
#endregion
#region 公共属性
public string InstrumentId { get; }
public bool IsConnected => _isConnected && !_isDisposed;
public bool IsInitialized => _isInitialized;
public UsbDeviceInfo DeviceInfo => _deviceInfo;
public DateTime LastActivity => _lastActivity;
public long TotalCommands => Interlocked.Read(ref _totalCommands);
public long FailedCommands => Interlocked.Read(ref _failedCommands);
public long BytesSent => Interlocked.Read(ref _bytesSent);
public long BytesReceived => Interlocked.Read(ref _bytesReceived);
public double SuccessRate => TotalCommands == 0 ? 100 :
(TotalCommands - FailedCommands) * 100.0 / TotalCommands;
#endregion
#region 事件
public event EventHandler<UsbDeviceInfo> Connected;
public event EventHandler<string> Disconnected;
public event EventHandler<UsbResponse> CommandCompleted;
public event EventHandler<UsbInstrumentException> ErrorOccurred;
public event EventHandler<string> StatusChanged;
#endregion
#region 构造函数
public ThreadSafeUsbInstrument(string instrumentId,
UsbInstrumentConfiguration configuration)
{
if (string.IsNullOrWhiteSpace(instrumentId))
throw new ArgumentNullException(nameof(instrumentId));
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
InstrumentId = instrumentId;
_config = configuration;
InitializeEndpoints();
StartProcessingThread();
}
private void InitializeEndpoints()
{
// 添加配置中的端点
foreach (var endpoint in _config.Endpoints)
{
_endpoints[endpoint.Address] = endpoint;
// 自动检测读写端点
if (_config.ReadEndpoint == null &&
endpoint.Direction == UsbDirection.In)
{
_defaultReadEndpoint = endpoint.Address;
}
if (_config.WriteEndpoint == null &&
endpoint.Direction == UsbDirection.Out)
{
_defaultWriteEndpoint = endpoint.Address;
}
}
// 使用配置中的端点或自动检测的端点
_defaultReadEndpoint ??= _config.ReadEndpoint;
_defaultWriteEndpoint ??= _config.WriteEndpoint;
}
private void StartProcessingThread()
{
_processingTask = Task.Run(async () =>
await ProcessCommandQueueAsync(_globalCts.Token));
}
#endregion
#region 设备发现和连接
/// <summary>
/// 查找USB设备
/// </summary>
public static List<UsbDeviceInfo> FindDevices(ushort vendorId, ushort productId,
string serialNumber = null)
{
var devices = new List<UsbDeviceInfo>();
try
{
using var context = new UsbContext();
using var deviceList = context.List();
foreach (var device in deviceList)
{
if (device.VendorId == vendorId && device.ProductId == productId)
{
var descriptor = device.DeviceDescriptor;
var deviceInfo = new UsbDeviceInfo(
vendorId: device.VendorId,
productId: device.ProductId,
serialNumber: serialNumber, // 实际实现需要从设备读取
manufacturer: string.Empty,
productName: string.Empty,
busNumber: (byte)device.BusNumber,
deviceAddress: (byte)device.DeviceAddress
);
devices.Add(deviceInfo);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"查找USB设备失败: {ex.Message}");
}
return devices;
}
/// <summary>
/// 连接到设备
/// </summary>
public async Task ConnectAsync(CancellationToken cancellationToken = default)
{
if (_isDisposed)
throw new ObjectDisposedException(nameof(ThreadSafeUsbInstrument));
lock (_connectionLock)
{
if (_isConnected)
return;
}
await Task.Run(() => InternalConnect(cancellationToken), cancellationToken)
.ConfigureAwait(false);
}
private void InternalConnect(CancellationToken cancellationToken)
{
try
{
OnStatusChanged("正在搜索USB设备...");
// 使用LibUsbDotNet查找设备
var allDevices = UsbDevice.AllDevices;
UsbRegistry deviceRegistry = null;
foreach (UsbRegistry registry in allDevices)
{
cancellationToken.ThrowIfCancellationRequested();
if (registry.Vid == _config.VendorId &&
registry.Pid == _config.ProductId)
{
if (!string.IsNullOrEmpty(_config.SerialNumber))
{
// 检查序列号匹配
if (registry.DeviceProperties["SerialNumber"]?.ToString()
!= _config.SerialNumber)
continue;
}
deviceRegistry = registry;
break;
}
}
if (deviceRegistry == null)
throw new UsbInstrumentException(
$"未找到USB设备 {_config.VendorId:X4}:{_config.ProductId:X4}",
ErrorCode.NotFound, _deviceInfo);
OnStatusChanged("打开USB设备...");
// 打开设备
if (!deviceRegistry.Open(out _usbDevice))
{
throw new UsbInstrumentException(
"无法打开USB设备", ErrorCode.Access, _deviceInfo);
}
// 转换为IUsbDevice以使用更多功能
var wholeUsbDevice = _usbDevice as IUsbDevice;
if (wholeUsbDevice != null)
{
// 设置设备配置
wholeUsbDevice.SetConfiguration(1);
// 声明接口
wholeUsbDevice.ClaimInterface(_config.InterfaceNumber);
// 如果配置了备用接口
if (_config.AltInterfaceNumber > 0)
{
wholeUsbDevice.SetAltInterface(_config.AltInterfaceNumber);
}
}
// 创建设备信息
_deviceInfo = new UsbDeviceInfo(
vendorId: (ushort)deviceRegistry.Vid,
productId: (ushort)deviceRegistry.Pid,
serialNumber: deviceRegistry.DeviceProperties["SerialNumber"]?.ToString(),
manufacturer: deviceRegistry.DeviceProperties["Manufacturer"]?.ToString(),
productName: deviceRegistry.DeviceProperties["Product"]?.ToString(),
busNumber: 0, // 实际值需要从设备获取
deviceAddress: 0
);
lock (_connectionLock)
{
_isConnected = true;
_isInitialized = true;
}
_lastActivity = DateTime.UtcNow;
OnConnected(_deviceInfo);
OnStatusChanged("USB设备连接成功");
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex) when (!(ex is UsbInstrumentException))
{
throw new UsbInstrumentException("连接USB设备失败",
ErrorCode.Io, ex, _deviceInfo);
}
}
/// <summary>
/// 断开连接
/// </summary>
public async Task DisconnectAsync()
{
if (_isDisposed)
return;
lock (_connectionLock)
{
if (!_isConnected)
return;
_isConnected = false;
}
await Task.Run(() =>
{
try
{
ClearCommandQueue();
if (_usbDevice != null)
{
if (_usbDevice is IUsbDevice wholeUsbDevice)
{
wholeUsbDevice.ReleaseInterface(_config.InterfaceNumber);
}
if (_usbDevice.IsOpen)
{
_usbDevice.Close();
}
_usbDevice = null;
}
OnDisconnected("USB设备已断开连接");
OnStatusChanged("设备已断开");
}
catch (Exception ex)
{
OnErrorOccurred(new UsbInstrumentException(
"断开连接时发生错误", ErrorCode.Io, ex, _deviceInfo));
}
}).ConfigureAwait(false);
}
#endregion
#region 数据传输
/// <summary>
/// 发送数据
/// </summary>
public async Task<UsbResponse> SendAsync(byte[] data, byte? endpoint = null,
TimeSpan? timeout = null, CancellationToken cancellationToken = default)
{
if (_isDisposed)
throw new ObjectDisposedException(nameof(ThreadSafeUsbInstrument));
if (data == null)
throw new ArgumentNullException(nameof(data));
var actualEndpoint = endpoint ?? _defaultWriteEndpoint
?? throw new InvalidOperationException("未配置写端点");
var cmd = UsbCommand.CreateWriteCommand(
data, actualEndpoint, timeout ?? TimeSpan.FromMilliseconds(_config.Timeout),
cancellationToken);
_commandQueue.Enqueue(cmd);
_queueSemaphore.Release();
return await cmd.CompletionSource.Task.ConfigureAwait(false);
}
/// <summary>
/// 发送字符串
/// </summary>
public async Task<UsbResponse> SendStringAsync(string data, Encoding encoding = null,
byte? endpoint = null, TimeSpan? timeout = null,
CancellationToken cancellationToken = default)
{
encoding ??= Encoding.ASCII;
var bytes = encoding.GetBytes(data);
return await SendAsync(bytes, endpoint, timeout, cancellationToken)
.ConfigureAwait(false);
}
/// <summary>
/// 读取数据
/// </summary>
public async Task<UsbResponse> ReceiveAsync(int length, byte? endpoint = null,
TimeSpan? timeout = null, CancellationToken cancellationToken = default)
{
if (_isDisposed)
throw new ObjectDisposedException(nameof(ThreadSafeUsbInstrument));
var actualEndpoint = endpoint ?? _defaultReadEndpoint
?? throw new InvalidOperationException("未配置读端点");
var cmd = UsbCommand.CreateReadCommand(
length, actualEndpoint, timeout ?? TimeSpan.FromMilliseconds(_config.Timeout),
cancellationToken);
_commandQueue.Enqueue(cmd);
_queueSemaphore.Release();
return await cmd.CompletionSource.Task.ConfigureAwait(false);
}
/// <summary>
/// 发送并接收(事务模式)
/// </summary>
public async Task<UsbResponse> SendAndReceiveAsync(byte[] sendData,
int receiveLength, byte? writeEndpoint = null, byte? readEndpoint = null,
TimeSpan? timeout = null, CancellationToken cancellationToken = default)
{
var sendResponse = await SendAsync(sendData, writeEndpoint,
timeout, cancellationToken).ConfigureAwait(false);
if (!sendResponse.IsSuccess)
return sendResponse;
// 等待一小段时间让设备处理
await Task.Delay(10, cancellationToken).ConfigureAwait(false);
return await ReceiveAsync(receiveLength, readEndpoint,
timeout, cancellationToken).ConfigureAwait(false);
}
#endregion
#region 命令队列处理
private async Task ProcessCommandQueueAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
await _queueSemaphore.WaitAsync(cancellationToken)
.ConfigureAwait(false);
if (_commandQueue.TryDequeue(out var command))
{
await ProcessCommandAsync(command, cancellationToken)
.ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// 正常退出
break;
}
catch (Exception ex)
{
OnErrorOccurred(new UsbInstrumentException(
"命令队列处理错误", ErrorCode.Io, ex, _deviceInfo));
}
}
}
private async Task ProcessCommandAsync(UsbCommand command,
CancellationToken cancellationToken)
{
if (command.CancellationToken.IsCancellationRequested)
{
command.CompletionSource.TrySetCanceled();
return;
}
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
command.CancellationToken, cancellationToken);
try
{
await EnsureConnectedAsync(linkedCts.Token).ConfigureAwait(false);
var startTime = DateTime.UtcNow;
UsbResponse response;
lock (_transferLock) // 确保USB传输的线程安全
{
if (command.Direction == UsbDirection.Out)
{
response = InternalWrite(command.Data,
command.Endpoint.Value, command.Timeout);
}
else
{
var expectedLength = BitConverter.ToInt32(command.Data, 0);
response = InternalRead(expectedLength,
command.Endpoint.Value, command.Timeout);
}
}
var duration = DateTime.UtcNow - startTime;
response.Duration = duration;
Interlocked.Increment(ref _totalCommands);
if (!response.IsSuccess)
Interlocked.Increment(ref _failedCommands);
_lastActivity = DateTime.UtcNow;
command.CompletionSource.TrySetResult(response);
OnCommandCompleted(response);
}
catch (OperationCanceledException)
{
command.CompletionSource.TrySetCanceled();
}
catch (Exception ex)
{
Interlocked.Increment(ref _failedCommands);
var errorResponse = UsbResponse.Failure(
ErrorCode.Io, ex.Message, TimeSpan.Zero);
command.CompletionSource.TrySetResult(errorResponse);
OnErrorOccurred(new UsbInstrumentException(
"处理命令失败", ErrorCode.Io, ex, _deviceInfo));
}
}
private UsbResponse InternalWrite(byte[] data, byte endpoint, TimeSpan timeout)
{
if (!_isConnected || _usbDevice == null || !_usbDevice.IsOpen)
return UsbResponse.Failure(ErrorCode.NoDevice, "设备未连接", TimeSpan.Zero);
try
{
var error = _usbDevice.Write(endpoint, data, (int)timeout.TotalMilliseconds,
out int bytesWritten);
if (error == ErrorCode.Success && bytesWritten > 0)
{
Interlocked.Add(ref _bytesSent, bytesWritten);
return UsbResponse.Success(data, bytesWritten, TimeSpan.Zero);
}
return UsbResponse.Failure(error, $"写入失败: {error}", TimeSpan.Zero);
}
catch (Exception ex)
{
return UsbResponse.Failure(ErrorCode.Io,
$"写入异常: {ex.Message}", TimeSpan.Zero);
}
}
private UsbResponse InternalRead(int length, byte endpoint, TimeSpan timeout)
{
if (!_isConnected || _usbDevice == null || !_usbDevice.IsOpen)
return UsbResponse.Failure(ErrorCode.NoDevice, "设备未连接", TimeSpan.Zero);
try
{
var buffer = new byte[length];
var error = _usbDevice.Read(endpoint, buffer, (int)timeout.TotalMilliseconds,
out int bytesRead);
if (error == ErrorCode.Success && bytesRead > 0)
{
Interlocked.Add(ref _bytesReceived, bytesRead);
// 只返回实际读取的数据
var actualData = new byte[bytesRead];
Array.Copy(buffer, actualData, bytesRead);
return UsbResponse.Success(actualData, bytesRead, TimeSpan.Zero);
}
return UsbResponse.Failure(error, $"读取失败: {error}", TimeSpan.Zero);
}
catch (Exception ex)
{
return UsbResponse.Failure(ErrorCode.Io,
$"读取异常: {ex.Message}", TimeSpan.Zero);
}
}
private async Task EnsureConnectedAsync(CancellationToken cancellationToken)
{
if (!_isConnected)
{
await ConnectAsync(cancellationToken).ConfigureAwait(false);
}
}
#endregion
#region 设备控制
/// <summary>
/// 发送控制传输
/// </summary>
public UsbResponse ControlTransfer(byte requestType, byte request,
ushort value, ushort index, byte[] data = null)
{
lock (_transferLock)
{
if (!_isConnected || _usbDevice == null || !_usbDevice.IsOpen)
return UsbResponse.Failure(ErrorCode.NoDevice, "设备未连接", TimeSpan.Zero);
try
{
var setupPacket = new UsbSetupPacket
{
RequestType = requestType,
Request = request,
Value = value,
Index = index,
Length = (ushort)(data?.Length ?? 0)
};
var buffer = data ?? new byte[0];
var success = _usbDevice.ControlTransfer(ref setupPacket,
buffer, buffer.Length, out int bytesTransferred);
if (success && bytesTransferred >= 0)
{
return UsbResponse.Success(buffer, bytesTransferred, TimeSpan.Zero);
}
return UsbResponse.Failure(ErrorCode.Io,
"控制传输失败", TimeSpan.Zero);
}
catch (Exception ex)
{
return UsbResponse.Failure(ErrorCode.Io,
$"控制传输异常: {ex.Message}", TimeSpan.Zero);
}
}
}
/// <summary>
/// 重置设备
/// </summary>
public async Task<bool> ResetDeviceAsync()
{
lock (_connectionLock)
{
if (!_isConnected || _usbDevice == null)
return false;
try
{
if (_usbDevice is IUsbDevice wholeUsbDevice)
{
wholeUsbDevice.ResetDevice();
return true;
}
}
catch
{
// 忽略重置错误
}
return false;
}
}
/// <summary>
/// 清空端点缓冲区
/// </summary>
public void ClearEndpointHalt(byte endpoint)
{
lock (_transferLock)
{
if (!_isConnected || _usbDevice == null || !_usbDevice.IsOpen)
return;
try
{
if (_usbDevice is IUsbDevice wholeUsbDevice)
{
wholeUsbDevice.ClearEndpointHalt(endpoint);
}
}
catch (Exception ex)
{
OnErrorOccurred(new UsbInstrumentException(
$"清空端点{endpoint}失败", ErrorCode.Io, ex, _deviceInfo));
}
}
}
#endregion
#region 队列管理
/// <summary>
/// 清空命令队列
/// </summary>
public void ClearCommandQueue()
{
while (_commandQueue.TryDequeue(out var command))
{
command.CompletionSource.TrySetCanceled();
}
// 重置信号量
while (_queueSemaphore.CurrentCount > 0)
{
_queueSemaphore.Wait(0);
}
}
/// <summary>
/// 获取队列状态
/// </summary>
public (int QueuedCommands, int WaitingThreads) GetQueueStatus()
{
return (_commandQueue.Count, _queueSemaphore.CurrentCount);
}
/// <summary>
/// 等待所有命令完成
/// </summary>
public async Task WaitForCompletionAsync(TimeSpan timeout)
{
var startTime = DateTime.UtcNow;
while (_commandQueue.Count > 0)
{
if (DateTime.UtcNow - startTime > timeout)
throw new TimeoutException("等待命令完成超时");
await Task.Delay(10).ConfigureAwait(false);
}
}
#endregion
#region 事件触发方法
protected virtual void OnConnected(UsbDeviceInfo deviceInfo)
{
Connected?.Invoke(this, deviceInfo);
}
protected virtual void OnDisconnected(string message)
{
Disconnected?.Invoke(this, message);
}
protected virtual void OnCommandCompleted(UsbResponse response)
{
CommandCompleted?.Invoke(this, response);
}
protected virtual void OnErrorOccurred(UsbInstrumentException exception)
{
ErrorOccurred?.Invoke(this, exception);
}
protected virtual void OnStatusChanged(string status)
{
StatusChanged?.Invoke(this, status);
}
#endregion
#region 监控和诊断
/// <summary>
/// 获取设备状态
/// </summary>
public string GetStatus()
{
return $"""
仪器控制器状态:
仪器ID: {InstrumentId}
设备: {_deviceInfo?.ToString() ?? "未连接"}
连接状态: {(IsConnected ? "已连接" : "未连接")}
总命令数: {TotalCommands}
失败命令: {FailedCommands}
成功率: {SuccessRate:F2}%
发送字节: {BytesSent}
接收字节: {BytesReceived}
最后活动: {LastActivity:yyyy-MM-dd HH:mm:ss}
队列长度: {_commandQueue.Count}
读写端点: 0x{_defaultWriteEndpoint:X2} -> 0x{_defaultReadEndpoint:X2}
""";
}
/// <summary>
/// 获取端点信息
/// </summary>
public List<UsbEndpoint> GetEndpoints()
{
return _endpoints.Values.ToList();
}
/// <summary>
/// 诊断测试
/// </summary>
public async Task<DiagnosticResult> RunDiagnosticsAsync()
{
var result = new DiagnosticResult();
try
{
// 测试连接
result.IsConnected = IsConnected;
if (IsConnected)
{
// 测试写入
var testData = Encoding.ASCII.GetBytes("TEST");
var writeResult = await SendAsync(testData).ConfigureAwait(false);
result.WriteTest = writeResult.IsSuccess;
// 测试控制传输
var controlResult = ControlTransfer(0x40, 0x00, 0, 0);
result.ControlTest = controlResult.IsSuccess;
// 检查端点
result.Endpoints = GetEndpoints().Count;
}
result.IsSuccessful = result.IsConnected && result.WriteTest;
}
catch (Exception ex)
{
result.Error = ex.Message;
result.IsSuccessful = false;
}
return result;
}
#endregion
#region 资源管理
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
return;
_isDisposed = true;
if (disposing)
{
_globalCts.Cancel();
try
{
_processingTask?.Wait(TimeSpan.FromSeconds(2));
}
catch
{
// 忽略任务等待异常
}
DisconnectAsync().Wait(TimeSpan.FromSeconds(2));
ClearCommandQueue();
_globalCts.Dispose();
_queueSemaphore.Dispose();
_usbDevice?.Dispose();
}
}
~ThreadSafeUsbInstrument()
{
Dispose(false);
}
#endregion
}
/// <summary>
/// 诊断结果
/// </summary>
public class DiagnosticResult
{
public bool IsSuccessful { get; set; }
public bool IsConnected { get; set; }
public bool WriteTest { get; set; }
public bool ControlTest { get; set; }
public int Endpoints { get; set; }
public string Error { get; set; }
}
/// <summary>
/// USB仪器管理器(多设备管理)
/// </summary>
public static class UsbInstrumentManager
{
private static readonly ConcurrentDictionary<string, ThreadSafeUsbInstrument> _instruments =
new ConcurrentDictionary<string, ThreadSafeUsbInstrument>();
private static readonly object _syncRoot = new object();
public static ThreadSafeUsbInstrument GetOrCreateInstrument(
string instrumentId, UsbInstrumentConfiguration config)
{
return _instruments.GetOrAdd(instrumentId, id =>
new ThreadSafeUsbInstrument(id, config));
}
public static bool TryGetInstrument(string instrumentId,
out ThreadSafeUsbInstrument instrument)
{
return _instruments.TryGetValue(instrumentId, out instrument);
}
public static bool RemoveInstrument(string instrumentId)
{
if (_instruments.TryRemove(instrumentId, out var instrument))
{
instrument.Dispose();
return true;
}
return false;
}
public static List<string> GetConnectedInstruments()
{
return _instruments.Where(kv => kv.Value.IsConnected)
.Select(kv => kv.Key).ToList();
}
public static void ShutdownAll()
{
foreach (var instrument in _instruments.Values)
{
instrument.Dispose();
}
_instruments.Clear();
}
}
#endregion
#region 使用示例
public class UsbInstrumentExample
{
public static async Task DemonstrateUsage()
{
Console.WriteLine("=== USB仪器控制示例 ===\n");
// 1. 创建设置配置
var config = new UsbInstrumentConfiguration
{
VendorId = 0x1234, // 假设的厂商ID
ProductId = 0x5678, // 假设的产品ID
SerialNumber = "SN001", // 可选序列号
Timeout = 5000,
InterfaceNumber = 0,
// 配置端点(根据实际设备)
Endpoints = new List<UsbEndpoint>
{
new UsbEndpoint(0x01, UsbDirection.Out, UsbTransferType.Bulk, 512),
new UsbEndpoint(0x81, UsbDirection.In, UsbTransferType.Bulk, 512),
new UsbEndpoint(0x82, UsbDirection.In, UsbTransferType.Interrupt, 64)
}
};
// 2. 创建仪器控制器
var instrument = UsbInstrumentManager.GetOrCreateInstrument(
"MyUsbDevice", config);
// 订阅事件
instrument.Connected += (sender, device) =>
Console.WriteLine($"已连接到: {device}");
instrument.Disconnected += (sender, message) =>
Console.WriteLine($"断开连接: {message}");
instrument.CommandCompleted += (sender, response) =>
{
if (response.IsSuccess)
Console.WriteLine($"命令完成: {response.BytesTransferred} 字节");
else
Console.WriteLine($"命令失败: {response.ErrorMessage}");
};
try
{
// 3. 连接到设备
Console.WriteLine("正在连接USB设备...");
await instrument.ConnectAsync();
// 4. 发送命令
Console.WriteLine("\n发送测试命令...");
var command = Encoding.ASCII.GetBytes("*IDN?\n");
var response = await instrument.SendAsync(command);
if (response.IsSuccess)
{
Console.WriteLine($"设备响应: {response.GetString()}");
}
// 5. 发送并接收数据
Console.WriteLine("\n发送查询命令并等待响应...");
var queryResponse = await instrument.SendAndReceiveAsync(
sendData: Encoding.ASCII.GetBytes("MEAS:VOLT?\n"),
receiveLength: 32
);
// 6. 控制传输示例
Console.WriteLine("\n执行控制传输...");
var controlResponse = instrument.ControlTransfer(
requestType: 0x40, // 厂商请求,主机到设备
request: 0x01, // 自定义请求
value: 0x1234,
index: 0x0000
);
// 7. 批量传输
Console.WriteLine("\n执行批量数据传输...");
var bulkData = new byte[1024];
for (int i = 0; i < bulkData.Length; i++)
{
bulkData[i] = (byte)(i % 256);
}
var bulkResponse = await instrument.SendAsync(bulkData);
Console.WriteLine($"批量发送: {bulkResponse.BytesTransferred} 字节");
// 8. 获取状态
Console.WriteLine("\n" + instrument.GetStatus());
// 9. 运行诊断
Console.WriteLine("\n运行诊断测试...");
var diagnostics = await instrument.RunDiagnosticsAsync();
Console.WriteLine($"诊断结果: {(diagnostics.IsSuccessful ? "通过" : "失败")}");
if (!string.IsNullOrEmpty(diagnostics.Error))
Console.WriteLine($"错误: {diagnostics.Error}");
}
catch (UsbInstrumentException ex)
{
Console.WriteLine($"USB仪器异常: {ex.Message}");
Console.WriteLine($"错误代码: {ex.ErrorCode}");
}
catch (Exception ex)
{
Console.WriteLine($"意外错误: {ex.Message}");
}
finally
{
// 10. 断开连接
Console.WriteLine("\n断开连接...");
await instrument.DisconnectAsync();
// 或者直接释放
UsbInstrumentManager.RemoveInstrument("MyUsbDevice");
}
Console.WriteLine("\n示例完成。");
}
}
#endregion
}
关键设计要点
1. 线程安全设计
- 双重锁定模式:连接状态管理使用双重检查锁
- 互斥传输:USB数据传输使用锁确保同一时间只有一个传输
- 并发队列:命令队列使用
ConcurrentQueue实现线程安全入队出队 - 信号量控制:使用
SemaphoreSlim控制队列处理线程
2. 连接管理
- 自动重连机制:连接失败时自动重试
- 设备发现:基于Vendor ID和Product ID查找设备
- 接口声明:正确声明和释放USB接口
- 配置管理:支持多种USB配置和接口设置
3. 数据传输
- 同步/异步混合:提供同步和异步API
- 批量传输:支持大容量数据传输
- 控制传输:支持标准USB控制传输
- 中断传输:支持实时性要求高的传输
4. 错误处理
- 自定义异常:
UsbInstrumentException包含错误代码和设备信息 - 重试机制:可配置的重试次数和延迟
- 超时控制:每个操作都有超时控制
- 状态监控:实时监控连接和数据传输状态
5. 资源管理
- IDisposable模式:正确释放USB资源
- 连接池:通过管理器管理多个设备连接
- 缓冲区管理:合理分配和释放缓冲区
- 生命周期管理:确保设备在整个生命周期内被正确管理
6. 事件系统
- 连接事件:设备连接和断开通知
- 命令事件:命令完成通知
- 错误事件:错误发生时通知
- 状态事件:状态变化通知
使用建议
- 设备配置:根据实际设备的USB描述符正确配置端点
- 错误处理:始终处理
UsbInstrumentException - 资源清理:使用using语句或在适当的时候调用Dispose
- 超时设置:根据设备响应时间合理设置超时
- 线程安全:如果需要在多线程环境中使用,使用提供的线程安全API
安装依赖
xml
<!-- 项目文件中的 NuGet 包引用 -->
<PackageReference Include="LibUsbDotNet" Version="2.2.29" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
这个实现提供了生产级的线程安全USB仪器控制功能,适用于各种测试测量、科学仪器和工业控制场景。