Conectando Arduino y Unity haciendo uso del Tracker de las HTC VIVE

Unos de los principales retos que presentaba nuestro proyecto era la necesidad de que el entorno o plataforma elegida para el desarrollo del mismo fuese capaz de recoger los datos generados por los elementos a “sensorizar”. En esta entrada al igual que en la que se abordo el uso del puerto serie como método de conexión;  se va a utilizar una botonera como fuente de datos.

ESTUDIO DEL ELEMENTO

En nuestro caso se trata una botonera de la casa Schneider electric con 7 pulsadores  simples o una velocidad, 4 pulsadores dobles o dos velocidades y un botón adicional de parada de emergencia.  De estos datos deducimos que nuestra botonera genera 16 salidas digitales independientes.

ELECCIÓN DEL MICROCONTROLADOR

Como ya vimos en una anterior entrada, el microcontrolador sera el encargado de leer los datos del elemento a sensorizar (en este caso la botonera). Debido a que cada elemento presentara unas características diferentes,  el microcontrolador tendrá que estar elegido en base a las mismas.

Cada modelo de Arduino cuenta con una serie de especificaciones que los harán más o menos indicados dependiendo de las exigencias de la tarea a realizar. En nuestro caso las características indispensables son:

  • 16 entradas digitales
  • Contar con un puerto USB Nativo

¿Qué es esto de USB Nativo?

Para empezar necesitamos familiarizarnos con uno de los principios básicos del USB.  En las comunicaciones USB contamos con dos elementos: Uno de ellos actua como “host” o anfitrión y el otro como “device” o dispositivo.

La mayoría de las veces la función de anfitrión la suele llevar a cabo el ordenador y la de dispositivo por los periféricos que conectamos al mismo (impresoras, teclado, webcams,….).

El anfitrión debe saber que dispositivo se va a conectar a él y que características tendrá la comunicación a utilizar. El dispositivo en cambio solo deberá comunicarse con el anfitrión “a su manera”. Por lo que siempre será más complejo el trabajo del “host” o anfitrión que el del “device” o dispositivo conectado al mismo. Los Arduinos, como es de suponer, suelen actuar como dispositivos.

En nuestro caso y adelantándonos  a que los datos escogidos han de ser enviados a nuestro entorno de desarrollo en el que trabajaremos con los dispotivos de HTC vive necesitamos que nuestro microcontrolador pueda actuar como host o anfitrión para el resto de dispositivos.

Para esta labor tendremos que optar por un modelo de Arduino que cuente con un chip más “poderoso” que sea capaz de llevar a cabo ese roll de anfitrión para otros dispositivos (en nuestro caso el Tracker de HTC).

En el mercado existen varios modelos que cuentan  con posibilidad de actuar como anfitriones o “host”, entre los que están el arduino Yun, el arduino Zero, el aduino Tian y el Due. Habiéndonos decantado nosotros por este ultimo debido a unas especificaciones más acordes con las necesidades del proyecto. Cabe mencionar que también existen las llamadas USB host shields que serán un hardware adicional que nos permite añadir la posibilidad de actuar como anfitriones a modelos que no cuentan con ella.

Presentados los elementos el conexionado nos sería algo como lo siguiente:

//foto conexionado entre botonera grande, arduino y tracker.

En cuanto a la programación, tal y como lo hicimos en la entrada que estudiábamos el envió de datos vía puerto serie, vamos a ver por separado el codigo correspondiente a Arduino y el de Scripts de Unity.

ANTES DE EMPEZAR

Antes de meternos a programar nada tenemos que darnos cuenta que ahora contamos con un nueva variable que no conocemos en la ecuacion: el tracker.

No podemos programar nada sin comprender el funcionamiento del mismo y hay varias preguntas que debemos responder. ¿que posibilidades nos permite el tracker? ¿Cuantos datos podemos mandar? ¿en que formato? 

Todos estos datos los podemos encontrar en la guia para desarrolladores del producto que podeis encontrar en el siguiente link: https://dl.vive.com/Tracker/Guideline/HTC_Vive_Tracker_Developer_Guidelines_v1.3.pdf.

Este documento contiene información sobre cómo usar el HTC Vive Tracker para habilitar la posición seguimiento y transmisión de datos específicos (con o sin el sistema HTC Vive VR). En el tambien se explica que un accesorio adjunto a Vive Tracker (en nuestro cado el Arduino) puede:

  • Simular botones del controlador Vive a través los Pogo pins.
  • Enviar datos específicos a una PC a través de la interfaz USB de Vive Tracker.

En la documentación descrita en el parrafo anterior se presentan varios modos de uso del Tracker siendo el 5 caso de uso el realmente interesante para nuestro cometido.

En el diagrama anterior se puede ver como podemos utilizar tanto los POGO pins como el USB para “escribir” información en el tracker desde nuestro accesorio (Arduino en nuestro caso) y que este se encargara de mandar via Dongle los datos del accesorio junto los datos de Trackeo (posicionado) al PC. Las flecha de “Accessory data” hace intuir en la bidireccionalidad de los datos aunque en esta entrada solo abordaremos la transmisión de datos del accesorio al Tracker y no al revés.

 

