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 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 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 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 GetDevices() { var libDevicesInfo = Hid.Enumerate(); var usbHidDevicesInfo = new List(); 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; } }