Auricle: hackeando el sensor de movimiento de los AirPods para controlar Linux con la cabeza

¿Y si pudiéramos utilizar los sensores de movimiento de unos AirPods para controlar un ordenador Linux sin tocar el teclado ni el ratón? No hablamos de una funcionalidad oficial de Apple, ni de una aplicación que aproveche una API pública. Hablamos de conectarse directamente a los auriculares, establecer una comunicación Bluetooth de bajo nivel y extraer los datos de su unidad de medición inercial (IMU).

Eso es precisamente lo que hace Auricle, un pequeño proyecto escrito en Rust que merece bastante atención por lo que demuestra: que, con suficiente ingeniería inversa, es posible acceder a funcionalidades de dispositivos comerciales que sus fabricantes nunca han pensado exponer directamente a otros sistemas operativos.

El proyecto, desarrollado por freaktopus, permite leer los datos de orientación de determinados modelos de AirPods mediante Bluetooth L2CAP en Linux y convertirlos en un flujo de información reutilizable por otras aplicaciones.

Y lo mejor es que no se queda en una prueba de concepto que imprime números por consola. Incluye una API HTTP, un dashboard en tiempo real, un modo de ratón controlado por movimientos de cabeza e incluso un pequeño juego de bicicleta que utiliza la inclinación de los auriculares como mecanismo de control.

Vamos a ver cómo funciona.

1. El problema: acceder a sensores que Apple no expone

Los auriculares inalámbricos modernos son bastante más sofisticados de lo que parece. Además de reproducir audio, algunos incorporan acelerómetros y giroscopios que permiten detectar movimientos y orientación de la cabeza.

Apple utiliza esta información, entre otras cosas, para implementar funcionalidades como el audio espacial con seguimiento dinámico de la cabeza (Dynamic Head Tracking).

Cuando utilizamos unos AirPods compatibles con un dispositivo Apple, el sistema puede conocer determinados movimientos de nuestra cabeza y adaptar la experiencia de audio en consecuencia. Sin embargo, desde Linux la situación es diferente.

El sensor no aparece como un dispositivo HID convencional, ni existe una característica GATT estándar que permita consultar directamente su estado. Tampoco basta con emparejar los auriculares y utilizar una biblioteca Bluetooth habitual. Los datos se transmiten a través de un canal de control propietario de Apple, basado en Bluetooth L2CAP.

Aquí es donde entra Auricle: en lugar de intentar utilizar una interfaz de alto nivel inexistente, implementa directamente un cliente capaz de establecer la conexión con ese canal y solicitar los informes del sensor.

Es importante destacar que el proyecto es experimental. El autor ha probado su funcionamiento con unos AirPods Pro de segunda generación, pero no ha validado de forma independiente el funcionamiento de todos los modelos que podrían utilizar el mismo protocolo.

2. Bluetooth L2CAP: bajando un nivel

Para entender lo que hace Auricle hay que detenerse en L2CAP (Logical Link Control and Adaptation Protocol). L2CAP es una de las capas fundamentales de la pila Bluetooth. Se sitúa por encima de los mecanismos de transporte de bajo nivel y permite establecer canales lógicos entre dispositivos. A diferencia de una aproximación basada en perfiles estándar como HID o GATT, aquí el proyecto necesita comunicarse directamente con un canal específico de Apple. El parámetro clave es el PSM (Protocol/Service Multiplexer):

PSM: 0x1001

Este identificador corresponde al canal de control que utiliza el protocolo propietario de Apple para intercambiar información con los auriculares.

La implementación de Auricle aprovecha las capacidades que proporciona el propio kernel Linux para abrir un socket Bluetooth L2CAP, sin depender de una biblioteca Bluetooth externa. La operación se realiza mediante llamadas al sistema:

socket(
    AF_BLUETOOTH,
    SOCK_SEQPACKET,
    BTPROTO_L2CAP
);

La elección de SOCK_SEQPACKET es especialmente interesante: permite preservar los límites de los mensajes, de modo que cada operación de recepción obtiene un paquete completo del protocolo en lugar de tratar el flujo como una secuencia arbitraria de bytes.

Para establecer la conexión, el cliente construye una estructura sockaddr_l2 con la dirección Bluetooth del dispositivo y el PSM correspondiente.

Hay otro detalle importante: las direcciones Bluetooth se almacenan en orden inverso en la estructura utilizada por el kernel. Por tanto, una dirección como:

AA:BB:CC:DD:EE:FF

debe convertirse en:

