Usb Client RawDevice write timeout

Currently I ran into an issue revolving around the USBClient RawDevice and a write timeout.

Here is the relevant code:


namespace HotasExpansion
{
    public class UsbClient
    {

        public static ConfiguredDevice ConfigureUsbDevice()
        {
           
            ushort myVID = 0x1234;
            ushort myPID = 0x0012;
            ushort myDeviceVersion = 0x100;
            ushort myDeviceMaxPower = 500; // in milli amps
            string companyName = "Something Something";
            string productName = "HOTAS Throttle";
            string myDeviceSerialNumber = "0";

            
            RawDevice device = new RawDevice(myVID, myPID, myDeviceVersion, myDeviceMaxPower, companyName, productName, myDeviceSerialNumber);

            // Endpoints
            ConfiguredEndpoint endPoints = ConfigureEndpoints(device);

            ConfigureMouseDevice(device, endPoints);

            ConfigureJoystickDevice(device, endPoints);

            return new ConfiguredDevice { Device = device, EndPoint = endPoints };
        }

        private static ConfiguredEndpoint ConfigureEndpoints(RawDevice device)
        {
            byte mouseWriteEPNumber = (byte)device.ReserveNewEndpoint();
            byte mouseReadEPNumber = (byte)device.ReserveNewEndpoint();
            MS.Configuration.Endpoint[] mouseEpDesc = {
                new MS.Configuration.Endpoint(mouseWriteEPNumber, MS.Configuration.Endpoint.ATTRIB_Write | MS.Configuration.Endpoint.ATTRIB_Interrupt),
                new MS.Configuration.Endpoint(mouseReadEPNumber, MS.Configuration.Endpoint.ATTRIB_Read | MS.Configuration.Endpoint.ATTRIB_Interrupt),
            };
            mouseEpDesc[0].wMaxPacketSize = 32;
            mouseEpDesc[0].bInterval = 10;
            mouseEpDesc[1].wMaxPacketSize = 32;
            mouseEpDesc[1].bInterval = 10;

            byte joystickWriteEPNumber = (byte)device.ReserveNewEndpoint();
            byte joystickReadEPNumber = (byte)device.ReserveNewEndpoint();

            MS.Configuration.Endpoint[] joystickEpDesc = {
                new MS.Configuration.Endpoint(joystickWriteEPNumber, MS.Configuration.Endpoint.ATTRIB_Write | MS.Configuration.Endpoint.ATTRIB_Interrupt),
                new MS.Configuration.Endpoint(joystickReadEPNumber, MS.Configuration.Endpoint.ATTRIB_Read | MS.Configuration.Endpoint.ATTRIB_Interrupt),
            };

            joystickEpDesc[0].wMaxPacketSize = 16;
            joystickEpDesc[0].bInterval = 10;
            joystickEpDesc[1].wMaxPacketSize = 16;
            joystickEpDesc[1].bInterval = 10;

            return new ConfiguredEndpoint { 
                MouseWriteEndpointNumber = mouseWriteEPNumber, 
                MouseReadEndpointNumber = mouseReadEPNumber, 
                MouseEndPointDescriptor = mouseEpDesc, 
                JoystickWriteEndpointNumber = joystickWriteEPNumber, 
                JoystickReadEndpointNumber = joystickReadEPNumber, 
                JoystickEndPointDescriptor = joystickEpDesc 
            };
        }

        private static void ConfigureMouseDevice(RawDevice device, ConfiguredEndpoint endPoints)
        {
            byte[] hidMouseReportDescriptorPayload = GetMouseHidReportDescriptorPayload();

            // Class Descriptor
            var mouseClassDescriptorPayload = GetHidClassDescriptorPayload(((byte)hidMouseReportDescriptorPayload.Length));

            // HID class descriptor
            MS.Configuration.ClassDescriptor hidMouseClassDescriptor = new MS.Configuration.ClassDescriptor(HID_DESCRIPTOR_TYPE, mouseClassDescriptorPayload);
            // Interface
            MS.Configuration.UsbInterface usbMouseInterface = new MS.Configuration.UsbInterface(0, endPoints.MouseEndPointDescriptor);
            usbMouseInterface.classDescriptors = new MS.Configuration.ClassDescriptor[] { hidMouseClassDescriptor };
            usbMouseInterface.bInterfaceClass = 0x03; // HID
            usbMouseInterface.bInterfaceSubClass = 0x00;
            usbMouseInterface.bInterfaceProtocol = 0x02;
            // Attach interface
            byte interfaceIndex = device.AddInterface(usbMouseInterface, "Hotas Track Ball/KeyPad");

            MS.Configuration.GenericDescriptor hidMouseGenericReportDescriptor = new MS.Configuration.GenericDescriptor(0x81, 0x2200, hidMouseReportDescriptorPayload);
            hidMouseGenericReportDescriptor.bRequest = 0x06; // GET_DESCRIPTOR
            hidMouseGenericReportDescriptor.wIndex = 0x00; // INTERFACE 0 (zero)
            // attach descriptor
            device.AddDescriptor(hidMouseGenericReportDescriptor);
        }

        public static byte[] GetMouseHidReportDescriptorPayload()
        {
            return new byte[]
            {
                    0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
                    0x09, 0x02,        // USAGE (Mouse)
                    0xa1, 0x01,        // COLLECTION (Application)
                    0x09, 0x02,        //   USAGE (Mouse)
                    0xa1, 0x02,        //   COLLECTION (Logical)
                    0x09, 0x01,        //     USAGE (Pointer)
                    0xa1, 0x00,        //     COLLECTION (Physical)
                                       // ------------------------------  Buttons
                    0x05, 0x09,        //       USAGE_PAGE (Button)      
                    0x19, 0x01,        //       USAGE_MINIMUM (Button 1)
                    0x29, 0x20,        //       USAGE_MAXIMUM (Button 32)
                    0x15, 0x00,        //       LOGICAL_MINIMUM (0)
                    0x25, 0x01,        //       LOGICAL_MAXIMUM (1)
                    0x75, 0x01,        //       REPORT_SIZE (1)
                    0x95, 0x20,        //       REPORT_COUNT (32)
                    0x81, 0x02,        //       INPUT (Data,Var,Abs)

                                        // ------------------------------  Buttons
                    0x05, 0x09,        //       USAGE_PAGE (Button)      
                    0x19, 0x21,        //       USAGE_MINIMUM (Button 33)
                    0x29, 0x29,        //       USAGE_MAXIMUM (Button 41)
                    0x15, 0x00,        //       LOGICAL_MINIMUM (0)
                    0x25, 0x01,        //       LOGICAL_MAXIMUM (1)
                    0x75, 0x01,        //       REPORT_SIZE (1)
                    0x95, 0x9,         //       REPORT_COUNT (9)
                    0x81, 0x02,        //       INPUT (Data,Var,Abs)

                                       // ------------------------------  Padding
                    0x75, 0x17,        //       REPORT_SIZE (23)
                    0x95, 0x01,        //       REPORT_COUNT (1)
                    0x81, 0x03,        //       INPUT (Cnst,Var,Abs)
    

                                      // ------------------------------  X,Y position
                    0x05, 0x01,        //       USAGE_PAGE (Generic Desktop)
                    0x09, 0x30,        //       USAGE (X)
                    0x09, 0x31,        //       USAGE (Y)
                    0x17, 0x01, 0x00, 0x00, 0x80, //       LOGICAL_MINIMUM (-2147483647)
                    0x27, 0xff, 0xff, 0xff, 0x7f,  //       LOGICAL_MAXIMUM (2147483647)
                    0x75, 0x20,        //       REPORT_SIZE (32)
                    0x95, 0x02,        //       REPORT_COUNT (2)
                    0x81, 0x06,        //       INPUT (Data,Var,Rel)
 
                    0xc0,              //     END_COLLECTION
                    0xc0,              //   END_COLLECTION
                    0xc0     
            };
        }

