Apuntes de Domótica

Nota
Este artículo se actualizó por última vez el 2022-04-22, es posible que el contenido no esté actualizado.

Apuntes no terminados sobre Domótica Libre

Work in progress
Estos apuntes no están completos, (ni de lejos)

Grupo de investigación

Hardware para empezar

  • Wemos: este 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
  • Pantalla OLED Como esta compatible con la mecánica Wemos
  • Módulo de gestión de batería Como este.Para alimentar el sensor con una batería de 3,3 volt

Ver la tabla de componentes aquí

MQTT

Message Queuing Telemetry Transport, 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 (Internet of Things)

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.

1
2
3
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:

1
2
$ 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)

1
2
3
4
5
6
7
8
9
$ 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:

1
2
3
$ 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:

1
2
3
4
5
6
7
8
9
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

<measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [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:

1
2
3
4
5
6
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> 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 node version manager.

Para ello ejecutamos el siguiente comando (la versión actual de nvm es la 0.37.0)

1
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

1
2
3
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

1
2
3
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

1
2
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. Para proceder a la instalación se puede consultar la guía de postinstalación de Linux Mint 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 (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

Referencias MQTT

Referencias Influxdb

Home assistant seguros

Ejemplos del escenario básico que queremos implementar

Para revisar