{"id":2513,"date":"2020-07-19T09:59:03","date_gmt":"2020-07-19T09:59:03","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=2513"},"modified":"2020-10-21T12:14:44","modified_gmt":"2020-10-21T12:14:44","slug":"a-ritmo-de-conga-y-7","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=2513","title":{"rendered":"A ritmo de conga (7)"},"content":{"rendered":"\n<p><strong>DISCLAIMER:<\/strong> no ser\u00e9 responsable si alguien decide seguir mis pasos y se carga su aspiradora. En principio todo lo que cuento deber\u00eda ser seguro, pero por motivos obvios no me puedo responsabilizar de lo que hagan otras personas, s\u00f3lo de lo que haga yo.<\/p>\n\n\n\n<p>En la anterior entrada par\u00e9 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\u00edas encontr\u00e9 un <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/felix-engelmann\/robotbona\" target=\"_blank\">repositorio de Git donde Felix Engelmann explica ese formato para la aspiradora Proscenic 790T<\/a>, la cual parece ser la misma que la Conga 1490, y aunque pasa muy por encima del protocolo en general, s\u00ed explica en detalle c\u00f3mo funcionan los mapas, por lo que me ahorr\u00e9 el trabajo de descubrirlo por mi cuenta. Paso a limitarme a describir con mis palabras lo que explica ah\u00ed.<\/p>\n\n\n\n<figure class=\"wp-block-video aligncenter meme\"><video height=\"238\" style=\"aspect-ratio: 320 \/ 238;\" width=\"320\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/relax.mp4\"><\/video><\/figure>\n\n\n\n<p>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\u00f3lo si est\u00e1 aspirando), nos llegar\u00e1 un mensaje de la aspiradora con este formato:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted mycode\">{\n  \"version\": \"1.0\",\n  \"control\": {\n    \"targetId\": \"0\",\n    \"targetType\": \"6\",\n    \"broadcast\": \"0\"\n  },\n  \"value\": {\n    \"noteCmd\": \"101\",\n    \"clearArea\": \"0\",\n    \"clearTime\": \"10\",\n    \"clearSign\": \"2020-06-24-01-31-41-2\",\n    \"clearModule\": \"11\",\n    \"isFinish\": \"1\",\n    \"chargerPos\": \"8,12\",\n    \"map\": \"AAAAAAAAZABk0vwAaoDXAGpA1wBqgNcAqNL8AA==\",\n    \"track\": \"AQAEADIxMzExMTEy\"\n  }\n}<\/pre>\n\n\n\n<p>Aqu\u00ed hay tres elementos que nos interesan: los campos <em>chargerPos<\/em>, <em>map<\/em> y <em>track<\/em>. Para entenderlos hay que saber primero que la aspiradora genera un mapa interno en forma de una cuadr\u00edcula, donde cada cuadrado que la compone puede estar en uno de varios estados (<em>zona sin explorar<\/em>, <em>zona libre<\/em> y <em>obst\u00e1culo<\/em>).<\/p>\n\n\n\n<p>Tras hacer algunos c\u00e1lculos y ver c\u00f3mo trabaja la aspiradora, parece que cada elemento de la cuadr\u00edcula es un cuadrado de unos 20cm en el mundo real<em>(tm)<\/em>. Esto es consistente con el modo <em>area<\/em>, en el que la aspiradora va limpiando cuadrados de 2&#215;2 metros (o sea, bloques de 10&#215;10 cuadrados de la cuadr\u00edcula). Y como la aspiradora tiene un di\u00e1metro de 32cm, vemos que hay cierto solape, lo cual es perfectamente l\u00f3gico si queremos garantizar que aspire todo el polvo y suciedad. Por defecto, la cuadr\u00edcula que env\u00eda la aspiradora es de 100&#215;100 elementos, lo que nos da un tama\u00f1o m\u00e1ximo de 20&#215;20 metros para la casa, aunque tambi\u00e9n es cierto que el tama\u00f1o viene en la cabecera del bloque <em>map<\/em>, por lo que supongo que en casas grandes lo ajustar\u00e1 din\u00e1micamente.<\/p>\n\n\n\n<figure class=\"wp-block-video aligncenter meme\"><video height=\"124\" style=\"aspect-ratio: 208 \/ 124;\" width=\"208\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/freddie_vacuum.mp4\"><\/video><\/figure>\n\n\n\n<p>Sabiendo esto podemos empezar a analizar los campos. El m\u00e1s sencillo es <em>chargerPos<\/em>. \u00c9ste contiene las coordenadas de la cuadr\u00edcula del mapa en donde est\u00e1 situado el cargador, lo que permite a la aspiradora volver hasta \u00e9l para recargar la bater\u00eda.<\/p>\n\n\n\n<p>El siguiente en complejidad es <em>map<\/em>: es f\u00e1cil darse cuenta de que es una ristra de bytes codificado con <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Base64\" target=\"_blank\">base64<\/a>, por lo que lo primero que hay que hacer es convertirlo a bytes. En Javascript se puede hacer con <a rel=\"noreferrer noopener\" href=\"https:\/\/www.w3schools.com\/jsref\/met_win_atob.asp\" target=\"_blank\">atob<\/a>, y en Python con el m\u00f3dulo <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.python.org\/3\/library\/base64.html\" target=\"_blank\">base64<\/a>. Una vez hecho esto, tiramos los primeros 4 bytes (que no sabemos qu\u00e9 significan), y luego cada par de bytes son las coordenadas <em>x<\/em> e <em>y<\/em> del recorrido de la aspiradora. As\u00ed, el primer par de bytes son las coordenadas del mapa en donde empez\u00f3 a limpiar, el siguiente par es la siguiente cuadr\u00edcula a la que se ha movido, etc. Dado que la cuadr\u00edcula, por defecto, es de 100&#215;100, parece que no tendremos que preocuparnos de si son n\u00fameros en <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two%27s_complement\" target=\"_blank\">complemento a 2<\/a> (desde -128 hasta 127) o <em>normales<\/em> (desde 0 hasta 255).<\/p>\n\n\n\n<figure class=\"wp-block-video aligncenter meme\"><video height=\"130\" style=\"aspect-ratio: 232 \/ 130;\" width=\"232\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/calculadora.mp4\"><\/video><\/figure>\n\n\n\n<p>Con este campo podremos saber la ruta que ha ido siguiendo la aspiradora por la cuadr\u00edcula.<\/p>\n\n\n\n<p>El tercer campo es el m\u00e1s complejo, pues est\u00e1 comprimido utilizando una t\u00e9cnica <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Run-length_encoding\" target=\"_blank\">Run-Length Encoding<\/a>, que, afortunadamente, es relativamente sencilla. Para ello tenemos que fijarnos en los dos primeros bits de cada byte. Si ambos est\u00e1n a 1, entonces es un contador de repeticiones y el n\u00famero de veces que se repetir\u00e1 el siguiente byte viene indicado por los seis bits inferiores. Ojo, porque si el siguiente byte tambi\u00e9n tiene ambos bits superiores a 1 entonces tambi\u00e9n se debe tener en cuenta para el n\u00famero de repeticiones, tomando los seis bits del primer byte y movi\u00e9ndolos seis posiciones a la izquierda, para luego anexionar los seis bits del segundo byte. Y as\u00ed sucesivamente, anexionando tantos bytes seguidos como haya con los dos bits superiores a 1. As\u00ed, si tenemos los bytes 0xC2 0xDA 0xAA, nos est\u00e1 diciendo que tenemos que repetir el byte 0xAA un total de 0x21A veces (0xC2 &amp; 0x3F = 0x02; 0xDA &amp; 0x3F = 1A).<\/p>\n\n\n\n<figure class=\"wp-block-video aligncenter meme\"><video height=\"206\" style=\"aspect-ratio: 288 \/ 206;\" width=\"288\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/calculating.mp4\"><\/video><\/figure>\n\n\n\n<p>\u00bfY qu\u00e9 pasa con los bytes que no tienen sus dos bits superiores a uno? Esos contienen el estado de cuatro cuadrados consecutivos de la cuadr\u00edcula del mapa. El formato es el siguiente:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>00: zona sin explorar<\/li><li>01: zona explorada libre (o sea, <em>suelo<\/em>)<\/li><li>10: obst\u00e1culo (una pared, un mueble, una pata de una silla, un programador tocando las narices&#8230;)<\/li><\/ul>\n\n\n\n<p>As\u00ed, un byte 0x16 ser\u00eda 00010110 en binario, lo que se traduce en cuatro elementos de la cuadr\u00edcula: 00  01  01  10. Por tanto aqu\u00ed tendr\u00edamos un bloque sin explorar, los dos siguientes a su derecha ser\u00edan bloques explorados libres (suelo), y finalmente una pared.<\/p>\n\n\n\n<p>Por otro lado, la secuencia 0xC8 0x55 se descomprimir\u00eda como 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55, u ocho veces 0x55; o sea 4 x 8 = 32 bloques de suelo libre.<\/p>\n\n\n\n<p>Entonces, si tenemos estas dos secuencias (<em>mapa<\/em> arriba y <em>track<\/em> abajo):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted mycode\">AAAAAAAAZABk0fIAAqnWAAqqqdYABqqp1QABJqqp1gDCqqnVAAEqqqnT0wA=<br>AQIKADIxOjE6MDMwMy86LzouNC40MTAx<\/pre>\n\n\n\n<p>Tras procesarlas tendremos esto:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1.png\" rel=\"lightbox-0\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"548\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1-1024x548.png\" alt=\"\" class=\"wp-image-2522\" srcset=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1-1024x548.png 1024w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1-300x161.png 300w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1-768x411.png 768w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1-1536x822.png 1536w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/mapa-1.png 1538w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>donde vemos que el cargador est\u00e1 en el punto verde, vemos tambi\u00e9n largas zonas exploradas que est\u00e1n libres (puntos rojos), paredes que delimitan la habitaci\u00f3n (puntos azules), y el recorrido que ha seguido la aspiradora (l\u00ednea negra).<\/p>\n\n\n\n<p>Y con esto se ha terminado el an\u00e1lisis 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.<\/p>\n\n\n\n<p><a href=\"https:\/\/blog.rastersoft.com\/?p=2541\">Parte 8<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>DISCLAIMER: no ser\u00e9 responsable si alguien decide seguir mis pasos y se carga su aspiradora. En principio todo lo que cuento deber\u00eda ser seguro, pero por motivos obvios no me puedo responsabilizar de lo que hagan otras personas, s\u00f3lo de lo que haga yo. En la anterior entrada par\u00e9 tras comentar los distintos comandos de &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=2513\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">A ritmo de conga (7)<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,16,5,7],"tags":[],"class_list":["post-2513","post","type-post","status-publish","format-standard","hentry","category-cacharreo","category-opendonita","category-programacion","category-tutoriales"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2513","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2513"}],"version-history":[{"count":16,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2513\/revisions"}],"predecessor-version":[{"id":2599,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2513\/revisions\/2599"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2513"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2513"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2513"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}