        private static void ConfigureJoystickDevice(RawDevice device, ConfiguredEndpoint endPoints)
        {
            byte[] hidJoystickReportDescriptorPayload = GetJoystickHidReportDescriptorPayload();

            // Class Descriptor
            var joystickClassDescriptorPayload = GetHidClassDescriptorPayload(((byte)hidJoystickReportDescriptorPayload.Length));

            // HID class descriptor
            MS.Configuration.ClassDescriptor hidJoystickClassDescriptor = new MS.Configuration.ClassDescriptor(HID_DESCRIPTOR_TYPE, joystickClassDescriptorPayload);

            // Interface
            MS.Configuration.UsbInterface usbJoystickInterface = new MS.Configuration.UsbInterface(1, endPoints.JoystickEndPointDescriptor);

            usbJoystickInterface.classDescriptors = new MS.Configuration.ClassDescriptor[] { hidJoystickClassDescriptor };
            usbJoystickInterface.bInterfaceClass = 0x03; // HID
            usbJoystickInterface.bInterfaceSubClass = 0x00;
            usbJoystickInterface.bInterfaceProtocol = 0x00;

            // Attach interface
            byte interfaceIndex = device.AddInterface(usbJoystickInterface, "Hotas Joystick");

            MS.Configuration.GenericDescriptor hidJoystickGenericReportDescriptor = new MS.Configuration.GenericDescriptor(0x81, 0x2200, hidJoystickReportDescriptorPayload);
            hidJoystickGenericReportDescriptor.bRequest = 0x06; // GET_DESCRIPTOR
            hidJoystickGenericReportDescriptor.wIndex = 0x01; // INTERFACE 0 (zero)
            // attach descriptor
            device.AddDescriptor(hidJoystickGenericReportDescriptor);
        }

        public static byte[] GetJoystickHidReportDescriptorPayload()
        {
            return new byte[]
            {
                 0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
                 0x09, 0x05,                    // USAGE (Joystick)
                 0xa1, 0x01,                    //   COLLECTION (Application)
                 0xa1, 0x00,                    // COLLECTION (Physical)
                 0x05, 0x09,                    //   USAGE_PAGE (Button)
                 0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
                 0x29, 0x10,                    //   USAGE_MAXIMUM (Button 16)
                 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
                 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
                 0x95, 0x10,                    //   REPORT_COUNT (6)
                 0x75, 0x01,                    //   REPORT_SIZE (1)
                 0x81, 0x02,                    //   INPUT (Data,Var,Abs)
                 0xc0,                          // END COLLECTION
                 0xc0                           // END COLLECTION
            };
        }

        public static byte[] GetHidClassDescriptorPayload(byte ReportPayLoadLength)
        {
            return new byte[] 
            {
                0x01, 0x01,     // bcdHID (v1.01)
                0x00,           // bCountryCode
                0x01,           // bNumDescriptors
                0x22,           // bDescriptorType (report)
                ReportPayLoadLength, 0x00      // wDescriptorLength (report descriptor size in bytes)
            };
        }
    }

    public class ConfiguredEndpoint
    {
        public byte MouseWriteEndpointNumber { get; set; }
        public byte MouseReadEndpointNumber { get; set; }

        public byte JoystickWriteEndpointNumber { get; set; }
        public byte JoystickReadEndpointNumber { get; set; }

        public MS.Configuration.Endpoint[] MouseEndPointDescriptor { get; set; }

        public MS.Configuration.Endpoint[] JoystickEndPointDescriptor { get; set; }
    }

    public class ConfiguredDevice
    {
        public RawDevice Device;
        public ConfiguredEndpoint EndPoint;
    }
}

I then call this in my Program too create two streams for my two interfaces.


 try
            {
                Device = UsbClient.ConfigureUsbDevice();
                Controller.ActiveDevice = Device.Device;
                MouseStream = Device.Device.CreateStream(Device.EndPoint.MouseWriteEndpointNumber, Device.EndPoint.MouseReadEndpointNumber);
                JoystickStream = Device.Device.CreateStream(Device.EndPoint.JoystickWriteEndpointNumber, Device.EndPoint.JoystickReadEndpointNumber);
            }
            catch (Exception e)
            {
                Debug.Print(
                    "Client stream could not be created due to exception " +
                    e.Message);
                
                return;
            }

This results in a device created in windows that is both a mouse and a gamepad controller. I am able to open the properties and verify they are initialized correctly and functioning.

Later in my code i call something like this every 10ms:


foreach (var byteValue in mouseReport)
            {
                MouseStream.WriteByte(byteValue);
            }
            MouseStream.Flush();

In that code I am iterating through a byte array of mouse data that is outputted according to the report descriptor.

However when MouseStream.WriteByte(byteValue) is called i am getting a timeout on the after the first few writes. From that point on every write fails.

Using mainboard G120II version 2.0
#### Exception System.InvalidOperationException - 0x00000000 (4) ####
#### Message: Operation timed out.
#### GHI.Usb.Client.RawDevice+RawStream::Write [IP: 007d] ####
#### System.IO.Stream::WriteByte [IP: 0010] ####
#### HotasExpansion.Program::Report_Frame [IP: 0020] ####
A first chance exception of type ‘System.InvalidOperationException’ occurred in GHI.Usb.dll
An unhandled exception of type ‘System.InvalidOperationException’ occurred in GHI.Usb.dll
Additional information: Operation timed out.

Debugging over serial shows me that the stream was created and initialized successfully and the Stream state indicates that it can read and write.

I am using v4.3.5.0 of The GHI.Usb library and .net micro 4.3.1 on a G120hdrII

@ CSharpie - Is there a reason you are writing a single byte at a time? Have you tried to increase the WriteTimeout property?

Hi john. Originally i was using write rather than writebyte it got changed just experimenting with different things. I’ve increased the timeout on the stream to 10 ms but i still get some failures.

@ CSharpie - Can you try something closer to 100ms or 1000ms?

I changed the timeout to 100 and 1000 ms respectively and i’m still getting write timeouts. It seems like the stream is never truly getting opened because none of the writes ever succeed. A bit strange considering the device shows up in windows with both interfaces and no error status. I am also able to verify the correct report descriptors are being sent for each device with a usb analyzer tool.

I initially thought some of the writes were succeeding but after closer inspection and better debug statements i see that they all fail.

Additionally i stubbed out an empty report with just 16 bytes for the mouse as per the descriptor i wrote and had no luck getting anything to be written.

OK I’ve narrowed down the cause of the problem. Here is my class extension for RawDevice.


using System;
using System.Threading;
using GHI.Usb.Client;
using Microsoft.SPOT.Hardware.UsbClient;


namespace HotasExpansion
{
    public class HotasThrottle : RawDevice
    {
        static ushort myVID = 0x0335;
        static ushort myPID = 0x0013;
        static ushort myDeviceVersion = 0x100;
        static ushort myDeviceMaxPower = 500; // in milli amps
        static string companyName = "Something Something";
        static string productName = "HOTAS Throttle";
        static string myDeviceSerialNumber = "0";

        private int MouseWriteEndpoint { get; set; }
        private int JoystickWriteEndpoint { get; set; }

        private RawStream JoystickStream { get; set; }
        private RawStream MouseStream { get; set; }

        public HotasThrottle()
            : this(myVID, myPID, myDeviceVersion, myDeviceMaxPower, companyName, productName, myDeviceSerialNumber)
        {
        }
        public HotasThrottle(ushort myVID, ushort myPID, ushort myDeviceVersion, ushort myDeviceMaxPower, string companyName, string productName, string myDeviceSerialNumber) :
            base(myVID, myPID, myDeviceVersion, myDeviceMaxPower, companyName, productName, myDeviceSerialNumber)
        {

            MouseWriteEndpoint = this.ReserveNewEndpoint();
            JoystickWriteEndpoint = this.ReserveNewEndpoint();

            InitializeMouse();
            InitializeJoystick();
        }

