HID class

(Esta entrada es la segunda parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

USB ha tenido a bien reemplazar casi todos los conectores de una computadora; en el caso de los teléfonos celulares inteligentes modernos (que son realmente computadoras), de hecho reemplaza todos los conectores, excepto el conector para audífonos. Bueno; hablo de teléfonos celulares civilizados: obviamente el iPhone tiene que tener su conector propietario, porque si no cómo le van a cobrar millones de dólares a sus usuarios cuando quieran reemplazarlo.

El bus serial universal (que es lo que USB significa) ha tenido este éxito por muchos motivos, de los cuales no voy a mencionar ni uno solo. Yo de lo que quiero escribir hoy aquí es de la clase USB para dispositivos con interfaz humana, o USB human interface device class en inglés.

Un dispositivo de clase USB-HID tiene la capacidad de decirle a la computadora a la cual se conecta ciertos datos; si es un teclado, por ejemplo, cuántas teclas tiene; si es un ratón, que resolución tiene el sistema de coordenadas que puede manejar; si es un joystick o gamepad, cuántos ejes y botones tiene, etc. Como USB-HID es un estándar abierto, en general esto permite que no haya necesidad de escribir controladores (drivers) para estos dispositivos: todos los sistemas operativos en existencia los soportan de manera nativa. Uno sólo conecta el dispositivo USB-HID a la computadora, y el mismo dispositivo le dice qué es, y qué características tiene. La computadora puede entonces comenzar a usar el dispositivo de inmediato, porque éste ya le dijo qué le puede pedir.

Voy a hacer la historia corta: le pide bytes. Lo único que debe hacer el dispositivo es explicarle a la computadora qué significa el bit número X del byte Y en el reporte que cada determinado número de milisegundos la computadora le pide. Es a la vez primitivo y elegante el asunto.

Cuando el dispositivo USB-HID se conecta a la computadora, ésta le pide un descriptor de dispositivo (device descriptor), el cual tiene información como qué protocolo utiliza, cuántas configuraciones maneja, y lo más visible para todo mundo, dos llaves de diccionario para el identificador de fabricante y el identificador de producto. Estas dos llaves son en Linux muy fáciles de revisar (me imagino que en otros sistemas operativos también; pero como no los uso, no tengo idea); usando el comando lsusb en mi laptop produce el siguiente resultado:

canek@acero ~ $ lsusb
Bus 004 Device 004: ID 8086:0189 Intel Corp. 
Bus 004 Device 003: ID 1bcf:288e Sunplus Innovation Technology Inc. 
Bus 004 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 031: ID 18d1:4ee1 Google Inc. Nexus 4 / 10
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 008: ID 0461:0010 Primax Electronics, Ltd HP Keyboard
Bus 001 Device 009: ID 0461:4d0f Primax Electronics, Ltd HP Optical Mouse
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Las primeras columnas vienen de Linux; especifican el puerto USB físico donde están conectados los dispositivos. La últimas columnas, que son humanamente legibles ("Primax Electronics, Ltd HP Keyboard") son entradas de un diccionario que Linux y todos los sistemas operativos modernos mantienen; es una base de datos que especifica qué información corresponde a las llaves que se muestran en la columna central ("0461:0010"), y que de hecho es la única información que viene del dispositivo USB. El primer número hexadecimal (0x0461) es el identificador de fabricante (VendorId), y el segundo (0x0010) es el identificador de producto (ProductId).

Además del identificador de dispositivo, un dispositivo USB-HID le envía a la computadora un reporte descriptivo (report descriptor) que es donde se (valga la rebuznancia) describe lo que el dispositivo puede o no puede hacer. Usando algunas opciones extras de lsusb, podemos ver esta información:

canek@acero ~ $ lsusb -vvv -d 0461:0010
...
          Report Descriptor: (length is 65)
            Item(Global): Usage Page, data= [ 0x01 ] 1
                            Generic Desktop Controls
            Item(Local ): Usage, data= [ 0x06 ] 6
                            Keyboard
            Item(Main  ): Collection, data= [ 0x01 ] 1
                            Application
            Item(Global): Usage Page, data= [ 0x07 ] 7
                            Keyboard
            Item(Local ): Usage Minimum, data= [ 0xe0 ] 224
                            Control Left
            Item(Local ): Usage Maximum, data= [ 0xe7 ] 231
                            GUI Right
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0x01 ] 1
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x01 ] 1
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x01 ] 1
                            Constant Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x03 ] 3
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Global): Usage Page, data= [ 0x08 ] 8
                            LEDs
            Item(Local ): Usage Minimum, data= [ 0x01 ] 1
                            NumLock
            Item(Local ): Usage Maximum, data= [ 0x03 ] 3
                            Scroll Lock
            Item(Main  ): Output, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x05 ] 5
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Main  ): Output, data= [ 0x01 ] 1
                            Constant Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x06 ] 6
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Global): Usage Page, data= [ 0x07 ] 7
                            Keyboard
            Item(Local ): Usage Minimum, data= [ 0x00 ] 0
                            No Event
            Item(Local ): Usage Maximum, data= [ 0xff 0x00 ] 255
                            (null)
            Item(Main  ): Input, data= [ 0x00 ] 0
                            Data Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
...

(Estoy aquí usando un truco en el que no quiero entrar en detalle; pero básicamente así podemos obtener el reporte descriptivo).

Esa información que está allá arriba es bastante legible para un humano; de nuevo, Linux es el que está haciendo esa chamba por nosotros. El dispositivo lo que envía son una serie de bytes codificados para expresar lo de arriba; por ejemplo, el primer elemento especifica un “Usage Page: Generic Desktop”: esto está codificado por los bytes 0x05 (Usage Page), y 0x01 (Generic Desktop). El segundo elemento es un “Usage: Keyboard”, que está codificado por los bytes 0x09 (Usage) y 0x06 (Keyboard). En todos los tutoriales en línea esto se suele representar así (usando C, pero cualquier lenguaje funciona):

0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x06, /* Usage (Keyboard) */
...

Pero obviamente lo que la computadora recibe es 0x05 0x01 0x09 0x06 ...

Con esta información, la computadora sabe cuántos bytes pedirle a un dispositivo USB-HID, y qué codifica cada uno de estos bytes; el estándar es (paradójicamente) muy flexible e increíblemente rígido al respecto. Muy flexible porque uno puede enviar vía USB básicamente lo que a uno se le dé la regalada gana; pero los reportes descriptivos sólo codifican información para un puñado de cosas: ejes, botones/teclas apretados o no apretados, y cosas de este estilo. Sin embargo, como todo esto son únicamente bytes, uno puede darle la vuelta sencillamente describiéndolo como un “eje”, cuando en realidad está enviando un número que representa la temperatura en grados centígrados. O lo que sea; la verdad no leí el estándar completo (lo pueden bajar aquí), sólo lo hojeé hasta que me salí con la mía.

Y con la mía me salí; aunque no al 100%: aún hay un detalle que necesito resolver, pero es secundario a mis principales objetivos, y lo que quería hacer ya lo hice casi absolutamente todo. Pero si quisiera resolver absolutamente todo, debería leer con cuidado el estándar y ver si le puedo dar la vuelta al último obstáculo que me he encontrado.

De eso escribiré más adelante.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *