You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.9 KiB
188 lines
6.9 KiB
using Common.Logging;
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
using HidApi;
|
|
using MultiTerm.Protocols.Model;
|
|
using MultiTerm.Protocols.Types;
|
|
using System.Diagnostics;
|
|
|
|
namespace MultiTerm.Protocols.UsbHid;
|
|
|
|
public class UsbHidProtocol : CommunicationProtocol
|
|
{
|
|
public override ProtocolType ProtocolType => ProtocolType.UsbHid;
|
|
|
|
public override string InstanceIdentifier { get; protected set; } = string.Empty;
|
|
public override string LongInstanceIdentifier { get; protected set; } = string.Empty;
|
|
|
|
private const uint ReadTimeoutMs = 100; // timeout after which a new read cycle is started
|
|
private const uint MaxBytesPerReport = 100; // maximum number of bytes to send per report, including report ID and pther prefixes
|
|
|
|
private IUsbHidProtocolSettings? usbHidSettings;
|
|
private Device? usbHidDevice = null;
|
|
|
|
|
|
public UsbHidProtocol(ILogger logger, IMessenger messenger) : base(logger, messenger) { }
|
|
|
|
protected override bool InternalConnect(IProtocolSettings settings)
|
|
{
|
|
// check if settings are of correct type
|
|
if (settings is not IUsbHidProtocolSettings usbHidProtocolSettings)
|
|
{
|
|
this.usbHidSettings = null;
|
|
throw new ArgumentException($"Cannot connect due to wrong type of Protocol Settings. " +
|
|
$"Check parameter {nameof(settings)}' of '{nameof(InternalConnect)}()' in {nameof(UsbHidProtocol)}.");
|
|
}
|
|
|
|
// store locally
|
|
this.usbHidSettings = usbHidProtocolSettings;
|
|
|
|
// update Identifier
|
|
this.InstanceIdentifier = $"V:{this.usbHidSettings.VendorId:X4} P:{this.usbHidSettings.ProductId:X4}";
|
|
this.LongInstanceIdentifier = $"Vendor ID:{this.usbHidSettings.VendorId:X4} Product ID:{this.usbHidSettings.ProductId:X4}";
|
|
|
|
// try create usb hid device
|
|
try
|
|
{
|
|
// if serial number is emtpy => connect without serial number
|
|
if (string.IsNullOrEmpty(this.usbHidSettings.SerialNumber))
|
|
{
|
|
this.usbHidDevice = new Device(this.usbHidSettings.VendorId, this.usbHidSettings.ProductId);
|
|
}
|
|
else
|
|
{
|
|
this.usbHidDevice = new Device(this.usbHidSettings.VendorId, this.usbHidSettings.ProductId, this.usbHidSettings.SerialNumber);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.logger.LogException(ex, $"'{nameof(InternalConnect)}()'Opening USB HID Device failed:", nameof(UsbHidProtocol));
|
|
this.usbHidDevice = null;
|
|
}
|
|
|
|
// device not found or had exception
|
|
if (usbHidDevice == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected override void InternalDisconnect()
|
|
{
|
|
this.usbHidDevice?.Dispose();
|
|
this.usbHidDevice = null;
|
|
this.usbHidSettings = null;
|
|
}
|
|
|
|
protected override void InternalRead(CancellationToken ct)
|
|
{
|
|
while (ct.IsCancellationRequested == false)
|
|
{
|
|
// if usb hid device is null => something is wrong => break loop
|
|
if (this.usbHidDevice == null)
|
|
{
|
|
this.OnUnintentionallyDisconnected();
|
|
break;
|
|
}
|
|
|
|
ReadOnlySpan<byte> readData;
|
|
try
|
|
{
|
|
// read with timeout
|
|
readData = this.usbHidDevice.ReadTimeout(200, (int)ReadTimeoutMs);
|
|
}
|
|
catch // on exception => break loop
|
|
{
|
|
this.OnUnintentionallyDisconnected();
|
|
break;
|
|
}
|
|
|
|
// any data received?
|
|
if (readData.Length > 0)
|
|
{
|
|
var dataArray = readData.ToArray();
|
|
var payloadLength = dataArray[1];
|
|
for (int i = 2; i < (payloadLength + 2); i++)
|
|
{
|
|
// report new byte with event
|
|
this.OnReceivedData(new ExtendedByte(dataArray[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override bool InternalSendBytes(byte[] bytes)
|
|
{
|
|
List<byte> bytesToSend = new(bytes);
|
|
int numBytesToTake = (int)MaxBytesPerReport - 2;
|
|
|
|
Debug.WriteLine($"{nameof(InternalSendBytes)}() of {nameof(UsbHidProtocol)} has {bytesToSend.Count} bytes to send.");
|
|
|
|
while (bytesToSend.Count > 0)
|
|
{
|
|
// take from the list the amount of bytes to send. ToList creates a shallow copy.
|
|
//bytesToSend.CopyTo(0, nextBytes, 0, Math.Min(numBytesToTake, bytesToSend.Count));
|
|
var nextBytes = bytesToSend.Take(numBytesToTake).ToList();
|
|
// remove from start of original list either; taken amount of bytes or remaining count of bytes in list
|
|
bytesToSend.RemoveRange(0, Math.Min(numBytesToTake, bytesToSend.Count));
|
|
|
|
Debug.WriteLine($"{nameof(InternalSendBytes)}() of {nameof(UsbHidProtocol)} took {nextBytes.Count} bytes and has {bytesToSend.Count} left on queue.");
|
|
|
|
// check number of bytes to send
|
|
int numBytesToSend = nextBytes.Count;
|
|
if (numBytesToSend > MaxBytesPerReport || numBytesToSend > byte.MaxValue || numBytesToSend <= 0)
|
|
{
|
|
throw new Exception($"'{nameof(InternalSendBytes)}()': Invalid of bytes to send ({nameof(numBytesToSend)})");
|
|
}
|
|
|
|
// create list with sendable bytes
|
|
// Structure: (1Byte)Report Id, (1Byte)Payload Num Bytes, (n Bytes)Payload
|
|
List<byte> sendableBytes = new() { 0x00, (byte)numBytesToSend };
|
|
sendableBytes.AddRange(nextBytes);
|
|
|
|
// if usb hid device is null => something is wrong => quit sending
|
|
if (this.usbHidDevice == null)
|
|
{
|
|
this.OnUnintentionallyDisconnected();
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
this.usbHidDevice.Write(sendableBytes.ToArray());
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
this.logger.LogException(ex, $"Failed to Write Data to USB HID device " +
|
|
$"VID={this.usbHidSettings!.VendorId}, PID={this.usbHidSettings.ProductId}.", nameof(UsbHidProtocol));
|
|
return false;
|
|
}
|
|
|
|
// report sent bytes
|
|
foreach (byte b in sendableBytes)
|
|
{
|
|
this.OnSentData(new ExtendedByte(b));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static IEnumerable<UsbHidDeviceInfo> GetDevices()
|
|
{
|
|
var libDevicesInfo = Hid.Enumerate();
|
|
var usbHidDevicesInfo = new List<UsbHidDeviceInfo>();
|
|
foreach (var deviceInfo in libDevicesInfo)
|
|
{
|
|
usbHidDevicesInfo.Add(new UsbHidDeviceInfo
|
|
{
|
|
VendorId = $"{deviceInfo.VendorId:X4}",
|
|
ProductId = $"{deviceInfo.ProductId:X4}",
|
|
Manufacturer = deviceInfo.ManufacturerString,
|
|
SerialNumber = deviceInfo.SerialNumber
|
|
});
|
|
}
|
|
return usbHidDevicesInfo;
|
|
}
|
|
}
|
|
|