FF:EE:DD:CC:BB:AA

Auricle configura además el nivel de seguridad Bluetooth mediante BT_SECURITY_MEDIUM antes de realizar la conexión. Durante las pruebas, el flujo de datos únicamente se observó de forma consistente cuando el enlace estaba cifrado, aunque el propio autor advierte que no ha aislado experimentalmente esta condición del resto del proceso.

No estamos ante una simple lectura de características de un dispositivo Bluetooth: estamos implementando un cliente para un protocolo propietario no documentado.

3. El handshake: hablando el idioma de los AirPods

Una vez establecido el canal L2CAP, comienza la parte más interesante.

Auricle necesita iniciar una sesión, negociar determinadas capacidades y solicitar explícitamente que los auriculares comiencen a transmitir los informes del sensor. El protocolo utiliza mensajes binarios con una estructura relativamente sencilla: un prefijo de cuatro bytes, un opcode de dos bytes en little-endian y un payload. El proceso documentado por el proyecto sigue esta secuencia:

OperaciónOpcodeFunción
Handshake—Inicializar la sesión
Feature flags0x004DComunicar capacidades
Notifications0x000FSuscribirse a notificaciones
Take over0x0009Solicitar el control de la conexión
Start tracking0x0017Iniciar el envío de datos de movimiento

El primer mensaje es el handshake:

00 00 04 00 01 00 02 00
00 00 00 00 00 00 00 00

A continuación se envía el mensaje de capacidades:

04 00 04 00 4D 00 D7 00
00 00 00 00 00 00

Y posteriormente la suscripción a notificaciones:

04 00 04 00 0F 00 FF FF
FF FF

El siguiente paso es especialmente significativo:

04 00 04 00 09 00 06 01
00 00 00

Se trata del comando Take over, que solicita al cliente asumir el control de esa conexión.

Finalmente llega la petición de inicio del seguimiento de movimiento, mediante el opcode 0x0017.

Hay un comportamiento curioso: según las observaciones del desarrollador, existen dos variantes del mensaje de inicio, aparentemente relacionadas con diferencias de firmware. Auricle alterna ambas y vuelve a enviar periódicamente una de ellas para mantener activo el flujo.

Los mensajes se espacian aproximadamente 300 milisegundos durante la inicialización, seguidos de una espera de 1,2 segundos. Una vez completada esta secuencia, los auriculares empiezan a transmitir mensajes que contienen los datos del sensor.

4. Reverse engineering de los paquetes: extrayendo la IMU

Los informes de movimiento utilizan el opcode 0x0017 y tienen una longitud aproximada de 80 bytes (se han observado mensajes de 80 y 81 bytes).

Auricle identifica estos paquetes y extrae determinados campos de su payload.

Los valores relevantes se encuentran en las siguientes posiciones:

OffsetCampoDescripción
0-3PrefixCabecera del mensaje
4-5Opcode0x0017
43-44o1Componente de orientación
45-46o2Componente de orientación
47-48o3Componente de orientación
51-52axAceleración horizontal
53-54ayAceleración vertical

Los valores se interpretan como enteros de 16 bits con signo (int16) y en orden little-endian.

Un aspecto interesante es que el proyecto no intenta decodificar completamente el descriptor del sensor que envían los auriculares durante el establecimiento de la sesión. En su lugar, utiliza offsets conocidos a partir de las observaciones y del trabajo previo de ingeniería inversa de la comunidad.

Esto funciona, pero también introduce una limitación: cualquier modificación del formato de los informes podría romper el decodificador.

De valores binarios a ángulos

Una vez extraídos los datos, Auricle calcula dos magnitudes especialmente útiles:

  • Yaw: rotación horizontal de la cabeza.
  • Pitch: inclinación vertical de la cabeza.

Las fórmulas utilizadas, basadas en las notas de ingeniería inversa de la comunidad, son:

yaw = (o2 - o3) / 2 / 32000 * 180

pitch = (o2 + o3) / 2 / 32000 * 180

El factor 32000 es una constante empírica utilizada para convertir los valores internos a grados aproximados.

No debe interpretarse como una calibración física validada. El autor reconoce que todavía no ha contrastado estas fórmulas con una referencia externa de orientación. Además, el proyecto establece una posición inicial como centro de referencia. De esta forma, los movimientos posteriores se calculan respecto a la posición neutral de la cabeza. El resultado es un flujo de datos que puede contener información como esta:

{
  "t": 4548,
  "o1": -24881,
  "o2": 4192,
  "o3": 4491,
  "ax": -4,
  "ay": 1,
  "yaw": -0.841,
  "pitch": 24.421
}

Durante las pruebas se observaron aproximadamente 23 informes por segundo mientras el usuario movía la cabeza.

Hay una particularidad que conviene tener presente: cuando la cabeza permanece inmóvil, el sensor puede dejar de enviar datos. Esto no significa necesariamente que se haya perdido la conexión; simplemente puede no haber cambios que reportar.

5. De los paquetes Bluetooth a una API reutilizable

Una de las decisiones de diseño que más me gustan de Auricle es que desacopla la captura de los datos de las aplicaciones que los consumen.

En lugar de construir una herramienta monolítica, el proyecto mantiene un proceso encargado de leer el canal Bluetooth y publicar la última muestra disponible.

Su arquitectura puede resumirse así: 

La implementación utiliza un hilo lector que mantiene la conexión y actualiza el estado compartido.

Los consumidores no necesitan conocer los detalles del protocolo Bluetooth: simplemente acceden a las últimas muestras disponibles.

Esto permite utilizar los mismos datos desde diferentes interfaces.

Modo stream: datos en tiempo real

El comando:

./target/release/auricle stream

genera una secuencia de objetos JSON, uno por cada informe recibido.

Es probablemente el punto de entrada más interesante para experimentar, ya que permite integrar los movimientos de cabeza con cualquier aplicación capaz de procesar datos desde stdout.

Por ejemplo, podríamos desarrollar un pequeño cliente Python que leyera el flujo y utilizara los ángulos para controlar una interfaz gráfica.

Modo servidor: API HTTP y dashboard

El comando:

./target/release/auricle serve

levanta un servidor HTTP local en el puerto 8000.

Entre sus endpoints encontramos:

EndpointFunción
GET /Dashboard en tiempo real
GET /dataÚltimos valores del sensor
GET /recalRecalibrar la posición central
GET /examples/bike-gameJuego de ejemplo

La respuesta de /data proporciona, entre otros valores, los ángulos, los datos originales del sensor y el estado de la conexión.

El proyecto también permite configurar parámetros como la sensibilidad, la zona muerta (deadzone) y la inversión de los ejes.

Una advertencia importante: el servidor no incorpora autenticación y escucha únicamente en 127.0.0.1. No debería exponerse directamente a una red.

6. Controlar el ratón con la cabeza

Aquí es donde la investigación empieza a convertirse en algo bastante más tangible.

Auricle dispone de un modo que utiliza los ángulos calculados para controlar un dispositivo apuntador virtual.

./target/release/auricle mouse

La idea es sencilla:

  • Girar la cabeza horizontalmente desplaza el cursor en el eje X.
  • Inclinar la cabeza verticalmente desplaza el cursor en el eje Y.
  • Una zona muerta evita pequeños movimientos involuntarios.

Para implementar esta funcionalidad, el proyecto utiliza uinput, la interfaz del kernel Linux que permite crear dispositivos de entrada virtuales.

Es decir, Auricle no necesita modificar el comportamiento del escritorio ni comunicarse directamente con cada aplicación. Presenta al sistema operativo un dispositivo de entrada que se comporta como un ratón.

Para utilizarlo hay que disponer de permisos de escritura sobre /dev/uinput. Una posibilidad es configurar una regla udev:

sudo tee /etc/udev/rules.d/99-uinput.rules <<'EOF'
KERNEL=="uinput", GROUP="input", MODE="0660"
EOF

Después, añadimos nuestro usuario al grupo correspondiente:

sudo usermod -aG input "$USER"

Será necesario volver a iniciar sesión para aplicar los cambios.

7. Montando nuestro propio laboratorio

Si queremos probar Auricle, necesitaremos un sistema Linux con BlueZ, una herramienta de compilación Rust y unos auriculares Apple compatibles, emparejados y conectados previamente.

El proyecto indica que se ha desarrollado y probado con BlueZ 5.87.

Podemos obtener el código:

git clone https://github.com/freaktopus/auricle.git

cd auricle

Compilamos en modo release:

cargo build --release

El resultado será un ejecutable:

target/release/auricle

Primero podemos comprobar si los auriculares conectados exponen el servicio Bluetooth esperado:

./target/release/auricle scan

Si el dispositivo es reconocido, probamos el flujo de datos:

./target/release/auricle stream

Con los auriculares puestos, movemos ligeramente la cabeza y deberíamos empezar a recibir informes.

