|
|
|
@ -0,0 +1,168 @@ |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
ReadOnlySpan<byte> readData; |
|
|
|
|
|
|
|
if (this.usbHidDevice != null) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// read with timeout |
|
|
|
|
|
|
|
readData = this.usbHidDevice.ReadTimeout(200, (int)ReadTimeoutMs); |
|
|
|
|
|
|
|
// any data received? |
|
|
|
|
|
|
|
if(readData.Length > 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
foreach (var readByte in readData) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// report new byte with event |
|
|
|
|
|
|
|
this.OnReceivedData(new ExtendedByte((byte)readByte)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// if usb hid device is null => something is wrong => break loop |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.OnUnintentionallyDisconnected(); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.usbHidDevice.Write(bytes); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// report sent bytes |
|
|
|
|
|
|
|
foreach (byte b in bytes) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |