HID Output

I’m trying to send a message to our custom hid device and not getting a response. The message requests the device’s current state, which causes the device to output the state of all its knobs, buttons, etc.

    [StructLayout( LayoutKind.Explicit, Size = size )]
    public unsafe struct GetDeviceStateMessage : IInputDeviceCommandInfo
    {
        public static FourCC type => new FourCC( 'H', 'I', 'D', 'O' );

        internal const int size = InputDeviceCommand.BaseCommandSize + 3;
        internal const int id = 63;
        internal const int length = 7;
        internal const int message = 255;

        [FieldOffset(0)]
        public InputDeviceCommand baseCommand;

        [FieldOffset(InputDeviceCommand.BaseCommandSize + 0)]
        public byte reportId;
        [FieldOffset(InputDeviceCommand.BaseCommandSize + 2)]
        public byte reportLength;
        [FieldOffset(InputDeviceCommand.BaseCommandSize + 3)]
        public byte getDeviceStateMessage;

        public FourCC typeStatic => type;

        public static GetDeviceStateMessage Create()
        {
            return new GetDeviceStateMessage
            {
                baseCommand = new InputDeviceCommand( type, size ),
                reportId = id,
                reportLength = length,
                getDeviceStateMessage = message
            };
        }
    }

Its called when a device is reconnected:

InputSystem.onDeviceChange +=
    ( device, change ) =>
        {
            if( device.GetType() != typeof( My_HID_Device ) )
                return;

            switch( change )
            {
                case InputDeviceChange.Reconnected:
                    My_HID_Device.current.RequestDeviceState();
                    break;
            }
        };

which calls:

        public void RequestDeviceState()
        {
            Debug.Log( "REQUEST" );
            var command = GetDeviceStateMessage.Create();
            ExecuteCommand( ref command );
        }

On a previous platform I had packed the message into a 64 byte packet and sent it out by hand like this:

bool HandHeldInput::MessageDevice( BYTE msgIn[], int msgLength ) {
  
    bool SUCCESS = true;
    BYTE buff[USB_OUTPUT_SIZE];
    memset( buff, 0x0, USB_OUTPUT_SIZE );
    for( int i = 0; i < msgLength; ++i )
        buff[i] = msgIn[i];
    if( m_hDeviceHandle == NULL ||
        hid_write( m_hDeviceHandle, buff, USB_OUTPUT_SIZE ) == -1 )
    {
        std::cout << "Device message not sent! Is message format correct?" << std::endl;
        SUCCESS = false;
    }
      
    return SUCCESS;
}

I’m not sure if something different is going on under the hood for Unity and the base command?

I also notice that if I set a break point and connect the debugger and run (the game) - I stop receiving device input, though I still see them coming across in the Input Debug editor.

My suspicion would be that it’s the buffer size not matching. Probably because of the reportLength field. From the MessageDevice function you posted, it appears that the message length shouldn’t be part of the message.

In the Windows backend HID code, there’s a check that the buffer size matches the output buffer size reported by the device. If that is not the case, the device command fails. (wondering whether that check should actually be there)

So the payload size of your device command (i.e. total struct size minus sizeof(InputDeviceCommand)) has to correspond to the output report size expected by the device. Everything in the command’s payload is copied over verbatim and sent to the device via WriteFile.

Unfortunately, we don’t yet have any kind of nuanced error reporting for this kind of stuff. If it fails, there’s usually no indication why.

1 Like

On the previous platform it was sent like so:

    m_DEVICE_OUTPUT_MESSAGE[OUTPUT_BYTE_0] = OUTPUT_REPORT_ID;
    m_DEVICE_OUTPUT_MESSAGE[OUTPUT_BYTE_1] = OUTPUT_REPORT_LENGTH;
    m_DEVICE_OUTPUT_MESSAGE[OUTPUT_BYTE_2] = OUTPUT_MESSAGE_GETSTATE;
    m_inputDevice.MessageDevice( m_DEVICE_OUTPUT_MESSAGE, MAX_OUTPUT_SIZE );

where:

    enum DEVICE_OUTPUT
    {
        OUTPUT_BYTE_0                       = 0,
            OUTPUT_REPORT_ID                =   0x3F,
       
        OUTPUT_BYTE_1                       = 1,
            OUTPUT_REPORT_LENGTH            =   0x7,
       
        OUTPUT_BYTE_2                       = 2,
            OUTPUT_MESSAGE_GETSTATE         =   0xFF,
   
        MAX_OUTPUT_SIZE                     = 3
    };
    BYTE    m_DEVICE_OUTPUT_MESSAGE[MAX_OUTPUT_SIZE];

I tried changing the size in Unity to

internal const int size = InputDeviceCommand.BaseCommandSize + 64;

64 because we#define USB_OUTPUT_SIZE 64

but still no response =(

Never mind, it looks like changing the size did fix it.