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.

 

Modelos de Adaptador USB Zigbee

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.

 

Detección del adaptador

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

 

Instalación del servidor MQTT

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

 

Instalación de Zigbee2mqtt

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 archivoconfiguration.yamlpara personalizar la ubicación, nivel de detalle y nombre de los archivos de log. En este caso optamos por un nivel de loginfo, guardar en el directorio en /opt/zigbee2mqtt/data/logen un archivo llamadozigbee2mqtt.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

 

Actualizar a la última versión de zigbee2mqtt

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

 

Configurar sensor de temperatura y humedad

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 valor 1. 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}

 

Anular registro de un dispositivo

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.

 

Referencias