Para visualizar los datos desde el navegador:

./target/release/auricle serve

Y accedemos a:

http://127.0.0.1:8000

Desde ahí podremos observar los valores del sensor y experimentar con la aplicación de ejemplo.

Importante: que scan encuentre el servicio no garantiza que el sensor funcione. La detección comprueba la presencia del UUID del canal de control, pero no valida que el dispositivo proporcione informes de movimiento compatibles.

8. Limitaciones y aspectos pendientes

Auricle es un proyecto experimental y conviene separar lo que realmente se ha comprobado de aquello que todavía está por investigar.

La compatibilidad es limitada. El único dispositivo probado por el autor es un par de AirPods Pro de segunda generación. Otros modelos de Apple con seguimiento de cabeza podrían utilizar el mismo canal, pero todavía no están validados.

Tampoco es compatible con auriculares Sony, Samsung, Bose o Jabra. Cada fabricante puede implementar sus propios protocolos y sería necesario desarrollar un backend específico.

Hay otras cuestiones técnicas interesantes:

  • Precisión: los ángulos se calculan utilizando fórmulas obtenidas mediante ingeniería inversa comunitaria, sin validación contra una referencia independiente.
  • Roll: el proyecto no decodifica el tercer eje de rotación. Se desconoce todavía el significado exacto de o1.
  • Descriptores: los mensajes que describen el formato de los informes se ignoran y se utilizan offsets fijos.
  • Estabilidad: durante el desarrollo se observaron interrupciones del flujo sin una causa confirmada.
  • Conexiones concurrentes: los auriculares aceptan una sola conexión de control a la vez.
  • Dependencia del firmware: cualquier modificación del protocolo podría dejar de ser compatible.

También hay una cuestión de seguridad que no conviene pasar por alto: la API HTTP no incorpora autenticación. Aunque actualmente se limita a localhost, cualquier modificación de esa configuración debería ir acompañada de controles de acceso adecuados.

9. ¿Qué podríamos construir a partir de Auricle?

Más allá de la curiosidad técnica, creo que este proyecto tiene bastante recorrido como punto de partida para otros desarrollos.

La posibilidad de convertir los movimientos de cabeza en un flujo de datos estándar abre la puerta a diferentes escenarios.

Interfaces de accesibilidad. Un sistema de control por movimientos de cabeza puede servir como mecanismo alternativo de interacción para personas con dificultades para utilizar dispositivos apuntadores convencionales. Eso sí, habría que desarrollar una solución completa, calibrada y validada para ese propósito.

Interfaces de realidad virtual. Los datos de orientación podrían alimentar aplicaciones experimentales de seguimiento de cabeza, siempre teniendo en cuenta las limitaciones de precisión, latencia y disponibilidad de los informes.

Automatización. Podríamos asociar determinados gestos o movimientos a acciones concretas del sistema operativo. Por ejemplo, cambiar de escritorio, desplazarnos por una interfaz o activar determinadas funciones.

Hacking de hardware e investigación Bluetooth. Probablemente sea uno de los usos más interesantes desde el punto de vista de la comunidad: disponer de una implementación funcional de un protocolo propietario permite experimentar con otros dispositivos, estudiar su comportamiento y comprender mejor cómo se comunican los componentes internos.

Incluso sería interesante desarrollar un backend alternativo para otros fabricantes de auriculares, manteniendo el mismo modelo de datos y las mismas interfaces de salida.

Conclusión

Auricle es un buen ejemplo de por qué merece la pena investigar lo que ocurre por debajo de las interfaces que utilizamos habitualmente.

Unos auriculares que aparentemente solo sirven para escuchar música esconden sensores, protocolos de comunicación y capacidades que no necesariamente están disponibles a través de las APIs oficiales del fabricante.

Lo realmente interesante de este proyecto no es mover el ratón con la cabeza. Es haber conseguido establecer una conexión directa con un canal Bluetooth propietario, reconstruir el intercambio de mensajes necesario para activar el sensor y transformar paquetes binarios en información útil para Linux.

Todo ello con una implementación pequeña, escrita en Rust y sin depender de grandes frameworks.

Y, como suele ocurrir con este tipo de investigaciones, la parte más interesante quizá no sea lo que hace actualmente Auricle, sino todo lo que podemos construir a partir de él.

Repositorio: https://github.com/freaktopus/auricle
Créditos: freaktopus y el trabajo previo de ingeniería inversa de la comunidad LibrePods.

Comentarios