A ritmo de conga (7)

DISCLAIMER: no seré responsable si alguien decide seguir mis pasos y se carga su aspiradora. En principio todo lo que cuento debería ser seguro, pero por motivos obvios no me puedo responsabilizar de lo que hagan otras personas, sólo de lo que haga yo.

En la anterior entrada paré tras comentar los distintos comandos de que dispone la aspiradora y del formato del mensaje de estado. Ahora llega el momento de explicar el formato de los mapas. Claro que debo ser sincero: hace unos días encontré un repositorio de Git donde Felix Engelmann explica ese formato para la aspiradora Proscenic 790T, la cual parece ser la misma que la Conga 1490, y aunque pasa muy por encima del protocolo en general, sí explica en detalle cómo funcionan los mapas, por lo que me ahorré el trabajo de descubrirlo por mi cuenta. Paso a limitarme a describir con mis palabras lo que explica ahí.

Cada vez que enviamos un comando 131 a la aspiradora, o bien cada 30 segundos aproximadamente si no hacemos nada (pero en ambos casos sólo si está aspirando), nos llegará un mensaje de la aspiradora con este formato:

{
  "version": "1.0",
  "control": {
    "targetId": "0",
    "targetType": "6",
    "broadcast": "0"
  },
  "value": {
    "noteCmd": "101",
    "clearArea": "0",
    "clearTime": "10",
    "clearSign": "2020-06-24-01-31-41-2",
    "clearModule": "11",
    "isFinish": "1",
    "chargerPos": "8,12",
    "map": "AAAAAAAAZABk0vwAaoDXAGpA1wBqgNcAqNL8AA==",
    "track": "AQAEADIxMzExMTEy"
  }
}

Aquí hay tres elementos que nos interesan: los campos chargerPos, map y track. Para entenderlos hay que saber primero que la aspiradora genera un mapa interno en forma de una cuadrícula, donde cada cuadrado que la compone puede estar en uno de varios estados (zona sin explorar, zona libre y obstáculo).

Tras hacer algunos cálculos y ver cómo trabaja la aspiradora, parece que cada elemento de la cuadrícula es un cuadrado de unos 20cm en el mundo real(tm). Esto es consistente con el modo area, en el que la aspiradora va limpiando cuadrados de 2×2 metros (o sea, bloques de 10×10 cuadrados de la cuadrícula). Y como la aspiradora tiene un diámetro de 32cm, vemos que hay cierto solape, lo cual es perfectamente lógico si queremos garantizar que aspire todo el polvo y suciedad. Por defecto, la cuadrícula que envía la aspiradora es de 100×100 elementos, lo que nos da un tamaño máximo de 20×20 metros para la casa, aunque también es cierto que el tamaño viene en la cabecera del bloque map, por lo que supongo que en casas grandes lo ajustará dinámicamente.

Sabiendo esto podemos empezar a analizar los campos. El más sencillo es chargerPos. Éste contiene las coordenadas de la cuadrícula del mapa en donde está situado el cargador, lo que permite a la aspiradora volver hasta él para recargar la batería.

El siguiente en complejidad es map: es fácil darse cuenta de que es una ristra de bytes codificado con base64, por lo que lo primero que hay que hacer es convertirlo a bytes. En Javascript se puede hacer con atob, y en Python con el módulo base64. Una vez hecho esto, tiramos los primeros 4 bytes (que no sabemos qué significan), y luego cada par de bytes son las coordenadas x e y del recorrido de la aspiradora. Así, el primer par de bytes son las coordenadas del mapa en donde empezó a limpiar, el siguiente par es la siguiente cuadrícula a la que se ha movido, etc. Dado que la cuadrícula, por defecto, es de 100×100, parece que no tendremos que preocuparnos de si son números en complemento a 2 (desde -128 hasta 127) o normales (desde 0 hasta 255).

Con este campo podremos saber la ruta que ha ido siguiendo la aspiradora por la cuadrícula.

El tercer campo es el más complejo, pues está comprimido utilizando una técnica Run-Length Encoding, que, afortunadamente, es relativamente sencilla. Para ello tenemos que fijarnos en los dos primeros bits de cada byte. Si ambos están a 1, entonces es un contador de repeticiones y el número de veces que se repetirá el siguiente byte viene indicado por los seis bits inferiores. Ojo, porque si el siguiente byte también tiene ambos bits superiores a 1 entonces también se debe tener en cuenta para el número de repeticiones, tomando los seis bits del primer byte y moviéndolos seis posiciones a la izquierda, para luego anexionar los seis bits del segundo byte. Y así sucesivamente, anexionando tantos bytes seguidos como haya con los dos bits superiores a 1. Así, si tenemos los bytes 0xC2 0xDA 0xAA, nos está diciendo que tenemos que repetir el byte 0xAA un total de 0x21A veces (0xC2 & 0x3F = 0x02; 0xDA & 0x3F = 1A).

¿Y qué pasa con los bytes que no tienen sus dos bits superiores a uno? Esos contienen el estado de cuatro cuadrados consecutivos de la cuadrícula del mapa. El formato es el siguiente:

  • 00: zona sin explorar
  • 01: zona explorada libre (o sea, suelo)
  • 10: obstáculo (una pared, un mueble, una pata de una silla, un programador tocando las narices…)

Así, un byte 0x16 sería 00010110 en binario, lo que se traduce en cuatro elementos de la cuadrícula: 00 01 01 10. Por tanto aquí tendríamos un bloque sin explorar, los dos siguientes a su derecha serían bloques explorados libres (suelo), y finalmente una pared.

Por otro lado, la secuencia 0xC8 0x55 se descomprimiría como 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55, u ocho veces 0x55; o sea 4 x 8 = 32 bloques de suelo libre.

Entonces, si tenemos estas dos secuencias (mapa arriba y track abajo):

AAAAAAAAZABk0fIAAqnWAAqqqdYABqqp1QABJqqp1gDCqqnVAAEqqqnT0wA=
AQIKADIxOjE6MDMwMy86LzouNC40MTAx

Tras procesarlas tendremos esto:

donde vemos que el cargador está en el punto verde, vemos también largas zonas exploradas que están libres (puntos rojos), paredes que delimitan la habitación (puntos azules), y el recorrido que ha seguido la aspiradora (línea negra).

Y con esto se ha terminado el análisis del protocolo, y ya tenemos todo lo suficiente para hacer nuestro propio servidor y app, y es justo lo que he hecho. Pero mejor eso en la siguiente entrada.

Parte 8

CC BY-SA 4.0 A ritmo de conga (7) por A cuadros está licenciado bajo una Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional.

Deja una respuesta

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