        private void InitializeMouse()
        {
            //Configure Mouse Endpoint
            Configuration.Endpoint[] MouseEndPoints = new Configuration.Endpoint[1];
            Configuration.Endpoint mouseEndpoint1 = new Configuration.Endpoint((byte)MouseWriteEndpoint, (byte)131);
            mouseEndpoint1.wMaxPacketSize = (ushort)8;
            mouseEndpoint1.bInterval = (byte)10;
            MouseEndPoints[0] = mouseEndpoint1;

            //Configure Mouse Interface
            var MouseInterface = new Configuration.UsbInterface((byte)0, MouseEndPoints);
            MouseInterface.bInterfaceClass = (byte)3;
            MouseInterface.bInterfaceSubClass = (byte)0;
            MouseInterface.bInterfaceProtocol = (byte)0;

            MouseInterface.classDescriptors = new Configuration.ClassDescriptor[1] { 
                new Configuration.ClassDescriptor((byte)33, GetHidClassDescriptorPayload((byte)GetMouseHidReportDescriptorPayload().Length)) 
            };

            var genericMouseDescriptor = new Configuration.GenericDescriptor((byte)129, (ushort)8704, GetMouseHidReportDescriptorPayload());
            //Add members
            byte Mnum = this.AddInterface(MouseInterface, "HOTATrackball");
            this.AddDescriptor((Configuration.Descriptor)genericMouseDescriptor);
            genericMouseDescriptor.wIndex = (ushort)Mnum;

            //create the mouse stream;
            MouseStream = this.CreateStream(MouseWriteEndpoint, RawDevice.RawStream.NullEndpoint);
        }

        private void InitializeJoystick()
        {
            //Configure Joystick Endpoint
            Configuration.Endpoint[] JoystickEndPoints = new Configuration.Endpoint[1];
            Configuration.Endpoint joystickEndpoint1 = new Configuration.Endpoint((byte)JoystickWriteEndpoint, (byte)131);
            joystickEndpoint1.wMaxPacketSize = (ushort)8;
            joystickEndpoint1.bInterval = (byte)10;
            JoystickEndPoints[0] = joystickEndpoint1;

            //Configure Joystick Interface
            Configuration.UsbInterface JoystickInterface = new Configuration.UsbInterface((byte)1, JoystickEndPoints);
            JoystickInterface.bInterfaceClass = (byte)3;
            JoystickInterface.bInterfaceSubClass = (byte)0;
            JoystickInterface.bInterfaceProtocol = (byte)0;

            JoystickInterface.classDescriptors = new Configuration.ClassDescriptor[1] { 
                new Configuration.ClassDescriptor((byte)33, GetHidClassDescriptorPayload((byte)GetJoystickHidReportDescriptorPayload().Length)) 
            };

            Configuration.GenericDescriptor genericJoystickDescriptor = new Configuration.GenericDescriptor((byte)129, (ushort)8704, GetJoystickHidReportDescriptorPayload());
            //Add Members
            byte Jnum = this.AddInterface(JoystickInterface, "HOThrottle");
            this.AddDescriptor((Configuration.Descriptor)genericJoystickDescriptor);
            genericJoystickDescriptor.wIndex = (ushort)Jnum;

            JoystickStream = this.CreateStream(JoystickWriteEndpoint, RawDevice.RawStream.NullEndpoint);
        }

        public static byte[] GetHidClassDescriptorPayload(byte ReportPayLoadLength)
        {
            return new byte[] 
            {
                0x01, 0x01,     // bcdHID (v1.01)
                0x00,           // bCountryCode
                0x01,           // bNumDescriptors
                0x22,           // bDescriptorType (report)
                ReportPayLoadLength, 0x00      // wDescriptorLength (report descriptor size in bytes)
            };
        }

        public static byte[] GetJoystickHidReportDescriptorPayload()
        {
            return new byte[]
            {
                 0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
                 0x09, 0x05,                    // USAGE (Joystick)
                 0xa1, 0x01,                    //   COLLECTION (Application)
                 0xa1, 0x00,                    // COLLECTION (Physical)
                 0x05, 0x09,                    //   USAGE_PAGE (Button)
                 0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
                 0x29, 0x10,                    //   USAGE_MAXIMUM (Button 16)
                 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
                 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
                 0x95, 0x10,                    //   REPORT_COUNT (16)
                 0x75, 0x01,                    //   REPORT_SIZE (1)
                 0x81, 0x02,                    //   INPUT (Data,Var,Abs)
                 0xc0,                          // END COLLECTION
                 0xc0                           // END COLLECTION
            };
        }

