Desmontamos el protocolo de hervidor de agua Redmond G200S y lo conectamos a HomeAssistant

Entrada


Ya ha habido un artículo sobre Gicktime dedicado a analizar el protocolo de la tetera Redmond SkyKettle. Sin embargo, allí hablaron sobre el modelo RK-M171S, aquí hablaremos sobre un G200S más funcional. En este modelo, el protocolo de interacción ha cambiado, por lo que el enfoque del autor del artículo anterior ya no funciona, y han aparecido funciones adicionales de la luz nocturna y la indicación de la temperatura actual en color.

En este artículo, presentaré los resultados de un análisis de protocolo con ejemplos de código de Python (si alguien quiere desarrollar su módulo / aplicación para controlar la tetera). También al final del artículo hay un enlace a un módulo listo para conectar una tetera a HomeAssistant (esta es mi primera experiencia en escribir en Python después de tomar un curso en línea, por lo que este módulo puede e incluso debe mejorarse).

Todos los que estén interesados, bienvenidos a cat.

Problemas y tareas


Esta tetera tiene una gran desventaja (excepto las indicadas por el autor del primer artículo): tan pronto como la tetera se retira del soporte, la hora actual se restablece y, como resultado, el programa no puede usarse para hervir la tetera. Según las ideas de los autores de esta creación, cada vez que devuelva el hervidor al soporte, debe iniciar su aplicación patentada y sincronizar el hervidor con un teléfono inteligente. Entonces, en lugar de facilitar las tareas de rutina, la tecnología "inteligente" nos capacita para realizar acciones adicionales. Pero todo eso cambió cuando HomeAssistant apareció en la casa. Entonces decidí entender el protocolo.

Las herramientas


Honestamente intenté descompilar y analizar la aplicación original, pero fallé. Las herramientas que utilicé no me permitieron comprender la lógica de la tetera. Todos los procedimientos y funciones se obtuvieron por "curvas", sin nombre (por tipo a, b, c, etc.). Quizás no tengo suficiente experiencia y habilidad. Al final, seguí el mismo camino que el autor del artículo anterior. La única diferencia significativa es que utilicé el modo interactivo de la utilidad gatttool. La ventaja es que este modo elimina todo tipo de "razas", sobre las cuales escribió el autor del primer artículo.

Como HomeAssistant está escrito en python, escribiremos todos los comandos adicionales en él. Para utilizar el modo operativo interactivo gatttool en python, la biblioteca pexpect nos ayudará, lo que le permite generar la esencia de las aplicaciones de terceros y monitorear su salida (famosamente doblada).

Practica


