Utilizar Raspberry Pi como Gateway Zigbee
El uso de dispositivos de domótica que funcionan bajo protocolo Zigbee requiere para su conexión de un gateway Zigbee, un dispositivo hardware que sirve para interconectarlos. Podemos recurrir a dispositivos comerciales, que suelen estar basados en soluciones propietarias cerradas a un conjunto de dispositivos concretos de una marca, o montar nuestro propio gateway Zigbee utilizando el software zigbee2mqtt, un adaptador USB Zigbee y una Raspberry Pi, PC o dispositivo similar que ejecute GNU/LINUX para realizar esta función.
En este caso veremos como montar esta solución utilizando una Raspberry Pi 3.
Para obtener un buen rendimiento, la recomendación de zigbee2mqtt
es que se utilice cualquier adaptador USB Zigbee basado en los chips CC2652 y CC1352 de Texas Instruments. Hay otros dispositivos soportados que pueden tener alguna limitación o que necesitan que el firmware sea actualizado. La lista completa está descrita en https://www.zigbee2mqtt.io/guide/adapters/ y se incluyen enlaces con las instrucciones de actualización del firmware para algunos de ellos.
En este caso vamos a utilizar un Sonoff Zigbee 3.0 USB Dongle Plus, un dispositivo basado en el chip CC2652P, que se encuentra dentro de los recomendados, y cuyo firmware de serie es compatible con zigbee2mqtt.
Conectamos el adaptador a uno de los puertos de la Raspberry pi y desde un terminal comprobamos si se ha detectado el dispositivo, lanzando el siguiente comando:
lsusb | grep CP210
Bus 001 Device 005: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
El dispositivo debe aparecer en el directorio /dev
. Dependiendo del modelo, puede aparecer como ttyACM0 o bien, como es nuestro caso como ttyUSB0:
ls -l /dev/ttyUSB0
crw-rw----. 1 root dialout 188, 0 feb 7 22:21 /dev/ttyUSB0
Si no lo localizamos, podemos lanzar la siguiente instrucción:
ls -l /dev/serial/by-id
total 0
lrwxrwxrwx. 1 root root 13 feb 7 22:21 usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_c6edf6bd90c8eb119099c1c3de5b81b4-if00-port0 -> ../../ttyUSB0
Nos devolverá una descripción más larga que se corresponde a un enlace simbólico que apuntará al dispositivo anterior.
Tenemos que disponer de permisos de escritura en el puerto que corresponde al dispositivo. Lo podemos comprobar con el siguiente comando que devolverá success
si todo está correcto:
test -w /dev/ttyUSB0 && echo success || echo failure
success
Si el resultado es failure
hay que dar permisos al usuario con el que estamos trabajando, asignándolo al grupo dialout
o al grupo al que pertenezca el archivo del dispositivo (/dev/ttyUSB0
en nuestro caso):
usermod -a -G dialout $USER
newgrp dialout
Debemos disponer de un broker MQTT, ya sea en la misma Raspberry pi o en otro dispositivo al que tengamos acceso por la red. El proceso de instalación del broker mosquitto en la Raspberry pi está descrito en este artículo
Para la configuración tenemos que instalar primero los paquetes nodejs
, npm
, git
, make
, g++
y gcc
.
sudo apt-get install -y nodejs npm git make g++ gcc
Verificamos la versión de nodejs. Debe ser v10.X, v12.X, v14.X, v15.X or V16.X
node --version
v12.22.5
Verificamos la versión de npm. Debe ser 6.X o 7.X
npm --version
7.5.2
Una vez que tenemos instalados todos los paquetes en las versiones requeridas, procedemos a clonar el repositorio de zigbee2mqtt de Github:
cd /tmp
git clone https://github.com/Koenkk/zigbee2mqtt.git
sudo mv zigbee2mqtt /opt/zigbee2mqtt
Instalamos las dependencias del proyecto:
cd /opt/zigbee2mqtt
npm ci
Antes de arrancar Zigbee2MQTT necesitamos editar el archivo de configuración configuration.yaml
:
vi /opt/zigbee2mqtt/data/configuration.yaml
Debemos configurar la url y datos de autenticación del servidor MQTT. También debemos especificar la ruta donde se encuentra el adaptador USB Zigbee. El archivo quedaría de la siguiente forma:
# MQTT settings
homeassistant: false
permit_join: true
advanced:
network_key: GENERATE
mqtt:
# MQTT base topic for Zigbee2MQTT MQTT messages
base_topic: zigbee2mqtt
# MQTT server URL
server: 'mqtt://localhost:1883'
# MQTT server authentication, uncomment if required:
user: zigbee
password: contraseña
# Serial settings
serial:
# Location of the adapter (see first step of this guide)
port: /dev/ttyUSB0
Es recomendable utilizar una clave de red personalizada. Podemos hacer esto añadiendo lo siguiente al archivo configuraiton.yaml
, que generá una clave de red en el siguiente arranque.
advanced:
network_key: GENERATE
Guardamos el archivo y salimos del editor.
Ya podemos arrancar zigbee2mqtt manualmente:
cd /opt/zigbee2mqtt
npm start
Building Zigbee2MQTT... (initial build), finished
Zigbee2MQTT:info 2022-01-20 19:10:30: Logging to console and directory: '/opt/zigbee2mqtt/data/log/2022-01-20.19-10-26' filename: log.txt
Zigbee2MQTT:info 2022-01-20 19:10:30: Starting Zigbee2MQTT version 1.22.2 (commit #1a0a9a6e)
Zigbee2MQTT:info 2022-01-20 19:10:30: Starting zigbee-herdsman (0.13.188)
Zigbee2MQTT:info 2022-01-20 19:10:38: zigbee-herdsman started (reset)
Zigbee2MQTT:info 2022-01-20 19:10:38: Coordinator firmware version: '{"meta":{"maintrel":3,"majorrel":2,"minorrel":6,"product":0,"revision":20190608,"transportrev":2},"type":"zStack12"}'
Zigbee2MQTT:info 2022-01-20 19:10:38: Currently 0 devices are joined:
Zigbee2MQTT:warn 2022-01-20 19:10:38: `permit_join` set to `true` in configuration.yaml.
Zigbee2MQTT:warn 2022-01-20 19:10:38: Allowing new devices to join.
Zigbee2MQTT:warn 2022-01-20 19:10:38: Set `permit_join` to `false` once you joined all devices.
Zigbee2MQTT:info 2022-01-20 19:10:38: Zigbee: allowing new devices to join.
Zigbee2MQTT:info 2022-01-20 19:10:39: Connecting to MQTT server at mqtt://192.168.168.1:1883
Zigbee2MQTT:info 2022-01-20 19:10:39: Connected to MQTT server
Zigbee2MQTT:info 2022-01-20 19:10:39: MQTT publish: topic 'zigbee2mqtt/bridge/state', payload 'online'
Una vez que comprobamos que arranca bien, podemos crear archivo de configuración de systemctl para que Zigbee2MQTT pueda arrancar como un daemon:
sudo vi /etc/systemd/system/zigbee2mqtt.service
El contenido de ese archivo debe ser el siguiente:
[Unit]
Description=zigbee2mqtt
After=network.target
[Service]
ExecStart=/usr/bin/npm start
WorkingDirectory=/opt/zigbee2mqtt
# Descomentar siguiente línea para mostrar logs de los mensajes
#StandardOutput=inherit
# La siguiente línea evita que se generen demasiados logs
StandardOutput=null
# Or use StandardOutput=null if you don't want Zigbee2MQTT messages filling syslog, for more options see systemd.exec(5)
StandardError=inherit
Restart=always
User=pi
[Install]
WantedBy=multi-user.target
En caso de estar utilizando una Raspberry Pi 1 o Zero, hay que reemplazar ExecStart=/usr/bin/npm start
por ExecStart=/usr/local/bin/npm start
.
Si el sistema se está ejecutando desde una tarjeta SD, es recomendable minimizar el número de archivos de log, para prolongar la vida de la tarjeta. El servicio systemd con la opción StandardOutput=inherit
dará como resultado el registro de todo dos veces, una en journalctl a través de la unidad systemd y otra a través del sistema de log predeterminado de Zigbee2MQTT. Podemos mantener sólo una copia de ellos utilizando StandardOutput=null
en la unidad systemd y mantener sólo los logs que están dentro de data/log
, o estaleciendo ‘advanced.log_output = [‘console’]en la configuración de **Zigbee2MQTT** para mantener sólo los logs de *journalctl*. Complementando esto, podemos añadir algunos atributos en el archivo
configuration.yamlpara personalizar la ubicación, nivel de detalle y nombre de los archivos de log. En este caso optamos por un nivel de log
info, guardar en el directorio en
/opt/zigbee2mqtt/data/logen un archivo llamado
zigbee2mqtt.log`. El fragmento de parámetros de configuración sería:
...
advanced:
log_level: info
log_directory: data/log/
log_file: zigbee2mqtt.log
log_rotation: true
log_output:
- console
- file
log_symlink_current: false
Revisado todo esto, guardamos el archivo y salimos del editor.
Podemos comprobar que funciona la configuración arrancando el servicio:
sudo systemctl start zigbee2mqtt
Comprobamos el estado:
systemctl status zigbee2mqtt.service
La salida debe mostrar que el servicio está activo:
zigbee2mqtt.service - zigbee2mqtt
Loaded: loaded (/etc/systemd/system/zigbee2mqtt.service; disabled; vendor preset: enabled)
Active: active (running) since Thu 2022-01-20 22:31:23 CET; 10s ago
Lo habilitamos para que inicie en el arranque del sistema de manera automática:
systemctl enable zigbee2qtt.service
La actualización de versión de zigbee2mqtt consiste en los siguientes pasos
Parar zigbee2mqtt y entrar en el directorio donde se encuentra el código de github:
sudo systemctl stop zigbee2mqtt
cd /opt/zigbee2mqtt
Hacer backup de la configuración:
cp -R data data-backup
Descargar el código más reciente de github y actualizar las dependencias:
git checkout HEAD -- npm-shrinkwrap.json
git pull
npm ci
Restaurar la configuración:
cp -R data-backup/* data
rm -rf data-backup
Arrancar el servicio:
sudo systemctl start zigbee2mqtt
Para comprobar el funcionamiento, vamos a emparejar un sensor de temperatura y humedad WD500A de Tuya, cuya documentación para Zigbee2mqtt se encuentra en https://www.zigbee2mqtt.io/devices/WSD500A.html.
Para ver todo el proceso, podemos consultar el archivo de log, que se encontrará en /opt/zigbee2mqtt/data/log
, con el nombre que indicamos en nuestra configuración específica. Ejemplo:
tail -f /opt/zigbee2mqtt/data/log/zigbee2mqtt.log
Para realizar el emparejamiento del dispositivo con pinchamos el botón reset durante 5 segundos, hasta que el led del sensor se pone a parpadear. Zigbee2mqtt lo detecta y muestra lo siguiente:
Zigbee2MQTT:info 2022-01-20 19:16:41: MQTT publish: topic 'zigbee2mqtt/bridge/event', payload '{"data":{"friendly_name":"0xa4c13831700f4951","ieee_address":"0xa4c13831700f4951"},"type":"device_announce"}'
Zigbee2MQTT:info 2022-01-20 19:16:45: Successfully interviewed '0xa4c13831700f4951', device has successfully been paired
Zigbee2MQTT:info 2022-01-20 19:16:45: Device '0xa4c13831700f4951' is supported, identified as: TuYa Temperature & humidity sensor (WSD500A)
Zigbee2MQTT:info 2022-01-20 19:16:45: MQTT publish: topic 'zigbee2mqtt/bridge/event', payload '{"data":{"definition":{"description":"Temperature & humidity sensor","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Measured temperature value","name":"temperature","property":"temperature","type":"numeric","unit":"°C"},{"access":1,"description":"Measured relative humidity","name":"humidity","property":"humidity","type":"numeric","unit":"%"},{"access":1,"description":"Voltage of the battery in millivolts","name":"voltage","property":"voltage","type":"numeric","unit":"mV"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WSD500A","options":[{"access":2,"description":"Number of digits after decimal point for temperature, takes into effect on next report of device.","name":"temperature_precision","property":"temperature_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the temperature value (absolute offset), takes into effect on next report of device.","name":"temperature_calibration","property":"temperature_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for humidity, takes into effect on next report of device.","name":"humidity_precision","property":"humidity_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the humidity value (absolute offset), takes into effect on next report of device.","name":"humidity_calibration","property":"humidity_calibration","type":"numeric"}],"supports_ota":false,"vendor":"TuYa"},"friendly_name":"0xa4c13831700f4951","ieee_address":"0xa4c13831700f4951","status":"successful","supported":true},"type":"device_interview"}'
Zigbee2MQTT:info 2022-01-20 19:16:48: MQTT publish: topic 'zigbee2mqtt/0xa4c13831700f4951', payload '{"linkquality":123,"temperature":23.21}'
Zigbee2MQTT:info 2022-01-20 19:16:48: MQTT publish: topic 'zigbee2mqtt/0xa4c13831700f4951', payload '{"humidity":42.01,"linkquality":120,"temperature":23.21}'
Si volvemos a editar el archivo configuration.yaml
de zigbee2mqtt, aparecerá lo siguiente:
devices:
'0xa4c13831700f4951':
friendly_name: '0xa4c13831700f4951'
Podemos cambiar el atributo friendly_name
por un nombre más descriptivo, como sensor_habitación
. También podemos añadir otras propiedades que se encuentren dentro de la documentación del sensor, aunque de momento cambiaremos sólo el nombre:
devices:
'0xa4c13831700f4951':
friendly_name: sensor_habitacion
y reiniciamos el servicio para que se aplique el cambio:
sudo systemctl restart zigbee2mqtt
Una vez reiniciado el servicio y asignado un nombre fácil de reconocer, zigbee2mqtt se encarga de recibir la información del sensor y publicarla en el broker MQTT en un topic llamado zigbee2mqtt/friendly_name
en nuestro caso zigbee2mqtt/sensor_habitacion
. Los datos que se publican son un objeto JSON que variará según el dispositivo. Para comprobar los datos que se están publicando en nuestro broker MQTT nos vamos a suscribir a ese topic. Si lo hacemos desde la propia RPi, pondremos en el parámetro -h
el valor localhost
. Si lo hacemos desde otro equipo pondremos la dirección IP de la RPi. Tendremos que especificar el usuario y contraseña que especificáramos durante la instalación de mosquitto. En este caso se utilizan un par de variables de entorno:
mosquitto_sub -h localhost -p 1883 \
-u $MQTTUSER -P $MQTTPASS \
-t "zigbee2mqtt/sensor_habitacion"
zigbee2mqtt/sensor_habitacion {"battery":null,"humidity":61.29,"linkquality":69,"temperature":20.39,"voltage":null}
zigbee2mqtt/sensor_habitacion {"battery":null,"humidity":61.01,"linkquality":72,"temperature":20.39,"voltage":null}
El tiempo que tardemos en recibir el valor depende de varios factores, como las características del sensor en concreto y si el ambiente está cambiando, ya que si la temperatura y/o humedad están estables, enviará datos con menos frecuencia.
Vemos que recibimos como respuesta datos en formato JSON, parejas clave-valor separadas por comas, y en las que se separa el nombre de parámetro de su valor por dos puntos. En este caso no se reciben valores en los parámetros battery
y voltage
(null
). En cambio sí que se están enviando valores para los parámetros:
linkquality
. Fuerza de la señal. Es un valor comprendido entre 0 y 255.humidity
. Humedad relativa, expresada en tanto por ciento (%).temperature
. Temperatura, expresada en grados centígrados.
Es posible hacer algunos ajustes del sensor para personalizar la precisión de las mediciones o calibrar la temperatura y la humedad. Los parámetros que admite este sensor son:
temperature_precision
. Número de dígitos decimales para la temperatura. El valor admitido va de 0 (ningún decimal) a 3 (3 decimales).temperature_calibration
. Permite calibrar la temperatura, sumando o restando un valor numérico para ajustarlo al valor real que queramos obtener. Si nuestro sensor marca 1 grado menos, pondríamos un valor1
. Si marca 2,5 grados de más, pondríamos-2.5
.humidity_precision
. Número de dígitos decimales para la humedad. El valor admitido va de 0 (ningún decimal) a 3 (3 decimales).humidity_calibration
. Permite calibrar la humedad, sumando o restando un valor numérico para ajustarlo al valor real que queramos obtener.
Para ajustar estos parámetros podemos añadir uno o varios de ellos en el archivo de configuración /opt/zigbee2mqtt/data/configuration.yaml
, en la sección correspondiente al dispositivo. Un ejemplo de ello sería lo siguiente, para el sensor que hemos llamado sensor_habitacion
:
devices:
'0xa4c13831700f4951':
friendly_name: sensor_habitacion
temperature_precision: 1
humidity_precision: 0
temperature_calibration: 2.5
humidity_calibration: 3
Le estamos indicando que queremos la temperatura con un decimal, la humedad sin decimales, que sume 2 grados y medio a la temperatura que recoja el sensor y que sume 3 tantos porcentuales a la humedad. Para que se apliquen estos cambios tenemos que reiniciar el servicio zigbee2mqtt
:
sudo systemctl restart zigbee2mqtt
Al cabo de unos minutos, veremos que los datos recibidos cambian de formato y de valor:
mosquitto_sub -h localhost -p 1883 \
-u $MQTTUSER -P $MQTTPASS \
-t "zigbee2mqtt/sensor_habitacion"
zigbee2mqtt/sensor_habitacion {"battery":null,"humidity":64,"linkquality":93,"temperature":22.8,"voltage":null}
Enviar al topic zigbee2mqtt/bridge/request/device/remove
el payload {"id": "deviceID"}
. Ejemplo:
mosquitto_pub -h localhost -p 1883 \
-u $MQTTUSER -P $MQTTPASS \
-t "zigbee2mqtt/bridge/request/device/remove" \
-m '{"id": "0xa4c13831700f4950"}'
mosquitto_pub -h localhost -p 1883 \
-u $MQTTUSER -P $MQTTPASS \
-t "zigbee2mqtt/bridge/config/force_remove" \
-m '{"id": "0xa4c13831700f4950"}'
== Resumen
Como hemos visto, podemos recibir información de un sensor a través del comando mosquitto_sub, con lo que podríamos aprovechar esta información desde scripts. Además de esta posibilidad, una forma muy flexible de utilizar esta información es instalar Home Assistant e integrar nuestro gateway con esta aplicación. Nos permitirá mostrar de forma visual la información y aprovecharla junto con otros dispositivos para hacer automatizaciones, como por ejemplo, avisar a través de Telegram cuando la temperatura pase de un determinado valor, o encender el aire acondicionado.