
En este artículo, profundizaremos en la personalización de Home Assistant, creando nuestros propios scripts para recopilar datos de sensores remotos y otros dispositivos de control. También veremos varias formas de comunicarnos con los sensores remotos.
Obtener datos de temperatura en remoto
Supongamos que tiene este problema: tienes varios sensores de temperatura, como el DS1820 en tu casa, conectados a varios ODROID y deseas enviar los datos a Home Assistant, que se ejecuta en un único dispositivo. Necesitas decidirte por un protocolo de transporte de datos y escribir un script para recopilar las lecturas de temperatura y pasarlas a Home Assistant.
Vamos a analizar algunas técnicas:
- Sondeo por HTTP
- Conexión a través de la API de Home Assistant
- Conexión a través de MQTT
Sondeo por HTTP
Si estás acostumbrado a desarrollar web, probablemente habrás usado la denominada CGI (Interfaz Común de Comunicaciones), la forma más antigua de generar contenido dinámico utilizando un servidor web (http://bit.ly/2jNVkjT). Básicamente, carga un script en el servidor, independientemente del lenguaje, que es requerido por el servidor web y que a su vez sirve el resultado del script al cliente. Obviamente, primero necesitas instalar un servidor HTTP en tu host remoto y activar el soporte CGI. Usaremos Apache 2.4:
$ sudo apt-get install apache2 $ sudo a2enmod cgiLa configuración por defecto asigna /cgi-bin/URL a /usr/lib/cgi-bin en tu sistema de archivos. Cualquier script ejecutable que coloques en esta ubicación puede ser requerido por el servidor web. Vamos a suponer que puedes obtener los datos de temperatura del host remoto con estos comandos shell:
$ cat /sys/devices/w1_bus_master1/28-05168661eaff/w1_slave c6 01 4b 46 7f ff 0c 10 bd : crc=bd YES c6 01 4b 46 7f ff 0c 10 bd t=28375En el resultado anterior, la primera línea valida la lectura del valor (si coincide con el CRC) y la segunda línea devuelve el valor en mili-celsius. Vamos a crear dos scripts (no olvide marcarlos como ejecutables) para ilustrar el código en dos lenguajes diferentes: BASH y Python. Los archivos se almacenarán en /usr/lib/cgi-bin/temperature.sh y /usr/lib/cgi-bin/temperature.py.
#!/bin/bash filename='/sys/devices/w1_bus_master1/28-05168661eaff/w1_slave' valid=0 echo "Content-Type: text/plain" echo # read line by line, parse each line while read -r line do if [[ $line =~ crc=.*YES ]]; then # the CRC is valid. Continue processing valid=1 continue fi if [[ "$valid" == "1" ]] && [[ $line =~ t=[0-9]+ ]]; then # extract the temperature value rawtemperature=`echo "$line" | cut -d "=" -f 2` # convert to degrees celsius and keep 1 digit of accuracy echo "scale=1;$rawtemperature/1000" | bc fi #read line by line from $filename done < "$filename"

#!/usr/bin/python import re filename = '/sys/devices/w1_bus_master1/28-05168661eaff/w1_slave' valid = False print "Content-Type: text/plain" print "" # execute the command and parse each line of output with open(filename) as f: for line in f: if re.search('crc=.*YES', line): # the CRC is valid. Continue processing valid = True continue if valid and re.search('t=[0-9]+', line): # extract the temperature value temperature = re.search('t=([0-9]+)', line) # convert to degrees celsius and keep 1 digit of accuracy output = "%.1f" % (float(temperature.group(1))/1000.0) print output

Analicemos un poco los scripts. Ambos scripts empiezan con una línea shebang que le indica al llamador qué intérprete usar para ejecutar el script (línea 1). A continuación, definimos dos variables que apuntan al archivo que va a leer (línea 4) y una variable para recordar si la lectura es válida o no (línea 5). En las líneas 7 y 8, grabamos los encabezados HTTP. El script CGI tiene que devolver los encabezados HTTP en las primeras líneas, separados por una línea en blanco del resto del resultado. El servidor web necesita al menos el encabezado Content-Type para procesar la solicitud. Si omites esto, recibirás un error de HTTP 500. En la línea 11 empezamos a leer las líneas del archivo para analizarlas. Buscamos un CRC válido con una expresión regular en la línea 14 y si es correcto, fijamos valid = true. En la línea 19, si el CRC es true y la línea contiene una temperatura, extraemos la temperatura en bruto (línea 21) y la convertimos a Celsius, con un digito de precisión (línea 23), y la mostramos como resultado estándar. Para acceder a los datos, puede usar cualquier cliente HTTP, como wget, tal y como se muestra en la Figura 2.
Puede haber ligeras diferencias en el resultado que se devuelva debido a los diferentes métodos de redondeo utilizados, o por variaciones en el periodo de tiempo en el que se realiza la consulta, lo cual puede provocar que los datos del sensor fluctúen un poco.
Por razones de seguridad, puedes activar la autenticación básica HTTP en la configuración de tu servidor. Necesitarás SSL/HTTPS con certificados válidos para protegerte de cualquiera que quiera analizar tu tráfico, aunque esta cuestión está fuera del alcance de este artículo. Puedes leer más sobre este tema aquí y aquí.
Para añadir el sensor a Home Assistant podemos usar el sensor REST dentro de configuration.yaml :
sensor: ... - platform: rest resource: http://192.168.1.13/cgi-bin/temperature.sh name: Temperature REST Bash unit_of_measurement: C - platform: rest resource: http://192.168.1.13/cgi-bin/temperature.py name: Temperature REST Python unit_of_measurement: CPuedes conseguir el código aquí.
Pros de este método:
- Es fácil de implementar si estás familiarizado con el desarrollo web.
- En Home Assistant se reinician los nuevos datos que se sondean
Contras de este método:
- El uso de un servidor web lo expone a posibles vulnerabilidades.
- El servidor web puede usar muchos recursos en comparación con lo que realmente necesitas hacer.
Conexión a través de la API de HA
Una técnica diferente en la que no interviene un servidor web es mover los datos del sensor a Home Assistant desde el sistema remoto. Podemos usar una Plantilla Sensor para almacenar y presentar los datos. Para hacer esto, puedes hacer que el script de la Figura 3 sea requerido periódicamente por cron en el sistema remoto
#!/bin/bash filename='/sys/devices/w1_bus_master1/28-05168661eaff/w1_slave' homeassistantip='192.168.1.9' haport=8123 api_password='odroid' sensor_name='sensor.temperature_via_api' valid=0 # read line by line, parse each line while read -r line do if [[ $line =~ crc=.*YES ]]; then # the CRC is valid. Continue processing valid=1 continue fi if [[ "$valid" == "1" ]] && [[ $line =~ t=[0-9]+ ]]; then # extract the temperature value rawtemperature=`echo "$line" | cut -d "=" -f 2` # convert to degrees celsius and keep 1 digit of accuracy temperature=`echo "scale=1;$rawtemperature/1000" | bc` # push the data to the Home Assistant entity via the API curl -X POST -H "x-ha-access: $api_password" -H "Content-Type: application/json" \ --data "{\"state\": \"$temperature\"}" http://$homeassistantip:$haport/api/states/$sensor_name fi #read line by line from $filename done < "$filename"

Como puede ver, el código es similar al ejemplo anterior, excepto que en la línea 25 utiliza la API REST de Home Assistant para enviar la lectura de la temperatura. La API REST requiere que envíes la clave de la API de Home Assistant dentro de un encabezado HTTP, y los datos que quieras cambiar deben estar en una carga JSON en la solicitud POST. La URL que envías es tu instancia de Home Assistant /api/states/sensor.name. Para activar esto y enviar los datos cada 5 minutos, añade la siguiente entrada cron:
$ crontab -e */5 * * * * /bin/bash /path/to/script/temperature-HA-API.sh > /dev/null 2>&1La configuración de Home Assistant debería parecerse a esto:
sensor: … - platform: template sensors: temperature_via_api: value_template: '{{ states.sensor.temperature_via_api.state }}' friendly_name: Temperature via API unit_of_measurement: CLa plantilla sensor generalmente se utiliza para extraer los datos de otras entidades de Home Assistant y, en este caso, la utilizamos para extraer datos de sí mismo. Este truco te evita que se eliminen datos de estado tras una actualización externa. Antes de configurar la temperatura, el estado del sensor estará en blanco. Una vez que cron ejecute el script por primera vez, obtendrás datos de temperatura. Puedes descargar el código desde here
Pros de este método:
- Permite controlar cuando se envían los datos
- El uso de recursos es muy bajo.
Contras de este método:
- Tu script necesita tener claramente tu contraseña secreta de Home Assistant
- Cuando se reinicia Home Assistant, el sensor no tendrá ningún valor hasta la primera actualización
Conexión a través de MQTT
El protocolo MQTT es un protocolo de máquina a máquina diseñado para la eficiencia (y para entornos de baja potencia) y ya ha sido tratado en anteriores artículos de ODROID Magazine. El modo de funcionamiento parte de un servidor central llamado broker que transmite mensajes a clientes que se suscriben a un tema común. Piensa en un tema como si fuera algo así como un canal IRC donde los clientes se conectan y se envían mensajes específicos.
Home Assistant tiene un MQTT Broker, integrado, pero en mis pruebas lo encontré poco fiable, así que usé un broker dedicado llamado Mosquitto. Se puede instalar en el mismo sistema que Home Assistant o en un sistema diferente. Para instalarlo, sigue estos pasos:
$ sudo apt-get install mosquitto mosquitto-clients $ sudo systemctl enable mosquittoLa versión 3.11 de MQTT soporta autenticación, de modo que, debes configurar un nombre de usuario y una contraseña que serán compartidos por el bróker y los clientes y opcionalmente, Encriptación SSL. En mi configuración, utilicé la autenticación usuario-contraseña y añadí el usuario 'ODROID'
$ sudo mosquitto_passwd -c /etc/mosquitto/passwd ODROID $ sudo vi /etc/mosquitto/conf.d/default.conf allow_anonymous false password_file /etc/mosquitto/passwdPuedes activar el soporte genérico para MQTT en Home Assistant añadiendo una plataforma MQTT en configuration.yaml (recuerda que el parámetro mqtt_password se define en secrets.yaml):
mqtt: broker: 127.0.0.1 port: 1883 client_id: home-assistant keepalive: 60 username: ODROID password: !secret mqtt_passwordPara enviar datos de temperatura a Home Assistant, nuestro script necesitará la librería Python Paho-MQTT. Para analizar los datos de configuración necesitaremos también la librería python-yaml:
$ sudo apt-get install python-pip python-yaml $ sudo pip install paho-mqttEl script se ejecuta como un demonio, realizando lecturas periódicas de temperatura en segundo plano y enviando los cambios a través de MQTT. El código que lee la temperatura en sí (línea 40) es el mismo que aparecen en la Figura 1b y no se muestra en la Figura 4 para simplificar. El único cambio es que, en lugar de mostrar la temperatura, la devuelve como una cadena.
El código comienza importando algunos módulos auxiliares, definiendo las funciones para analizar la configuración YAML en un diccionario. La lectura de la temperatura y la ejecución empiezan en la línea 57. Se define y se activa un nuevo objeto cliente MQTT con los detalles necesarios para acceder al bróker MQTT. En la línea 61, hay un subproceso en segundo plano iniciado por la llamada loop_start () que asegura que el cliente permanezca conectado al bróker MQTT. Sin él, la conexión expiraría tras un tiempo y tendrías que volver a conectarte manualmente. Tienes más información sobre la API MQTT en Python disponible aquí. En la línea 65, aparece un bucle que lee los datos de temperatura, los compara con la última lectura de temperatura y, si hay un cambio, publica un mensaje MQTT al bróker con la nueva temperatura. Después, el código está inactivo durante un tiempo antes de la próxima lectura. Cuando se publican datos en el bróker (en la línea 71), debes especificar el tema MQTT, el valor que se envía y también si estos datos deben ser persistentes o no. Los datos persistentes son muy ventajosos, ya que puedes obtener la última lectura de temperatura desde MQTT cuando inicias Home Assistant y leer la temperatura desde el principio. Puedes conseguir el código completo aquí.
#!/usr/bin/python import paho.mqtt.client as mqtt import re import time import sys import yaml # Prerequisites: # * pip: sudo apt-get install python-pip # * paho-mqtt: pip install paho-mqtt # * python-yaml: sudo apt-get install python-yaml # Configuration file goes in /etc/temperature-mqtt-agent.yaml and should contain your mqtt broker details # For startup copy temperature-mqtt-agent.service to /etc/systemd/system/ # Startup is done via systemd with # sudo systemctl enable temperature-mqtt-agent # sudo systemctl start temperature-mqtt-agent filename = '/sys/devices/w1_bus_master1/28-05168661eaff/w1_slave' valid = False oldValue = 0 """ Parse and load the configuration file to get MQTT credentials """ conf = {} def parseConfig(): global conf with open("/etc/temperature-mqtt-agent.yaml", 'r') as stream: try: conf = yaml.load(stream) except yaml.YAMLError as exc: print(exc) print("Unable to parse configuration file /etc/temperature-mqtt-agent.yaml") sys.exit(1) """ Read temperature from sysfs and return it as a string """ def readTemperature(): with open(filename) as f: for line in f: if re.search('crc=.*YES', line): # the CRC is valid. Continue processing valid = True continue if valid and re.search('t=[0-9]+', line): # extract the temperature value temperature = re.search('t=([0-9]+)', line) # convert to degrees celsius and keep 1 digit of accuracy output = "%.1f" % (float(temperature.group(1)) / 1000.0) # print("Temperature is "+str(output)) return output """ Initialize the MQTT object and connect to the server """ parseConfig() client = mqtt.Client() if conf['mqttUser'] and conf['mqttPass']: client.username_pw_set(username=conf['mqttUser'], password=conf['mqttPass']) client.connect(conf['mqttServer'], conf['mqttPort'], 60) client.loop_start() """ Do an infinite loop reading temperatures and sending them via MQTT """ while (True): newValue = readTemperature() # publish the output value via MQTT if the value has changed if oldValue != newValue: print("Temperature changed from %f to %f" % (float(oldValue), float(newValue))) sys.stdout.flush() client.publish(conf['mqttTopic'], newValue, 0, conf['mqttPersistent']) oldValue = newValue # sleep for a while # print("Sleeping...") time.sleep(conf['sleep'])

El script también necesitará un archivo de configuración donde guardar las credenciales MQTT, ubicado en /etc/temperature-mqtt-agent.yaml:
mqttServer: 192.168.1.9 mqttPort: 1883 mqttUser: ODROID mqttPass: ODROID mqttTopic: ha/kids_room/temperature mqttPersistent: True sleep: 10También hay un script de arranque systemd para que inicie tu script con cada arranque. Cópialo en /etc/systemd/system:
$ cat /etc/systemd/system/temperature-mqtt-agent.service [Unit] Description=Temperature MQTT Agent After=network.target [Service] ExecStart=/usr/local/bin/temperature-mqtt-agent.py Type=simple Restart=always RestartSec=5 [Install] WantedBy=multi-user.targetPara que se active al arrancar, ejecuta los siguientes comandos:
$ sudo systemctl enable temperature-mqtt-agent.service $ sudo systemctl start temperature-mqtt-agent.serviceEn lo que respecta a Home Assistant, necesitamos definir un sendor MQTT con la siguiente configuración:
sensor: ... - platform: mqtt state_topic: 'ha/kids_room/temperature' name: 'Temperature via MQTT' unit_of_measurement: C
Pros de este método:
- El uso de recursos es bajo
- API estándar de bajo coste operativo diseñada para la comunicación máquina a máquina.
Contras de este método:
- El sistema remoto necesita tener claramente la contraseña de MQTT
- Cuando se reinicia Home Assistant, el sensor no tendrá ningún valor hasta la primera actualización a menos que se use la opción de persistencia de MQTT
Ahora que has visto varios ejemplos de cómo obtener datos en Home Assistant, deberá elegir cual es el mejor para tu configuración. De ahora en adelante yo usare MQTT porque, aunque parezca más difícil al principio, permite un mejor escalado con tareas más complejas.
Controlar una Smart TV con un componente personalizado
Aquí tenemos un nuevo problema que queremos resolver. Queremos obtener el número de canal actual, el nombre del programa y el estado de TV de un televisor Samsung con firmware SamyGO. El televisor revela esta información a través de una API REST que se puede instalar en el televisor desde aquí. La API devuelve información en formato JSON sobre el estado actual del televisor. Se puede inyectar códigos de control remoto y también se puede enviar de vuelta capturas de pantalla con lo que aparece actualmente en pantalla. La llamada y los resultados de la información actual se ven así:
$ wget -O - "http://tv-ip:1080/cgi-bin/samygo-web-api.cgi?challenge=oyd4uIz5WWAkWPo5MzfxBFraI05C3FDorSPE7xiMLCVAQ40a&action=CHANNELINFO"{"source":"TV (0)", "pvr_status":"NONE", "powerstate":"Normal", "tv_mode":"Cable (1)", "volume":"9", "channel_number":"45", "channel_name":"Nat Geo HD", "program_name":"Disaster planet", "resolution":"1920x1080", "error":false}
En teoría, podríamos configurar los sensores REST para hacer la consulta anterior y utilizar la plantilla para conservar solo la información deseada, algo así como esto:
sensor: ... - platform: rest resource: http://tv-ip:1080/cgi-bin/samygo-web-api.cgi?challenge=oyd4uIz5WWAkWPo5MzfxBFraI05C3FDorSPE7xiMLCVAQ40a&action=CHANNELINFO method: GET value_template: '{{ value_json.channel_name }}' name: TV Channel NamePero el problema es que, para obtener toda la información de diferentes sensores, debes hacer la misma consulta, descartar una gran cantidad de datos y mantener solo lo que necesitas para ese sensor en particular. Esto es ineficaz y en este caso, no funcionará porque, para obtener y presentar esta información, la API web que se ejecuta en el televisor inyecta varias librerías en los procesos que se ejecutan en el televisor para captar algunas llamadas de funciones y obtener los datos aquí. La fase de la inyección es muy crítica, y hacer varias inyecciones al mismo tiempo podría causar que se bloquee el proceso, lo cual bloquearía a su vez el TV. Esta es la razón por la cual la API web realiza las consultas de forma progresiva y no responde a una consulta antes de que se complete la anterior, aunque esto genere tiempos de espera.
Lo que se necesita en este caso es que el componente del sensor almacene todos los datos JSON y tenga una plantilla de sensores para extraer los datos necesarios y presentarlo. Para hacer esto, necesitamos un componente personalizado, que deriva del sensor REST y que actúa justamente como el sensor REST, pero cuando recibe datos JSON, los almacena como atributos de la entidad en lugar de descartarlos.
Los componentes personalizados se encuentran en el directorio ~ homeassistant/.homeassistant/custom_components y mantienen la estructura de los componentes habituales (lo que significa que nuestro sensor estará en el subdirectorio del sensor). Se cargarán en el Inicio de Home Assistant antes de que se analice la configuración. La Figura 5 muestra las diferencias entre el sensor REST y el nuevo sensor JsonRest personalizado.
Para entender los cambios realizados, deberías seguir la guía de componentes personalizada http://bit.ly/2fvc1PT. El código hace algunos cambios en el nombre de las clases del módulo para evitar enfrentamientos con el componente REST, y activa y gestiona una lista de atributos que se analizan desde la entrada JSON. Estos se mostrarán como atributos en la vista de estados. El nombre del nuevo componente es JsonRest, igual que el nombre del archivo.
Para instalar el componente JsonRest, puede seguir estos pasos:
mkdir -p ~homeassistant/.homeassistant/custom_components/sensor/ wget -O ~homeassistant/.homeassistant/custom_components/sensor/jsonrest.py https://raw.githubusercontent.com/mad-ady/home-assistant-customizations/master/custom_components/sensor/jsonrest.pyTPara configurar el nuevo componente, una vez que se almacene en el directorio custom_components/sensor, podemos usar esta configuración para sondear el televisor cada 5 minutos:
sensor: … - platform: jsonrest resource: http://tv-ip:1080/cgi-bin/samygo-web-api.cgi?challenge=oyd4uIz5WWAkWPo5MzfxBFraI05C3FDorSPE7xiMLCVAQ40a&action=CHANNELINFO method: GET name: TV Living ChannelInfo scan_interval: '00:05' - platform: template sensors: tv_living_powerstate: value_template: '{{ states.sensor.tv_living_channelinfo.attributes.power_state }}' friendly_name: TV Living Power tv_living_channel_number: value_template: '{{ states.sensor.tv_living_channelinfo.attributes.channel_number }}' friendly_name: TV Living Channel Number tv_living_channel_name: value_template: '{{ states.sensor.tv_living_channelinfo.attributes.channel_name }}' friendly_name: TV Living Channel Name tv_living_program_name: value_template: '{{ states.sensor.tv_living_channelinfo.attributes.program_name }}' friendly_name: TV Living Program NameAhora solo el componente JsonRest será el que sondee el televisor para obtener la información, y la plantilla sensores extraerán los datos necesarios de los atributos, reduciendo la carga en el televisor.
Puesto que la API web del TV permite tomar capturas de pantalla, vamos añadir esta función también a Home Assistant (para no perder de vista lo que los niños ven el TV). La API devuelve una imagen JPEG cada vez que preguntes con el parámetro URL action=SNAPSHOT. Puedes usar un a Componente de Cámara IP genérica:
camera 2: platform: generic name: TV Living Image still_image_url: http://tv-ip:1080/cgi-bin/samygo-web-api.cgi?challenge=oyd4uIz5WWAkWPo5MzfxBFraI05C3FDorSPE7xiMLCVAQ40a&action=SNAPSHOTLa API web TV también te permite enviar acciones de control remoto, que se pueden configurar a través del Componente de commandos Restful:
rest_command: tv_living_power_on: url: !secret samygo_tv_living_power_on tv_living_power_off: url: !secret samygo_tv_living_power_offTras agrupar un poco, el resultado final lo puedes ver aquí. Tienes un enlace a la configuración disponible aquí, y un ejemplo para el archivo secrets aquí. Puedes encontrar el código y la configuración en la pçagina de GitHub.
Be the first to comment