En el caso de hacer uso de los POGO pins no tendremos más que utilizar microruptores para conectar a tierra los pines que queramos activar. Los pines cuentan en su interior con resistencias pull-up que nos facilitan mucho el trabajo.

En el caso del USB la cosa se complica un poco más pero no asustarse. En la misma documentación podemos encontrar un apartado dedicado a los “accessory makers” que nos sera de gran ayuda. 

En el diagrama se puede apreciar como el Tracker de Vive actuará tendrá el rol  de dispositivo en la comunicación USB para transferir datos hacia / desde el accesorio; que deberá actuar como “host” (pero esto no es nuevo para nosotros).

Vale, ahora ya sabemos que nos permite hace el Tracker, pero ¿cuantos datos podemos mandar y en que formato?

 

En el diagrama de flujo que podemos en contrar en la guia para desarrolladores se puede ver como contamos  con dos comanndos “setfeature” que podriamos traducir como elección de características. El primero (0xB3) lo utilizaremos para completar el byte de tipo Host de manera que el Tracker sepa a que tipo de dispositivo anfitrión a sido conectado. Si en el byte de Host Type escribimos un 2 estaremos diciendole que esta conectado a un telefono y si escribimos un 3 a un accesorio. Los byte de Charge Enable y OS Type por su parte estaran reservados a requerimiento del propio Tracker.

Una vez de que el Tracker ya sabe a que dispositivo esta conectado podemos continuar con nuestro trabajo de “escritura de datos” en el Tracker. Para ello en la documentación aparece una tabla que nos indica los bytes que tenemos disponibles para dicha tarea.

Viendo la anterior tabla hemos deducido, siempre con la prudencia que exige hacer este tipo de afirmaciones, que HTC no ha desarrollado un nuevo dispositivo de 0 para implementar el Tracker y que simplemente ha “reciclado” los mandos o controllers que vienen de serie con el Pack HTC Vive.

 

Como se puede apreciar los nombres de los botones del mando y los que encontramos dentro del byte Button de nuestro Tracker coinciden (Grip, Menu, pad,…). De la misma manera podemos deducir que los bytes destinados a guardar los datos del gatillo (Trigger) son el 6 y el 7; mientras que los destinado a guardar los valores del Pad (X e Y) son el 2, 3, 4, y 5.

ARDUINO

Antes de empezar dar gracias desde aqui al usuario matzman666 que ha realizado una modificación a la biblioteca de USBHost tanto para placas Arduino con arquitectura AVR(Uno, Nano, Leonardo) como ARM (due, Zero, Teensy), haciendola compatible con el Tracker de HTC; facilitándonos el trabajo.

La biblioteca USBHost es la que permite que una placa Arduino, en nuestro caso Due, aparezca como un host USB, permitiendole comunicarse con periféricos como mouse y teclados USB. Estudiemos entonces el añadido realizado por el usuario matzman666.

Link: https://github.com/matzman666/USBHost-samd

Si abrimos la librería nos encontraremos que el usuario a añadido el archivo ViveTrackerController.h dentro de la carpeta src de la libreria. En este archivo se encuentran la clase, las variables y los métodos necesarios para comunicarnos con el Tracker. Si tenéis curiosidad podéis abrir el archivo ViveTrackerController.h en cualquier editor de código y analizarla.

Si no queréis entrar ahí podéis abrir directamente el ejemplo de uso de la librería que podemos encontrar en la carpeta “Examples” de la misma, bajo el nombre “ViveTracker.ino”

En el código de ejemplo podemos ver como utilizamos el método setTrackerStatus para escribir los valores deseados en el Tracker. 

 

Como podemos apreciar la librería nos ha facilitado mucho el trabajo y ahora solo tenemos que preocuparnos de leer los datos de la botonera y darles un formato que nos permita meterlos en los bytes que nos permite el Tracker. 

En nuestro caso hemos utilizado los 2 bytes correspondientes al PadX para enviar nuestros datos. ¿Pero como encapsulamos 16 entradas digitales en 2 bytes? Nos hemos decidido por meter los valores leídos de la botonera en un array de booleanos, que luego convertiremos a entero mediante un pequeño método que hemos implementado. Si imprimimos el resultado por puerto serie tendríamos algo como lo siguiente.

En la captura podemos ver como el array que obtendremos contendrá los valores de los botones leidos: leemos 1s debido a que estamos utilizando las resistencias pull-up internas de nuestro Arduino por lo que leeremos 0 cuando se encuentren pulsados.

Tenemos que tener en cuenta que el los 2 bytes destinados a almacenar los valores de PadX pueden contener valores de -32768 a 32767. Es decir, que el MSB o bit de mayor de peso esta destinado al signo. Esto supone que para nuestra tarea solo nos son utiles 15 bits de esos 2 bytes.

