--- weight: 4 title: "Apuntes de Domótica" date: 2020-11-03T18:01:27+0100 draft: false summary: "Notas sobre Domótica" categories: - notes tags: - domotica - micros - mqtt - mosquitto - influxdb - grafana - homeassistant --- Apuntes **no terminados** sobre Domótica Libre {{< admonition type=warning title="Work in progress" open=true >}} Estos apuntes no están completos, (ni de lejos) {{< /admonition >}} ## Grupo de investigación * Fundado el 2020-11-02, tenemos grupo de telegram y BBB. * [Cryptpad del grupo de domótica](https://cryptpad.fr/pad/#/2/pad/edit/+K6cC9t-UGKfem6AdmkJ93ja/) ## Hardware para empezar * __Wemos__: [este](https://es.aliexpress.com/item/32843602421.html) es el micro procesador que vamos a usar para el sensor. Se puede programar con el IDE Arduino. Este es el ESP8285, equivale a un ESP8266 con 1Mb de flash. * __Sensor de temperatura/humedad__ * __RTC y tarjeta SD__ Como [este](https://es.aliexpress.com/item/32826265932.html) * __Pantalla OLED__ Como [esta](https://es.aliexpress.com/item/1005001636650130.html) compatible con la mecánica Wemos * __Módulo de gestión de batería__ Como [este](https://es.aliexpress.com/item/32796003002.html).Para alimentar el sensor con una batería de 3,3 volt Ver la tabla de componentes [aquí](https://cryptpad.fr/sheet/#/2/sheet/edit/PYqEabbLSfwUlQticILskoOC/) ## MQTT _**M**essage **Q**ueuing **T**elemetry **T**ransport_, es un protocolo **ligero** (bajo consumo de memoria y cpu) diseñado especificamente para que dispositivos de bajo consumo transmitan datos de banda estrecha (con velocidades muy bajas). El protocolo funciona sobre TCP/IP (otro protocolo, "el de internet"). Es un protocolo viejecito (1999), lo que pasa es que se ha puesto de moda por que encaja muy bien con todo el tema de IoT (_**I**nternet **o**f **T**hings_) ### Conceptos _Broker_ : Es un _middleware_ (un intermediario) entre los _publishers_ (agentes que envían datos) y _receivers_ (agentes que reciben los datos) _Publisher_ : Un agente que envía datos al _Broker_, puede ser un dispositivo sensor, un demonio monitorizando un servidor, una raspi, etc. etc. _Subscriber_ : Es el receptor de los datos enviados por el _Publisher_ _Topic_ : Los _Publisher_ y _Subscriber_ no se relacionan directamente. Cuando un _publisher_ envía datos al _broker_ los asocia a un _topic_. El _topic_ es una cadena en forma de jerarquia (p.ej. /sensor/cocina/temperatura), el carácter '/' actúa como separador de nivel, cada campo separado por '/' representa un nivel. Los puedes organizar como quieras. Cuando un _subscriber_ se conecta al _broker_ especifica los _topic_ en los que está interesado. El _broker_ le hará llegar todos los mensajes de esos _topic_. Tenemos dos operadores o máscaras para especificar las subscripciones: `#` y `+`. Supongamos que tenemos los siguientes _topic_: * `/sensor/cocina/temperatura` * `/sensor/cocina/humedad` * `/sensor/cocina/humo` * `/sensor/salon/temperatura` * `/sensor/salon/humedad` * `/sensor/salon/humo` Con la especificación `/sensor/+/temperatura` me suscribo a todos los sensores de temperatura. `+` encaja con un nivel simple. Con la especificación `/sensor/#` me suscribo a todos los sensores. `#` encaja con múltiples niveles. Si organizamos los niveles de los _topic_ con lógica, podremos gestionar fácilmente las suscripciones. _message_ : Los datos que son enviados y recibidos ### Versiones del protocolo * V3.1.1 * V5.0 ### Brokers MQTT Hay muchos brokers, esta lista es muy parcial pero todos los que están tienen licencia libre. * __Mosquitto__: Mosquitto es un broker sencillo y que aparece en un montón de tutoriales disponibles en internet. Está incluido en los repos de Debian y Ubuntu (seguramente no muy actualizado) y hay ppa disponible. La principal pega de Mosquitto es que no soporta "clustering" (varios brokers funcionando en paralelo) que es la forma natural de escalar la carga en el servido y dar redundancia. Es práctico para desarrollar aplicaciones pero puede dar problemas si lo quieres usar en un entorno de producción. * __RabittMQ__: Es un broker multiprotocolo así que no solo soporta MQTT. El soporte para distintos protocolos de colas de mensajes se implementa mediante plugins. Soporta clustering así que es escalable para aguantar cargas grandes de peticiones y se puede implementar redundancia. El soporte de MQTT no es completo, no tiene soporte para MQTT V5.0 ni para todas las carácteristicas de MQTT V3.1.1 (por ejemplo no soporta QoS2) * __VerneMQ__: Es un broker MQTT puro (no es multiprotocolo) Soporta clustering y todas las versiones de MQTT, aparentemente con todas sus funcionalidades. ### Pruebas con _Mosquitto_ Vamos a hacer pruebas con el _broker_ más sencillo: _Mosquitto_ En Ubuntu y derivadas es muy fácil de instalar, añadimos el origen de paquetes oficiales para estar a la última, instalamos el paquete `mosquitto` y el paquete `mosquitto-clients`, los clientes nos vendrán bien para hacer pruebas. En otras distribuciones tendrás que mirar como se instala, pero no puede ser muy complicado. ```bash sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa sudo apt-get update sudo apt install mosquitto mosquitto-clients ``` En Ubuntu y derivadas **queda lanzado el servicio** y podemos hacer pruebas con los clientes instalados. Si después queremos instalar el _Mosquitto_ de otra manera (con _docker_ por ejemplo) hay que acordarse de parar el servicio. #### Probando En un terminal con el comando `mosquitto_sub -h localhost -t test` lanzamos un _subscriber_ que atiende al _topic_ test. Desde otro terminal con el comando `mosquitto_pub -h localhost -t test -m "Hola Mundo"` ejecutamos un _publisher_ que manda el mensaje "Hola Mundo" al _topic_ test. En la primera ventana podemos ver: ```bash $ mosquitto_sub -h localhost -t test Hola mundo ``` Se ha recibido el mensaje en el _topic_ suscrito. #### Probando con python Vamos a ver como usar el _mqtt_ desde Python. Así queda mucho más claro como funciona el _broker_ Creamos un entorno virtual y lo activamos (yo lo he llamado `paho`), en ese entorno instalamos la librería: `pip install paho-mqtt` Creamos un programa _subscriber_: ```python import paho.mqtt.client as mqtt import time # la rutina a ejecutar cuando recibimos un mensaje def on_message(client, userdata, message): print("received message: ", str(message.payload.decode("utf-8"))) # la rutina a ejecutar al recibir CONNACK def on_connect(client, b, c, rt): print("Connected with result " + str(rt)) # hacemos la suscripcion dentro de la rutina on_connect # de esta forma si tenemos reconexiones ya nos aseguramos # la suscripcion client.subscribe("ALEATORIOS") # dirección del broker mqttBroker = "localhost" # creamos un "cliente" client = mqtt.Client("subs-1") client.on_connect = on_connect client.on_message = on_message # conexión al broker client.connect(mqttBroker) # lanzamos un loop no bloqueante client.loop_start() # Escuchamos durante 30 segundos y paramos el programa. time.sleep(30) client.loop_stop() ``` Y este sería el programa _publisher_ ```python import paho.mqtt.client as mqtt from random import randrange, uniform import time # definimos la rutina a ejecutar cuando recibimos CONNACK def on_connect(client, userdata, flags, rc): print("Connected with result " + str(rc)) # definimos la dirección del broker mqttBroker = "localhost" # creamos un "cliente" client = mqtt.Client("publisher-1") client.on_connect = on_connect # conexión al broker client.connect(mqttBroker) # Bucle infinito, publicamos un número aleatorio y esperamos 1 sg while True: randNumber = uniform(0, 10) client.publish("ALEATORIOS", randNumber) print("Publicado " + str(randNumber) + " en el topic ALEATORIOS") time.sleep(1) ``` Podemos ver los programas en acción ejecutando cada uno en su terminal (terminal=ventana de comandos) ¡Recuerda activar el entorno virtual en todos los terminales que abras! ##### ¿Qué está pasando aquí? Cada vez que desde un cliente le pedimos algo al _broker_ este nos responde con una señal, pero ¡ojo! el protocolo es asíncrono, así que mas que una conversación entre cliente y servidor tenemos un intercambio de correos. Por ejemplo, cuando nosotros le mandamos la petición de conexión al _broker_ debería llegarnos un _CONNACK_ desde el mismo. En el _CONNACK_ el _broker_ nos adjunta tres campos: _user data_, _flags_ y _result code_. El campo más interesante probablemente sea el _result code_, estos son los valores definidos en el protocolo: | rc | Significado | | :-----: |-------------------------------------------------| | 0 | Connection successful | | 1 | Connection refused – incorrect protocol version | | 2 | Connection refused – invalid client identifier | | 3 | Connection refused – server unavailable | | 4 | Connection refused – bad username or password | | 5 | Connection refused – not authorised | | 6-255 | Currently unused. | La biblioteca _paho-mqtt_ nos permite definir rutinas ___callback__ que la librería invocará cuando se produzcan ciertos eventos. Un par de ejemplos: un evento de conexión reconocida (se ha recibido el _CONNACK_) hará que se ejecute el _callback on_connect_, el evento "recibir un mensaje" hará que _paho_ intente ejecutar el _callback on_message_. Esta tabla cubre los principales eventos: | Evento | _Callback_ | |---------------------------|------------------| | Conexión reconocida | `on_connect` | | Desconexión reconocida | `on_disconnect` | | Suscripción reconocida | `on_subscribe` | | De-suscripción reconocida | `on_unsubscribe` | | Publicación reconocida | `on_publish` | | Mensaje recibido | `on_message` | | Info de log disponible | `on_log` | Los ejemplos que puse son muy sencillos, si queremos escribir un código más robusto hay que tener en cuenta todo lo que el protocolo ofrece y que es un protocolo asíncrono. Un par de ejemplos de mejoras * La función `on_connect` debería comprobar el _result code_ y no dejar continuar el programa sin tener una conexión verificada. * La función `on_message` seguramente tendrá que procesar el _topic_ del mensaje y puede que también la _qos_ ## influxdb * * * * _Influxdb_ es una base de datos especializada en series temporales de datos. Eso hace que sea mucho más eficiente que una base de datos relacional tratando este tipo de datos. Aunque internamente cambia bastante el funcionamiento el lenguaje de consultas no es muy diferente de SQL. Otra ventaja relacionada con nuestro escenario es que se integra muy bien con _Grafana_. Hasta ahora _influxdb_ viene con varias aplicaciones asociadas: _Telegraf_ : un agente de recolección de datos, para capturar datos de _stacks_ y otros sistemas _Cronograf_ : Un visualizador de datos al estilo de _Graphana_ Kapacitor : un motor de procesado de datos en tiempo real especializado en series temporales Pero está disponible la versión __InfluxDB OSS 2.0__ que integra todas las herramientas en un único programa. Por lo que veo la instalación es por binarios únicamente y además __no es compatible con Raspberry__. Parece interesante pero por ahora lo dejamos. Si instalamos el _Influxdb_ clásico nos quedamos con la versión 1.8.3 (en este momento). Una vez instalada y arrancado el servicio lo podemos probar preguntando por las bases de datos disponibles (solo está la _internal_) ```bash $ influx Connected to http://localhost:8086 version 1.8.3 InfluxDB shell version: 1.8.3 > show databases name: databases name ---- _internal > ``` Que no os despiste el interfaz http, no es _human friendly_ ni funciona con navegadores (en esta versión de _influxdb_). Si que podemos interrogarlo con _curl_ por ejemplo y nos responderá en perfecto _json_: ```bash $ curl -G http://localhost:8086/query --data-urlencode "q=SHOW DATABASES" {"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["_internal"]]}]}]} ``` Por lo demás trabajar con _InfluxDB_ no es tan diferente de trabajar con una base de datos relacional de toda la vida. * _InfluxDB_ está optimizada para trabajar con series de datos temporales, es más rápida (con este tipo de datos) que las bases de datos relacionales clásicas * Cambia un poco la terminología: * La base de datos clásica era una colección de tablas, la de _Influx_ puede verse como una colección de "medidas" (_measurements_) * Las "medidas" se parecen a las tablas de las bases de datos relacionales, pero: * Siempre tienen que tener una referencia temporal (_timestamp_, basicamente nanosegundos desde el _Linux Epoch_) * Se distingue entre campos (_fields_) y etiquetas (_tags_), equivalen a los campos (columnas) de las tablas de las bases de datos relacionales, pero: **las etiquetas están indexadas** y los campos no. * los registros, las filas de las tablas, se llaman puntos * Se pueden ajustar las políticas de retención de datos, es decir, cuanto tiempo se mantienen en la base de datos. Depende de para que quieras usar los datos, a lo mejor quieres un histórico de una hora o de cinco años. Ajustar bien las políticas de retención influye en el rendimiento de la base de datos. ### Instalación de _InfluxDB_ Dependerá del sistema operativo, por ejemplo para Linux Mint: ```bash wget -qO- https://repos.influxdata.com/influxdb.key | sudo apt-key add - #source /etc/lsb-release DISTRIB_ID=ubuntu DISTRIB_CODENAME=focal echo "deb https://repos.influxdata.com/${DISTRIB_ID} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list apt update apt install influxdb apt install telegraf ``` Una vez instalado verifica que el servicio está arrancado. El cliente de la base de datos se llama _influx_ si ejecutamos el cliente podemos preguntar por las bases de datos presentes con: `show databases` (solo muestra la base de datos interna) Una sesión completa * Creamos la base de datos * La ponemos como base de datos activa * Insertamos datos ```bash influx -precision rfc3339 Connected to http://localhost:8086 version 1.8.3 InfluxDB shell version: 1.8.3 > show databases name: databases name ---- _internal >create database mydb > show databases name: databases name ---- _internal mydb > use mydb Using database mydb > insert temperature,location=campo external=13,internal=23 > insert temperature,location=ciudad external=19, internal=22 ``` Las lineas de los _insert_ siguen el esquema: `[,=...] =[,=...] [unix-nano-timestamp]` El primer grupo son los _tags_ que **están indexados**. En nuestro ejemplo el _tag_ sería `location`. El segundo grupo son los _fields_ (no indexados), en nuestro ejemplo tendríamos dos `external` e `internal`. Opcionalmente podemos escribir el _timestamp_ o dejar que _influx_ lo ponga automáticamente (pondrá el instante en que se realiza la inserción) ### Un ejemplo con python Lo primero es instalar la biblioteca de python así que en nuestro entorno virtual ejecutamos `pip install influxdb` Un programa python chorras para ejecutar las inserciones del ejemplo anterior: ```python from influxdb import InfluxDBClient client = InfluxDBClient(host=localhost, port=8086) client.switch_database('mydb') client.write_points("temperature,location='countryside' external=13,internal=23", protocol='line') client.write_points("temperature,location='city' external=19,internal=22", protocol='line') ``` Si después de ejecutar el programa vamos al cliente _influx_: ```bash > use mydb Using database mydb > show measurements name ---- temperature > select * from temperature name: temperature time external internal location ---- -------- -------- -------- 2020-11-27T15:23:20.523830387Z 13 23 countryside 2020-11-27T15:23:33.459690565Z 19 22 city ``` ## Análisis y Visualización de datos * __Metabase__: Visualización de gráficos sencilla y potente. * __Grafana__: Plataforma de visualización y análisis de datos muy completa. ## node-red ### Instalación en Linux Para instalar node-red en linux necesitamos instalar primero `node.js`. Hay varias formas de instalar `node.js`, yo voy a optar por instalar `nvm` que es el **n**ode **v**ersion **m**anager. Para ello ejecutamos el siguiente comando (la versión actual de `nvm` es la 0.37.0) ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.0/install.sh | bash ``` El script de instalación añade las siguientes lineas al fichero `~/.bashrc`, nosotros las eliminamos de `~/.bashrc` y las movemos al fichero `~/.profile` ```bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion ``` Para comprobar la instalación usamos `command -v nvm` que nos devolverá `nvm`. `which` no funciona en este caso por que es un script pensado para invocarlo con `source` ### Instalación de `node.js` Ahora que tenemos `nvm` instalado, ya podemos instalar fácilmente la versión o versiones que queramos de `node.js` ```bash nvm ls-remote # para listar las versiones disponibles nvm install node # instala la última versión disponible nvm install --lts # instala la versión LTS ``` ### Instalación de `node-red` Una vez instalado el `node.js` instalar el `node-red` es muy fácil: activar el `node.js` que hemos instalado y hacer la instalación de `node-red` ```bash nvm use node npm install -g --unsafe-perm node-red ``` ## Instalación del IDE Arduino en Linux para trabajar con Wemos D1 Descargamos el IDE de la [pagina web](https://www.arduino.cc/en/software). Para proceder a la instalación se puede consultar [la guía de postinstalación de Linux Mint](https://gitlab.com/salvari/linuxmint_ulyana_20) que detalla todos los pasos. Solo necesitamos habilitar el soporte para ESP8266, añadiendo la linea siguiente a nuestras preferencias de Arduino: `http://arduino.esp8266.com/stable/package_esp8266com_index.json` ## Configuración de mini-PC como servidor Describimos la instalación en un mini-pc Beelink T34 (con 8 gb de RAM y 256Gb de disco duro SSD). No es obligatorio hacerlo como describimos, esto es solo para referencia. Nos hemos decidido por instalar Debian en el mini-pc. Hay varias formas de instalar Debian, pero como nosotros queremos un Debian sin interfaz gráfico y con lo mínimo, creemos que lo más fácil es usar el `netinst`. Descargamos la imagen iso de `netinst` desde [la página de Debian](https://cdimage.debian.org/debian-cd/current/amd64/bt-cd/) (la hemos descargado via torrent) Desde linux hemos usado Etcher (es multiplataforma) para grabar la image iso descargada en una memoria USB. Conectamos nuestro mini-PC a un teclado usb y a un monitor (hemos usado un coversor HDMI-VGA) y conectamos la memoria USB en algúno de los puertos libres, también le hemos dado internet con un cable ethernet a nuestro router. Al arrancar el mini-PC veremos un aviso: pulsando F7 nos deja elegir el dispositivo de arranque. Elegimos arrancar desde la memoria USB e iniciamos la instalación de Debian, con particionado manual y sin LVM. Hemos creado tres particiones: | Particion | Tamaño | Tipo | Punto de montaje | Comentarios | |-----------|--------|------|--------------------|-----------------------------------------------------------| | /dev/sda1 | 511Mb | EFI | /boot/efi | La crea el programa de instalación | | /dev/sda2 | 55Gb | ext4 | / | root | | /dev/sda4 | 171Gb | ext4 | /store | Dejamos casi todo el espacio disponible en esta partición | El programa de instalación nos ha pedido drivers adicionales pero lo hemos ignorado. Hemos escogido idioma inglés con teclado español. También hemos escogido locales `es_ES-UTF-8` **Solo** hemos instalado el openssh server y las herramientas básicas. Tendremos que instalar muchas más cosas pero eso nos deja un sistema lo más limpio posible. Tras completar la instalación el sistema **no** arranca normalmente se queda pillado en el _prompt_ de _grub_. Tras teclear `exit` en el prompt arranca normalmente. Se soluciona ejecutando como _root_ `grub-install` y ya no vuelve a fallar. Una vez arrancado el sistema podemos conectarnos via ssh. ## Referencias ### Wiki de Bricolabs * [Raspberry Pi, configuración para IOT](https://bricolabs.cc/wiki/guias/raspberry_iot) * [Sistema IOT de Bricolabs](https://bricolabs.cc/wiki/proyectos/sistema_iot_bricolabs) ### Referencias MQTT * [Buenos apuntes para aprender de MQTT](https://www.hivemq.com/mqtt-essentials/) * [Para empezar con python y MQTT (muy completo)](http://www.steves-internet-guide.com/into-mqtt-python-client/) * [Enviar datos desde ESP via mqtt](https://diyi0t.com/microcontroller-to-raspberry-pi-wifi-mqtt-communication/) * [Visualizar datos (cont. del ant.)](https://diyi0t.com/visualize-mqtt-data-with-influxdb-and-grafana/) ### Referencias _Influxdb_ * [Getting started](https://docs.influxdata.com/influxdb/v1.7/introduction/getting-started/) * [Getting started with Influxdb OSS 2.0](https://docs.influxdata.com/influxdb/v2.0/get-started/) * [Guia de Influxdb](https://devconnected.com/the-definitive-guide-to-influxdb-in-2019/) * [Instrucciones de instalación para la versión clásica y la nueva 2.0 en ubuntu y debian](https://devconnected.com/how-to-install-influxdb-on-ubuntu-debian-in-2019/#Option_2_Adding_the_repositories_to_your_package_manager) * [otro getting started](https://www.influxdata.com/blog/getting-started-with-influxdb-2-0-scraping-metrics-running-telegraf-querying-data-and-writing-data/) * [Python e Influxdb OSS 2.0](https://www.influxdata.com/blog/getting-started-with-python-and-influxdb-v2-0/) * [Docker compose: Grafana/Influx/Telegraf](https://dev.to/project42/install-grafana-influxdb-telegraf-using-docker-compose-56e9) * [idem](https://lazyadmin.nl/it/installing-grafana-influxdb-and-telegraf-using-docker/) * [idem, tiene dos partes](https://thenewstack.io/how-to-setup-influxdb-telegraf-and-grafana-on-docker-part-1/) * [Acumulados y caducidad: _Continous query and retention policy_](https://docs.influxdata.com/influxdb/v1.8/guides/downsample_and_retain/) ### Home assistant seguros * * * https://makezine.com/projects/mycroft-the-open-source-private-voice-assistant-on-raspberry-pi/> * Magpi vol 99, pag 54 ### Ejemplos del escenario básico que queremos implementar * [Visualizar datos con mqtt-influxdb-grafana (bridge-python)](https://diyi0t.com/visualize-mqtt-data-with-influxdb-and-grafana/) * [Una configuración con Telegraf](https://screenzone.eu/arduino-mqtt-sensor-grafana/) * [nilhcem (web)](http://nilhcem.com/iot/home-monitoring-with-mqtt-influxdb-grafana) ### Para revisar * [Pagina especializada en IoT](https://diyi0t.com/) *