        public static byte[] GetMouseHidReportDescriptorPayload()
        {
            return new byte[]
            {
                    0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
                    0x09, 0x02,        // USAGE (Mouse)
                    0xa1, 0x01,        // COLLECTION (Application)
                    0x09, 0x02,        //   USAGE (Mouse)
                    0xa1, 0x02,        //   COLLECTION (Logical)
                    0x09, 0x01,        //     USAGE (Pointer)
                    0xa1, 0x00,        //     COLLECTION (Physical)
                                       // ------------------------------  Buttons
                    0x05, 0x09,        //       USAGE_PAGE (Button)      
                    0x19, 0x01,        //       USAGE_MINIMUM (Button 1)
                    0x29, 0x20,        //       USAGE_MAXIMUM (Button 32)
                    0x15, 0x00,        //       LOGICAL_MINIMUM (0)
                    0x25, 0x01,        //       LOGICAL_MAXIMUM (1)
                    0x75, 0x01,        //       REPORT_SIZE (1)
                    0x95, 0x20,        //       REPORT_COUNT (32)
                    0x81, 0x02,        //       INPUT (Data,Var,Abs)

                                        // ------------------------------  Buttons
                    0x05, 0x09,        //       USAGE_PAGE (Button)      
                    0x19, 0x21,        //       USAGE_MINIMUM (Button 33)
                    0x29, 0x29,        //       USAGE_MAXIMUM (Button 41)
                    0x15, 0x00,        //       LOGICAL_MINIMUM (0)
                    0x25, 0x01,        //       LOGICAL_MAXIMUM (1)
                    0x75, 0x01,        //       REPORT_SIZE (1)
                    0x95, 0x9,         //       REPORT_COUNT (9)
                    0x81, 0x02,        //       INPUT (Data,Var,Abs)

                                       // ------------------------------  Padding
                    0x75, 0x17,        //       REPORT_SIZE (23)
                    0x95, 0x01,        //       REPORT_COUNT (1)
                    0x81, 0x03,        //       INPUT (Cnst,Var,Abs)
    

                                      // ------------------------------  X,Y position
                    0x05, 0x01,        //       USAGE_PAGE (Generic Desktop)
                    0x09, 0x30,        //       USAGE (X)
                    0x09, 0x31,        //       USAGE (Y)
                    0x17, 0x01, 0x00, 0x00, 0x80, //       LOGICAL_MINIMUM (-2147483647)
                    0x27, 0xff, 0xff, 0xff, 0x7f,  //       LOGICAL_MAXIMUM (2147483647)
                    0x75, 0x20,        //       REPORT_SIZE (32)
                    0x95, 0x02,        //       REPORT_COUNT (2)
                    0x81, 0x06,        //       INPUT (Data,Var,Rel)
 
                    0xc0,              //     END_COLLECTION
                    0xc0,              //   END_COLLECTION
                    0xc0     
            };
        }

        public void WriteJoystickReport(byte[] report)
        {
            if (JoystickStream.CanWrite)
            {
                JoystickStream.Write(report);
            }
        }

        public void WriteMouseReport(byte[] report)
        {
            if (MouseStream.CanWrite)
            {
                MouseStream.Write(report);
            }
        }
    }

}

I cleaned up my usb code and mirrored what i dug out of the mouse driver that wraps RawDevice. This didn’t solve my problem but it let me look elsewhere as i had more trust in the code afterwards. Now to the root of the problem. It seems that serial debugging somehow is blocking the RawStream from writing. I know this because after cleaning up my code i was able to feed fake data into the stream and verify that windows is receiving it. However this only works when I’m not debugging via serial.

As soon as i deploy the code and attach the debugger all of my writes throw exceptions. This means that ill have debug the program logic separately and test it’s output with usb disabled and the re enable usb for the deployed code. It also leaves me heavily dependent on my usb analyzer tool to troubleshoot usb problems that occur between my software stack and windows. I am thankful that this doesn’t really stop me from continuing down this avenue though even if it is a pain to work around.

@ CSharpie - I tried your code and Windows detected 2 out of the 3 devices. One of them appeared to be the mouse and it was able to successfully write when using serial debug. If you have a driver and sample use program that you know fails you can share it with us we can look into it more.