0

I am trying to access Hid (USB) devices connected to my computer on UWP. I have no problem enumerating the devices and talking to them through Windows API calls in .NET Core. In UWP, I can enumerate the devices, but when I call HidD_GetPreparsedData with the same device (https://msdn.microsoft.com/en-us/library/windows/hardware/ff539679(v=vs.85).aspx), it returns false.

I'm thinking that because UWP has its own HID library, I'm supposed to use that, but I'm hoping to reuse my existing code. Any ideas why this call might be failing?

I did think that this was a permissions problem, so I downloaded the UWP HID Sample from here: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/CustomHidDeviceAccess. I then modified the package manifest to use the device's VID and PID

<Capabilities>
  <DeviceCapability Name="humaninterfacedevice">
    <Device Id="vidpid:xxxx xxxx">
      <Function Type="usage:0005 *" />
    </Device>
  </DeviceCapability>
</Capabilities>

The device appears in UWP using the standard HID library in UWP. I can enumerate ALL devices (not just the ones I have specified access to), and my device shows up in the list of devices in the sample app.

Yet, when I compile and run my app, the HidD_GetPreparsedData returns false. So, I'm left wondering if I'm ever going to get this API call working. I.e. should I abandon the enterprise and just use the standard UWP HID library?

Christian Findlay
  • 6,770
  • 5
  • 51
  • 103
  • It seems that this [HidCollection Class](https://learn.microsoft.com/en-us/uwp/api/windows.devices.humaninterfacedevice.hidcollection) is similar and related. Please ask with MSDN or TechNet Forum. – kunif Jan 05 '18 at 11:48
  • I found such a related [article](https://qiita.com/kndysfm/items/ea06e088edc1893eec6d), but it is in Japanese. Please read it using Google's translation function etc. – kunif Jan 05 '18 at 12:06
  • Does you application specify the required [app capabilities](https://learn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations) in its manifest? – IInspectable Jan 05 '18 at 16:25
  • @IInspectable , this is a good question and I'm wondering that myself. There is supposed to be a USB app capability, but I cannot find it in the manifest. Perhaps it was removed? – Christian Findlay Jan 05 '18 at 21:00
  • @IInspectable , this article says I need to include a DeviceCapability, but adding the xml to the manifest that they recommend just causes a compile error. – Christian Findlay Jan 05 '18 at 21:03
  • '"a compile error"* isn't very helpful. Please provide both the error, as well as the manifest. – IInspectable Jan 05 '18 at 21:49
  • I included the error in the original post. But, I then found out that it does compile when I move the element inside the other element . It still doesn't work but if I find the right combination of vendor and product id I think it will work. Will post results if I get it right... – Christian Findlay Jan 05 '18 at 22:04
  • @IInspectable , I am still not sure if it does have the right DeviceCapability, but who knows how or what I am supposed to do? – Christian Findlay Jan 05 '18 at 23:33
  • The parameters if this problem keep changing... please keep an eye on the opening post. – Christian Findlay Jan 06 '18 at 00:40
  • Why are all hardware related questions in UWP completely ignored? How is it possible that no one in the UWP space is doing hardware integrations? – Chris Lees May 15 '18 at 13:32
  • I have a version of my library working with UWP. I have an interface that sits across .NET Core, UWP, and Android. I never got the Win32 version working on UWP. I am trying to dust this library off and open source it. – Christian Findlay May 16 '18 at 23:08

1 Answers1

1

The answer is no. The UWP platform does not allow Windows Hid, or USB API calls. The API call does not appear here. That's even though the API doesn't explicitly fail. This begs the question of why UWP allows you to access some API calls but doesn't explicitly throw an exception when you call them.

Anyway, it is not necessary because there is a full API for USB, and Hid access. You need to define your devices in the package manifest like this:

Hid:

<DeviceCapability Name="humaninterfacedevice">

  <Device Id="vidpid:534C 0001">
    <Function Type="usage:0005 *" />
    <Function Type="usage:FF00 0001" />
    <Function Type="usage:ff00 *" />
  </Device>

  <Device Id="vidpid:1209 53C0">
    <Function Type="usage:0005 *" />
    <Function Type="usage:FF00 0001" />
    <Function Type="usage:ff00 *" />
  </Device>

  <Device Id="vidpid:1209 53C1">
    <Function Type="usage:0005 *" />
    <Function Type="usage:FF00 0001" />
    <Function Type="usage:ff00 *" />
  </Device>

</DeviceCapability>

USB:

  <!--Trezor Firmware 1.7.x -->
  <Device Id="vidpid:1209 53C1">
    <Function Type="classId:ff * *" />
  </Device>

</DeviceCapability>

Here are two classes for communicating with USB, and Hid devices on UWP from Device.Net.

public class UWPHidDevice : UWPDeviceBase<HidDevice>
{
    #region Public Properties
    public bool DataHasExtraByte { get; set; } = true;
    #endregion

    #region Public Override Properties
    /// <summary>
    /// TODO: These vales are completely wrong and not being used anyway...
    /// </summary>
    public override ushort WriteBufferSize => 64;
    /// <summary>
    /// TODO: These vales are completely wrong and not being used anyway...
    /// </summary>
    public override ushort ReadBufferSize => 64;
    #endregion

    #region Event Handlers
    private void _HidDevice_InputReportReceived(HidDevice sender, HidInputReportReceivedEventArgs args)
    {
        HandleDataReceived(InputReportToBytes(args));
    }
    #endregion

    #region Constructors
    public UWPHidDevice()
    {
    }

    public UWPHidDevice(string deviceId) : base(deviceId)
    {
    }
    #endregion

    #region Private Methods
    private byte[] InputReportToBytes(HidInputReportReceivedEventArgs args)
    {
        byte[] bytes;
        using (var stream = args.Report.Data.AsStream())
        {
            bytes = new byte[args.Report.Data.Length];
            stream.Read(bytes, 0, (int)args.Report.Data.Length);
        }

        if (DataHasExtraByte)
        {
            bytes = RemoveFirstByte(bytes);
        }

        return bytes;
    }

    public override async Task InitializeAsync()
    {
        //TODO: Put a lock here to stop reentrancy of multiple calls

        //TODO: Dispose but this seems to cause initialization to never occur
        //Dispose();

        Logger.Log("Initializing Hid device", null, nameof(UWPHidDevice));

        await GetDevice(DeviceId);

        if (_ConnectedDevice != null)
        {
            _ConnectedDevice.InputReportReceived += _HidDevice_InputReportReceived;
            RaiseConnected();
        }
        else
        {
            throw new Exception($"The device {DeviceId} failed to initialize");
        }
    }

    protected override IAsyncOperation<HidDevice> FromIdAsync(string id)
    {
        return HidDevice.FromIdAsync(id, FileAccessMode.ReadWrite);
    }
    #endregion

    #region Public Methods

    public override async Task WriteAsync(byte[] data)
    {
        byte[] bytes;
        if (DataHasExtraByte)
        {
            bytes = new byte[data.Length + 1];
            Array.Copy(data, 0, bytes, 1, data.Length);
            bytes[0] = 0;
        }
        else
        {
            bytes = data;
        }

        var buffer = bytes.AsBuffer();
        var outReport = _ConnectedDevice.CreateOutputReport();
        outReport.Data = buffer;

        try
        {
            var operation = _ConnectedDevice.SendOutputReportAsync(outReport);
            await operation.AsTask();
            Tracer?.Trace(false, bytes);
        }
        catch (ArgumentException ex)
        {
            //TODO: Check the string is nasty. Validation on the size of the array being sent should be done earlier anyway
            if (ex.Message == "Value does not fall within the expected range.")
            {
                throw new Exception("It seems that the data being sent to the device does not match the accepted size. Have you checked DataHasExtraByte?", ex);
            }
            throw;
        }
    }
    #endregion
}

public class UWPUsbDevice : UWPDeviceBase<UsbDevice>
{
    #region Fields
    /// <summary>
    /// TODO: It should be possible to select a different configuration/interface
    /// </summary>
    private UsbInterface _DefaultConfigurationInterface;
    private UsbInterruptOutPipe _DefaultOutPipe;
    private UsbInterruptInPipe _DefaultInPipe;
    #endregion

    #region Public Override Properties
    public override ushort WriteBufferSize => (ushort)_DefaultOutPipe.EndpointDescriptor.MaxPacketSize;
    public override ushort ReadBufferSize => (ushort)_DefaultInPipe.EndpointDescriptor.MaxPacketSize;
    #endregion

    #region Constructors
    public UWPUsbDevice() : base()
    {
    }

    public UWPUsbDevice(string deviceId) : base(deviceId)
    {
    }
    #endregion

    #region Private Methods
    public override async Task InitializeAsync()
    {
        await GetDevice(DeviceId);

        if (_ConnectedDevice != null)
        {
            var usbInterface = _ConnectedDevice.Configuration.UsbInterfaces.FirstOrDefault();

            if (usbInterface == null)
            {
                _ConnectedDevice.Dispose();
                throw new Exception("There was no Usb Interface found for the device.");
            }

            var interruptPipe = usbInterface.InterruptInPipes.FirstOrDefault();

            if (interruptPipe == null)
            {
                throw new Exception("There was no interrupt pipe found on the interface");
            }

            interruptPipe.DataReceived += InterruptPipe_DataReceived;

            //TODO: Fill in the DeviceDefinition...

            // TODO: It should be possible to select a different configurations, interface, and pipes

            _DefaultConfigurationInterface = _ConnectedDevice.Configuration.UsbInterfaces.FirstOrDefault();

            //TODO: Clean up this messaging and move down to a base class across platforms
            if (_DefaultConfigurationInterface == null) throw new Exception("Could not get the default interface configuration for the USB device");

            _DefaultOutPipe = _DefaultConfigurationInterface.InterruptOutPipes.FirstOrDefault();

            if (_DefaultOutPipe == null) throw new Exception("Could not get the default out pipe for the default USB interface");

            _DefaultInPipe = _DefaultConfigurationInterface.InterruptInPipes.FirstOrDefault();

            if (_DefaultOutPipe == null) throw new Exception("Could not get the default in pipe for the default USB interface");


            RaiseConnected();
        }
        else
        {
            throw new Exception($"Could not connect to device with Device Id {DeviceId}. Check that the package manifest has been configured to allow this device.");
        }
    }

    protected override IAsyncOperation<UsbDevice> FromIdAsync(string id)
    {
        return UsbDevice.FromIdAsync(id);
    }

    #endregion

    #region Event Handlers
    private void InterruptPipe_DataReceived(UsbInterruptInPipe sender, UsbInterruptInEventArgs args)
    {
        HandleDataReceived(args.InterruptData.ToArray());
    }
    #endregion

    #region Public Methods
    public override async Task WriteAsync(byte[] bytes)
    {
        if (_DefaultOutPipe == null) throw new Exception("The device has not been initialized.");

        if (bytes.Length > WriteBufferSize) throw new Exception("The buffer size is too large");
        await _DefaultOutPipe.OutputStream.WriteAsync(bytes.AsBuffer());

        Tracer?.Trace(false, bytes);
    }
    #endregion
}
Christian Findlay
  • 6,770
  • 5
  • 51
  • 103