You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

685 lines
24 KiB

---
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
<!--more-->
{{< 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
* <https://discourse.nodered.org/t/mqtt-node-red-influxdb/34706>
* <https://diyi0t.com/visualize-mqtt-data-with-influxdb-and-grafana/>
* <https://dev.to/project42/install-grafana-influxdb-telegraf-using-docker-compose-56e9>
* <https://lazyadmin.nl/it/installing-grafana-influxdb-and-telegraf-using-docker/>
_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:
`<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:
```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/2020/03/17/private-by-design-free-and-private-voice-assistants/>
* <https://makezine.com/2020/08/03/set-up-your-own-private-smart-home/>
* https://makezine.com/projects/mycroft-the-open-source-private-voice-assistant-on-raspberry-pi/>
* Magpi vol 99, pag 54 <http://magpi.cc>
### 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/)
* <https://esphome.io/index.html#guides>