Enviaré nuevamente el primer artículo sobre la descripción general del protocolo de intercambio al autor del artículo, por lo que sin más dilación, procederemos a los comandos de control.

  1. Instalación y desconexión.

    Establecer una conexión:

    child = pexpect.spawn("gatttool -I -t random -b " + mac, ignore_sighup=False) child.expect(r'\[LE\]>', timeout=3) child.sendline("connect") child.expect(r'Connection successful.*\[LE\]>', timeout=3) 

    Aquí mac es la dirección de amapola de la tetera.

    Rompemos la conexión:

     child.sendline("exit") 
  2. Suscríbase a las notificaciones

    Después de establecer la conexión, antes que nada, debemos suscribirnos para recibir notificaciones del hervidor. Sin esto, la tetera percibirá los comandos, sin embargo, no podrá respondernos nada excepto el texto "Con éxito".

     child.sendline("char-write-cmd 0x000c 0100") child.expect(r'\[LE\]>') 
  3. Iniciar sesión

     child.sendline("char-write-req 0x000e 55" + iter + "ff" + key + "aa") child.expect("value: ") child.expect("\r\n") connectedStr = child.before[0:].decode("utf-8") answer = connectedStr.split()[3] # parse: 00 - no 01 - yes child.expect(r'\[LE\]>') 

    De aquí en adelante, iter es una variable hexadecimal iterativa entera de 0 a 64 (de 0 a 100 en el sistema decimal). Después de cada comando (exitoso y no exitoso), esta variable se debe aumentar en 1; cuando alcanza 64, se restablece nuevamente a 0; clave: clave de autorización hexadecimal de 8 bytes (por ejemplo: ffffffffffffffff).

    Ejemplo de respuesta:
    valor: 55 00 ff 01 aa
    El cuarto byte (01) significa que el hervidor lo ha autorizado; de lo contrario, la respuesta será 00.
  4. Algo de magia callejera
    Después de la autorización, siempre se envía una solicitud "mágica", cuya esencia no está clara para mí. Existe la teoría de que es necesario "mantener" el estado conectado. Supuestamente, si no lo envía, la desconexión se produce en un segundo y debe comenzar de nuevo. Si lo envía, el tiempo de espera aumenta significativamente, llegando a aproximadamente una docena de segundos. Confirme esto de manera confiable, no pude.

     child.sendline("char-write-req 0x000e 55" + iter + "01aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    Ejemplo de respuesta:
    valor: 55 01 01 02 1d aa

    En todos mis experimentos, la respuesta siempre ha sido esta.

    UPD: en los comentarios sugirieron que no era mágico en absoluto, sino simplemente solicitar la versión del software, y la respuesta en consecuencia contenía esta versión. Por lo tanto, esta solicitud generalmente se puede eliminar como innecesaria.
  5. Sincronización
    Un comando que sincroniza la hora en una tetera con un reloj del servidor. Ella tiene un efecto más. En el hervidor, es posible mostrar la temperatura actual en modo inactivo parpadeando un LED de un determinado color. Esta función solo funciona después de la sincronización. Para una descripción de la función en sí, vea el párrafo 11.

     child.sendline("char-write-req 0x000e 55" + iter + "6e" + timeNow + tmz + "0000aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    Aquí tmz es la zona horaria en formato hexadecimal inverso (por ejemplo, traduzca la zona horaria +3 en segundos, luego en formato hexadecimal y obtenga hexadecimal (3 * 60 * 60) = 2a30, divida en pares y genere 302a en orden inverso). No sé qué hacer con las zonas horarias negativas, no lo he probado, pero existe la sospecha de que el siguiente byte tmz es responsable de esto. Aquí timeNow es el tiempo actual unixtime en formato hexadecimal inverso. El algoritmo es el mismo: obtenemos el tiempo actual en segundos, lo traducimos a HEX, lo dividimos en pares y lo enviamos en una fila en el orden inverso.

    Ejemplo de respuesta:
    valor: 55 02 6e 00 aa
    En todos mis experimentos, la respuesta siempre ha sido esta.
  6. Estadísticas
    El hervidor tiene un medidor de electricidad consumida, tiempo total de operación y número de arranques. Si alguien no necesita estos datos, puede omitir este elemento de forma segura.

     child.sendline("char-write-req 0x000e 55" + iter + "4700aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") Watts = hexToDec(str(statusStr.split()[11] + statusStr.split()[10] + statusStr.split()[9])) alltime = round(self._Watts/2200, 1) child.expect(r'\[LE\]>') child.sendline("char-write-req 0x000e 55" + iter + "5000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") times = hexToDec(str(statusStr.split()[7] + statusStr.split()[6])) child.expect(r'\[LE\]>') 

    Vatios: devuelve la energía consumida en Wh * h, todo el tiempo, las horas de funcionamiento de la tetera, el tiempo, el número de arranques de la tetera. hexToDec: una función para convertir a formato decimal.
  7. Leer el modo de operación actual

     child.sendline("char-write-req 0x000e 55" + iter + "06aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split() status = str(answer[11]) temp = hexToDec(str(answer[8])) mode = str(answer[3]) 

    Ejemplo de respuesta:
    valor: 55 04 06 00 00 00 00 01 2a 1e 00 00 00 00 00 00 80 00 00 aa
    El cuarto byte es el modo de funcionamiento (modo): 00 - ebullición, 01 - calentamiento a temperatura, 03 - luz nocturna. El sexto byte es la temperatura hexadecimal a la que es necesario calentar en el modo de calentamiento, en modo de ebullición es 00. El noveno byte es la temperatura de la corriente hexadecimal del agua (2a = 42 Celsius). El duodécimo byte es el estado de la tetera: 00 - apagado, 02 - encendido. El decimoséptimo byte es la duración del hervidor después de alcanzar la temperatura deseada, por defecto es 80 en hexadecimal (aparentemente, estos son algún tipo de unidades relativas, ciertamente no segundos).
  8. Grabar el modo de operación actual

     child.sendline("char-write-req 0x000e 55" + iter + "05" + mode + "00" + temp + "00000000000000000000" + howMuchBoil + "0000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    Modo de parámetro: 00 - ebullición, 01 - calentamiento a temperatura, 03 - luz nocturna. El parámetro temp es la temperatura hexadecimal a la que es necesario calentar en el modo "calefacción", en modo de ebullición es 00. El parámetro howMuchBoil es la duración del hervidor después de alcanzar la temperatura deseada, el valor predeterminado es 80 en hexadecimal (aparentemente, estas son algunas unidades relativas , ciertamente no segundos).

    Ejemplo de respuesta:
    valor: 55 05 05 01 aa
    El cuarto byte de la respuesta indica el éxito de la configuración: 01 - exitoso, 00 - no exitoso.
  9. Ejecutar el modo de operación actual

     child.sendline("char-write-req 0x000e 55" + iter + "03aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    Ejemplo de respuesta:
    valor: 55 06 03 01 aa
    El cuarto byte de la respuesta indica el éxito de la inclusión: 01 - exitoso, 00 - no exitoso.
  10. Parar el modo de operación actual

     child.sendline("char-write-req 0x000e 55" + iter + "04aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    Ejemplo de respuesta:
    valor: 55 07 04 01 aa
    El cuarto byte de la respuesta indica el éxito del cierre: 01 - con éxito, 00 - sin éxito.
  11. Mostrar la temperatura actual en color en inactivo

     child.sendline("char-write-req 0x000e 55" + iter + "37c8c8" + onoff + "aa") # 00 - off, 01 - on child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    El parámetro onoff es 01 para habilitar la función o 00 para deshabilitar la función.

    Ejemplo de respuesta:
    valor: 55 08 37 00 aa
    En todos mis experimentos, la respuesta siempre ha sido esta.
  12. Grabe una paleta de colores de varios modos de operación
    Se establece una paleta de correspondencia entre el color del LED y la temperatura en el modo de visualización de la temperatura actual y los modos de calefacción y ebullición, así como la paleta de colores en el modo de luz nocturna.

     child.sendline("char-write-req 0x000e 55" + iter + "32" + boilOrLight + scale_from + rand + rgb1 + scale_mid + rand + rgb_mid + scale_to + rand + rgb2 + "aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    El parámetro boilOrLight es 00 si configuramos el modo de visualización de la temperatura actual o 01 si configuramos el modo nocturno. El parámetro scale_from indica el comienzo del rango de cambio de color y es igual a 00 en el modo de luz nocturna y 28 en el modo de visualización de temperatura actual (28 es 40 en formato decimal y es a partir de esta temperatura que comenzará un cambio de color suave). El parámetro scale_mid es el medio del rango y es 32 en el modo de luz nocturna y 46 en el modo de visualización de temperatura actual. El parámetro scale_to indica el final del rango de color y es 64 en ambos modos. El parámetro rgb1 es el color hexadecimal del comienzo de la paleta. El parámetro rgb_mid es el color hexadecimal del centro de la paleta (lo calculo como el centro entre los extremos izquierdo y derecho, pero en teoría puede especificar cualquier color, esto solo afectará la belleza y la suavidad del cambio de color). El parámetro rgb2 es el color hexadecimal del final de la paleta. El parámetro rand es un parámetro determinado, cuyo valor no entendí exactamente, tal vez de alguna manera relacionado con el brillo del color (ejemplos de valores: e5, cc).

    Ejemplo de respuesta:
    valor: 55 09 32 00 aa
    En todos mis experimentos, la respuesta siempre ha sido esta.
  13. Lea la paleta de colores de varios modos de operación.

     child.sendline("char-write-req 0x000e 55" + iter + "33" + boilOrLight + "aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") child.expect(r'\[LE\]>') 

    El parámetro boilOrLight puede ser 00, si configuramos el modo de visualización de la temperatura actual o 01, si configuramos el modo nocturno.

    Ejemplo de respuesta:
    valor: 55 10 33 01 00 7f 00 00 ff 32 7f 00 ff 00 64 7f ff 00 00 aa
    Aquí, los bytes sexto, undécimo y decimosexto (7f) son el parámetro rand del elemento 12. El quinto byte es scale_from, el décimo byte es scale_mid, el decimoquinto byte es scale_to. Séptimo + octavo + noveno bytes son rgb_from. El duodécimo + decimotercero + decimocuarto bytes son rgb_mid. Decimoséptimo + decimoctavo + decimonoveno bytes - rgb_to.

Conclusión


Si gatttool no quiere conectarse a la tetera (esto es posible la primera vez que se conecta a dispositivos desconocidos), intente buscar la tetera usando el sistema operativo antes de conectar el módulo:

 sudo hciconfig device reset sudo timeout 1 hcitool lescan 


dispositivo: identificación de su dispositivo Bluetooth (por ejemplo, hci0). Asegúrese de que la dirección de amapola de su hervidor de agua esté en la lista de dispositivos encontrados. Después de eso:

 sudo hcitool lewladd mac sudo hcitool lerladd mac 


mac: la dirección de amapola de tu tetera

UPD6 : Mejorado significativamente el módulo de hervidor de agua:
1. Transferido el módulo de la plataforma al modo de integración
2. Después de agregar, automáticamente tendrá 3 elementos: un calentador de agua (temperatura actual, temperatura objetivo, ebullición y calefacción), un sensor (tiempo de sincronización, energía gastada, horas de operación, número de arranques) y luz (se puede usar como lámpara de noche y elegir cualquier color luz de fondo)
3. El módulo ya está disponible en GitHub .
4. El módulo admite la instalación a través de HACS
5. Ejemplo de configuración:
 r4s_kettler: device: 'hci0' mac: 'FF:FF:FF:FF:FF:FF' password: 'ffffffffffffffff' 


Capturas de pantalla de la nueva versión.
imagen
imagen
imagen
imagen


UPD7 : información irrelevante eliminada

Source: https://habr.com/ru/post/es412583/


All Articles