Control LED con libusb
marzo 9, 2011 21 comentarios
Hoy me ha llegado un chinagadget comprado en DealExtreme.
Se trata de un notificador de nuevos emails o eventos de mensajería.
Como era de esperar el driver/software sólo funciona para sistemas Windows pero me gustaría poder utilizar este «cachibache» en mi Ubuntu.
He supuesto que encender una bombilla vía USB no debía ser muy complicado y me he puesto manos a la obra.
Lo primero que tenia que averiguar era la configuración y el protocolo del driver para encender/apagar el LED a voluntad.
Para hacer esta mini ingeniería inversa he utilizado VirtualBox y Wireshark.
Wireshark es ampliamente usado como sistema de captura y análisis de paquetes de red. Ahora además nos permite «sniffar» el tráfico USB.
Con VirtualBox se crea una maquina virtual Windows donde instalamos los drivers oficiales del notificador.
Con nuestra máquina virtual corriendo, ponemos a escuchar a nuestro sniffer.
Es necesario saber en que bus se encuentra nuestro dispositivo. Ésto se soluciona con un simple lsusb.
Ahora le indicamos a WireShark que escuche en dicho bus y ejecutamos un escenario donde se encienda/apague el led.
El resultado es un gran fichero de captura con mucha morralla.
Analizando el tráfico se observa un incontable número de paquetes enviados por interrupción pero cuyo contenido es siempre el mismo. Desconozco su propósito (¿quizás un sistema rudimentario de heartbeat?).
Filtrando por endpoint, se ve que hay dos endpoints, 1 y 2 (además del EP de control 0).
El EP 1 es el que usa para transferencias por interrupción del mensaje repetitivo.
El EP 2 es el que se usa para el envio de comandos de encendido.
En concreto, después de jugar con la aplicación de notificación, detecte la siguiente trama para encender el led rojo:
0x02 0x04 0x04 0x04 0x04
Para apagarlo, se usa ésta otra:
0x00 0x04 0x04 0x04 0x04
Con el protocolo (a priori) detectado, decidí hacer el driver en espacio de usuario para GNU/Linux.
Para acometer ésto, opté por usar la librería de acceso a USB, libusb.
Esta librería permite el desarrollo rápido de drivers/aplicaciones USB en GNU/Linux (también está disponible para Windows). Para dispositivos sencillos como este se ajusta perfectamente. Si por el contrario queremos controlar un dispositivo con alta intesidad de E/S, es recomendable implementarlo mediante un módulo kernel.
La implementación es sencilla. Se pasa como parámetro el color del LED a encender (en la secuencia, 2 = rojo, 1 = azul, 2 = verde).
Lo relevante está en la línea 55. Aquí es donde se envía por interrupción, el comando por el EP 2.
También es interesante la línea 41. Aquí se descarga el manejador kernel de dispositivo en caso de que algún driver lo haya reclamado (en este caso el driver HID).
#include <stdio.h> #include <libusb.h> #include <errno.h> #define VID 0x1294 #define PID 0x1320 static struct libusb_device_handle *devh = NULL; int main(int argc,char** argv) { int ret; unsigned char code = 0; if (argc != 2 ) { printf("syntax: %s red | green | blue | off\n",argv[0]); return -1; } if ( strcmp(argv[1],"red") == 0 ) { code = 2; } else if ( strcmp(argv[1],"green") == 0 ) { code = 3; } else if ( strcmp(argv[1],"blue") == 0 ) { code = 1; } libusb_init(NULL); devh = libusb_open_device_with_vid_pid(NULL, VID, PID); if (devh == NULL ) { printf("not found\n"); return -1; } if ( libusb_kernel_driver_active(devh,0) ) { printf("detach from kernel\n"); ret = libusb_detach_kernel_driver(devh,0); if (ret < 0 ) { printf("can't detach\n"); return -1; } } char data[5]; data[0] = code; data[1] = 0x4; data[2] = 0x4; data[3] = 0x4; data[4] = 0x4; int dummy; ret = libusb_interrupt_transfer(devh,0x2,data,5,&dummy,0); if ( ret < 0 ) { perror("error"); } return 0; }
Os dejo el fuente de programa, una versión compilada dinámicamente y otra estáticamente.
Fuente: ledcontroller.tar.gz