Thursday, November 14, 2013

HID communication for Windows 8.1 Store applications

     I've started thinking about this post a while ago when Windows 8.1 was still in preview (I actually did some testing at that time because I was really excited abouth this new "cool" scenario store applications) and after seeing this Build 2013 session: Apps for HID Devices
     As I did not had the cool missle launcher to play with I have chosen the XBOX360 Wireless controller connected to my PC using an USB adapter similar to this one. One of the reasons why I did this, apart from proof of concept post, is because I have some bluetooth toys that I like to "drive" using the Xbox360 controller (starting with version 8.1 we have full bluetooth support for the Store applications: SPP, OBEX, GATT/BLE ). The Xbox360 controller gives one of the best gaming experience, way better than any touchscreen, keyboard or motion controllers and also enables multiplayer gaming on the same device/screen. 
    In order to be able to connect to the Xbox controller we will need to find the VID, PID and UsagePage specific for our device. As I did not know these values I've used the HClient Sample application to retrieve them. To run the HClient sample you will need Microsoft Visual Studio 2013 and, more important, Windows Driver Kit (WDK) 8.1. HClient gives you detailed informations on all the HID devices connected to your PC and to easily find out which HID device is the one you are looking for just disconnect the device from the computer, look at the items inside the list, reconnect the device and identify the new device. This way I identified that my XBOX360 controller has VID: 0x045E, PID: 0x02A1, UsagePage: 0x1, Usage: 0x5  (there might be multiple hardware versions of the desktop adapter that might have different PIDs):  

Selecting the HID Caps for the controller we will see that the input report is 15 bytes long and we don't have an output or a feature report available for this device. This is one of the reasons why, in this case, it would be better to use XInput instead of HID because it would give you access to the lights and the motors inside the controller.

Now that we have the needed values we can start to build our Windows Store application. The first thing we need to do is to set the necessary Capabilities that enable communication between our application and the Xbox controller (manually edit the Package.appxmanifest inside your project using the View code option). In want to know more about the options you can set read How to specify device capabilities for HID.

So I've have added:

  <m2:DeviceCapability Name="humaninterfacedevice">  
    <m2:Device Id="vidpid:045E 02A1">  
     <m2:Function Type="usage:0001 *"/>  

This way we specify that we only want our application to communicate with devices that have VID 045E and PID 02A1.

Now that we have setup the capabilities we can try to find and connect to and Xbox controller. We will use HidDevice.GetDeviceSelector to generate the query that we will pass to the method DeviceInformation.FindAllAsync that returns an array with all the XBox360 controllers connected to our PC.

   public class Xbox360Controller  
     public const UInt16 Vid = 0x045E;  
     public const UInt16 Pid = 0x02a1;  
     public const UInt16 UsagePage = 0x01;  
     public const UInt16 UsageId = 0x05;  

 string selector = HidDevice.GetDeviceSelector(Xbox360Controller.UsagePage,Xbox360Controller.UsageId,Xbox360Controller.Vid,Xbox360Controller.Pid);   
       var deviceCollection = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(selector);  
       if (deviceCollection.Count == 0)  
         return "No Xbox360 controller found!";  
       for (int i = 0; i < deviceCollection.Count && i < MaxControllers;i++)  
         var _device = await Windows.Devices.HumanInterfaceDevice.HidDevice.FromIdAsync(deviceCollection[i].Id, Windows.Storage.FileAccessMode.ReadWrite);  
         if (_device == null)  
             var deviceAccessStatus = DeviceAccessInformation.CreateFromId(deviceCollection[i].Id).CurrentStatus;  
             switch (deviceAccessStatus)  
               case DeviceAccessStatus.DeniedByUser:  
                 return "User denied the access!";  
               case DeviceAccessStatus.DeniedBySystem:  
                 return "System denied the access!";  
           catch { }  
           return "Failed to connect to the controller!";  
         Controllers.Add(new Controller(_device));  

 We can connect to any of the devices in the returned array using HidDevice.FromIdAsync by just passing the Id of the HID device. The first time we connect to a new device the user will be prompted to grant permission to the application to communicate with that specific device. Keep in mind that the user can later change the access permissions to each device using the Settings -> Permisssions flyout so you should always check why the connection failed (_device==null) and if DeniedByUser ask him to reenable access to the device.

If everything went fine we are now connected to at least one Xbox360 Controller and we can subscribe the InputReportReceived event. The input report contain all the 15 bytes that we need and we can manually parse the bytes or, even better, we can use the defined structures. In our case we have 10 BooleanControls which are our buttons :

       * Buttons Boolean ID's mapped to 0-9 array  
       * A - 5   
       * B - 6  
       * X - 7  
       * Y - 8  
       * LB - 9  
       * RB - 10  
       * Back - 11  
       * Start - 12  
       * LStick - 13  
       * RStick - 14  

and 6 numerical controls :

       _leftStickX = args.Report.GetNumericControl(0x01, 0x30).Value;  
       _leftStickY = args.Report.GetNumericControl(0x01, 0x31).Value;  
       _rightStickX = args.Report.GetNumericControl(0x01, 0x33).Value;     
       _rightStickY = args.Report.GetNumericControl(0x01, 0x34).Value;  
       _DPAD = args.Report.GetNumericControl(0x01, 0x39).Value;  
       _LT = Math.Max(0, args.Report.GetNumericControl(0x01, 0x32).Value - 32768);  
       _RT = Math.Max(0, (-1) * (args.Report.GetNumericControl(0x01, 0x32).Value - 32768));  

The usageId for the GetNumericalControl method are the ones listed under "INPUT VALUE" section in the "Sample HID client" app.

The Left Trigger and Right Trigger are returned as an unique numerical control value equal to LR-RT value. This is the second reason why XInput is a better choice for communicationg with the Xbox controller (using XInput you have distinct values for RT and LT).

Here is a screenshot from the attached sample application.




No comments:

Post a Comment