Como anteriormente hemos comentado nuestra botonera genera 16 salidas digitales, por lo que esos 15 bits no son suficientes para almacenarlos. La solución que hemos adoptado ha sido enviar ese bit a traves del byte destinado a los botones.

 

UNITY

Lo primero a tener en cuenta a la hora de leer nuestro Tracker, es que el Tracker no es el unico elemento que envia datos a nuestras estaciones y Dongle, ya que tambien podremos acceder a los datos enviados por el HMD (Head Mounting Device) así como a los dos mandos incluidos en el Pack.

En la jerarquia SteamVR hay dos tipos de componentes principales. TrackedObjects y TrackedControllers. Los TrackerControllers nos servirán para acceder a las entradas básicas de nuestros mandos: básicamente leer cuando los botones del mando son pulsados. Los TrackedObjects por su parte están destinado a ser utilizado en cualquier objeto que sea Trackeado por las estaciones, incluido el HMD y el Tracker. De esa manera hemos usado el TrackedObject para obtener una representación entera de nuestro controlador que luego asignamos a SteamVR_Controller.Device para obtener algunos métodos específicos del controlador para nuestro uso. Esta forma ademas de permitirnos acceder a otros elementos que no son los controllers o mandos, nos permite permite tener más flexibilidad en términos de lo que realmente podemos obtener de nuestro elemento: leer los valores del Touchpad del mando por ejemplo.

Una vez de que ya tenemos nuestro Device asignado, ya podemos hacer uso de los métodos para acceder a los diferentes elementos (botones, pad, trigger,…) de nuestro dispositivo. Si abrimos la clase SteamVR_Controller podremos encontrarlos facilmente.

 

Tenemos que decir que en un principio resulta un poco difícil comprender como acceder a cada byte del campo de datos (0xB4), ya que la documentación no resulta demasiado clara. Si, como ya hemos comentado anteriormente, suponemos que los campos de datos del Tracker son los mismo que los de los Controllers (mandos) empezamos a ver la luz.

Cuando decidimos en nuestro código de Arduino que bytes íbamos a utilizar para mandar los valores recogidos por nuestra botonera, lo hicimos de la siguiente manera:

  • Utilizamos el byte para almacenar el PadX para mandar los primeros 15 botones
  • Utilizamos el byte destinado a enviar los valores de botón para el numero 16

Por lo tanto si ahora queremos acceder a ese valor de PadX, tendremos que usar el mismo método que usaríamos para tomar los valores del Pad Tactil de los Mandos (Controlers). Utilizamos una estructura de tipo Vector2 ya que esta nos permite representar tanto posiciones 2D como vectores.

En la siguiente instrucción podemos ver como leemos ese botón que nos hemos visto obligados a mandar a través del byte destinado a botones por falta de espacio en los bytes del PadX. ¿pero los eventos de que botón miramos?

Como podemos ver en la anterior instrucción nosotros escribimos directamente el valor booleano leido (0/1) en el campo button. Lo que significa que cuando nuestro microcontrolador lee una pulsación simplemente escribira un 1 en el byte destinado a botones.

Si miramos la tabla cuando el Tracker recibe un 0x01 (un 1 en hexadecimal) determina que el botón de Trigger a sido pulsado como podemos ver en la imagen inferior.

Si printeamos los valores recibidos en la consola de Unity, veriamos algo como lo siguiente.

Comos se puede apreciar poco tiene que ver el valor entero entre -32768 y 32767 que “escribiamos” en el Tracker al que recibimos. Esto se debe a que el Tracker mape los valores que nosotros introducimos en el campo PadX para que esten en el rango entre -1 y 1. Esto significa que nos tocara a nosotros hacer la operación inversa para obtener los valores originales.

Solo nos queda un ultimo trabajo: volver a convertir el int (0-32767) que acabos de conseguir al array de booleanos del que partíamos en Arduino. Para esa tarea utilizaremos el metodo ToString que nos permite establecer la base con la que se va a realizar la conversión. Luego dividiremos ese binaryString para guardar cada elemento en una posición del ArrayValorBotones.

Hecho esto ya tendremos los valores de los Botones leídos de nuestra botonera en nuestro entorno de desarrollo, fácilmente accesibles en el array que acabamos de crear.

DEL MANDO FÍSICO AL MANDO VIRTUAL

¿Y ahora que tengo los datos en Unity que? En nuestro caso y siguiendo con el objetivo principal del proyecto, hemos modelado el mando utilizado en el montaje descrito anteriormente y los hemos introducido en Unity. De esta manera podremos utilizar los datos que recibimos para animar el mando virtual y que actué como espejo del  mando real.

Como una imagen vale más que mil palabras, os dejamos un vídeo que da muestra de lo que se ha descrito en esta entrada. Os dejamos también el código tanto de la parte de Arduino como del Script de Unity con el objetivo de facilitar el trabajo de quien lo requiera.