Archivo por días: 13 julio, 2020

A ritmo de conga (6)

Actualizado: añadidos dos comandos extra.

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.

Ahora que ya tengo un servidor en marcha capaz de enviar comandos, llegó el momento de analizar todas las posibles opciones. Para ello puse otra vez el tcpdump a capturar y, desde la aplicación oficial, fui enviando todos los posibles comandos, anotando a la vez la hora a la que lo hacía para poder luego identificarlos. También modifiqué mi analizador de paquetes para que mostrase el instante de tiempo en que se emitía o recibía cada paquete.

La primera conclusión es que todos los comandos (a excepción, de momento, de los de mover la aspiradora manualmente, que en principio hay que enviarlos a través del puerto 8888 de la aspiradora; aún no probé qué pasa si lo envío por la conexión normal) se pueden ejecutar con este paquete (con un par de excepciones):

d1 00 00 00 | fa 00 c8 00 | 00 00 09 01 | 1a 27 00 00 | 00 00 00 00
{
  "cmd":0,
  "control":{
    "authCode":"yyyyyy",
    "deviceIp":"192.168.18.3",
    "devicePort":"8888",
    "targetId":"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
    "targetType":"3"
  },
  "seq":0,
  "value":{"transitCmd":"ID_COMANDO"}
}\n

Siendo ID_COMANDO un número que identifica la operación. En casi todos los casos, la aspiradora devolverá una respuesta, con la excepción de un par de ellos.

Los posibles comandos que ya tengo identificados son:

  • 98: pide una actualización del estado de la aspiradora. El aspirador no envía asentimiento. Sólo funciona mientras la aspiradora está en marcha.
  • 100: comienza a limpiar
  • 102: deja de limpiar
  • 104: vuelve a la base
  • 106: establece el modo de limpieza. Incluye el parámetro mode antes de transitCmd con uno de los siguientes valores:
    • 11: modo auto
    • 1: modo giro
    • 3: modo random
    • 4: modo edges
    • 6: modo area
    • 8: modo deep cleaning
    • 10: modo scrubbing
  • 110: establece la potencia del aspirador. Incluye el parámetro fan antes de transitCmd con uno de los siguientes valores:
    • 1: detenido
    • 2: normal
    • 3: turbo
    • 4: eco
  • 123: activa los sonidos de la aspiradora. Emitirá un pitido al ponerse en marcha, terminar de cargar, etc.
  • 125: desactiva los sonidos de la aspiradora.
  • 131: pide una actualización del mapa generado. Sólo funciona mientras la aspiradora está en marcha.
  • 145: establece la cantidad de agua en modo fregado. Incluye el parámetro waterTank depués de transitCmd con uno de los siguientes valores:
    • 255: no emitir agua
    • 60: flujo de agua bajo
    • 40: flujo de agua normal
    • 20: flujo de agua alto
  • 400: notifica que la aplicación se acaba de conectar. El aspirador no envía asentimiento.

A mayores hay al menos dos comandos más:

  • 139: sirve para poner la fecha y la hora y que debe ir acompañado del parámetro set_time, con formato AAAAMMDDhhmm000x, siendo AAAA en año (cuatro cifras), MM el mes, DD el día, hh la hora, mm el minuto (todos con dos cifras), y luego está esa x, que en algunos sitios es 0, en otros 1 y en otros 2, pero aún no encontré una regla para determinar su valor.
  • 127: sirve para actualizar el firmware, así que mi consejo: no se toca.

Cada vez que enviemos un comando, la aspiradora nos devolverá un asentimiento (con la excepción de los comandos 98 y 400). Éste será un paquete como éste:

db 01 00 00 | fa 00 00 00 | 01 00 00 00 | 1a 27 00 00 | 00 00 00 00
{
"version":"1.0",
"control":{
"targetId":"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
"targetType":"3",
"broadcast":"0"
},"value":{
"noteCmd":"102",
"workState":"5",
"workMode":"0",
"fan":"2",
"direction":"0",
"brush":"2",
"battery":"100",
"voice":"2",
"error":"0",
"standbyMode":"1",
"waterTank":"40",
"clearComponent":"0",
"waterMark":"0",
"version":"3.11.416(513)",
"attract":"0",
"deviceIp":"192.168.18.3",
"devicePort":"8888",
"cleanGoon":"2",
"extParam":"{\"cleanModule\":\"3\"}"
}
}

pero con el importante detalle de que el estado que devuelve es el anterior a la ejecución del comando. Esto es: si tenemos el sonido activado (que en el JSON con el estado de la aspiradora viene indicado con voice valiendo 2), y enviamos el comando 125, el estado que nos llegue en el ACK del comando tendrá todavía voice=2. Será justo después cuando la aspiradora nos enviará un paquete status, con el siguiente formato:

bc 01 00 00 | 18 00 00 00 | 01 00 00 00 | 1a 00 00 00 | 00 00 00 00
{
  "version":"1.0",
  "control":{
    "targetId":"0",
    "targetType":"6",
    "broadcast":"0"
  },"value":{
    "noteCmd":"102",
    "workState":"5",
    "workMode":"0",
    "fan":"2",
    "direction":"0",
    "brush":"2",
    "battery":"100",
    "voice":"1",
    "error":"0",
    "standbyMode":"1",
    "waterTank":"40",
    "clearComponent":"0",
    "waterMark":"0",
    "version":"3.11.416(513)",
    "attract":"0",
    "deviceIp":"192.168.18.3",
    "devicePort":"8888",
    "cleanGoon":"2",
    "extParam":"{\"cleanModule\":\"3\"}"
  }
}

donde esta vez sí vemos que voice vale 1. Este paquete debemos asentirlo con un ACK como éste, con el mismo número de secuencia (el cuarto entero de los cinco que forman la cabecera):

3c 00 00 00 | 19 00 c8 00 | 01 00 00 00 | 1a 00 00 00 | 01 00 00 00
{
  "msg":"OK",
  "result":0,
  "version":"1.0"
}\n

En el JSON del estado de la aspiradora tenemos los siguientes campos interesantes:

  • workstate: indica el modo actual en el que está la aspiradora, que puede ser:
    • 1: limpiando
    • 2: parada
    • 4: regresando a la base
    • 5: cargando la batería
    • 6: batería cargada
  • battery: un valor de 0 a 100 indicando el nivel de carga de la batería
  • voice: si vale 1, el pitido de aviso está desactivado; si vale 2, está activado

Parte 7