Multiprocotol Terminalprogram (BAT)
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.
MultiTerm/MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs

184 lines
6.6 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;
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)
{
// 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)
{
foreach (var readByte in readData)
{
// report new byte with event
this.OnReceivedData(new ExtendedByte((byte)readByte));
}
}
}
}
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;
}
}