Tutoriales

MQTT en Raspberry Pi: de los sensores al broker en 15 minutos

Publica lecturas de un BME280 o DHT22 desde una Raspberry Pi a un broker MQTT con Python y paho-mqtt 2.x, y ejecútalo como servicio systemd tras cada reinicio.

Actualizado · 8 min de lectura

MQTT en Raspberry Pi: de los sensores al broker en 15 minutos

Llevar las lecturas de un sensor de una Raspberry Pi a MQTT requiere tres piezas: una biblioteca para el sensor, el cliente paho-mqtt y un gestor de servicios que mantenga vivo el script. Este tutorial conecta un BME280 (o un DHT22) a una Pi, publica temperatura, humedad y presión en JSON cada 10 segundos y ejecuta el publicador como un servicio systemd que se reconecta solo y arranca con el sistema.

Todo lo que sigue está escrito para Raspberry Pi OS Bookworm (o más reciente) con Python 3.11+, paho-mqtt 2.x y los drivers de sensores CircuitPython de Adafruit. Funciona en Pi Zero 2 W, Pi 3, Pi 4 y Pi 5.

Lo que necesitas

ElementoNotas
Raspberry Pi con redWi-Fi o Ethernet, SSH activado
Placa BME280 (I2C)Temperatura, humedad, presión. Dirección 0x76 o 0x77
o DHT22 / AM2302Temperatura y humedad en un solo pin GPIO
4 cables dupont3,3 V, GND, SDA, SCL (BME280)
Un broker MQTTCualquier broker accesible; los ejemplos usan broker.connectmq.com

Conecta el BME280 a 3V3 (pin 1), GND (pin 6), SDA (pin 3, GPIO2) y SCL (pin 5, GPIO3). Un DHT22 va a 3V3, GND y un pin de datos como GPIO4 (pin 7), con una resistencia pull-up de 10 kΩ si tu módulo no la incluye ya.

No necesitas un broker en la Pi. Si aun así quieres uno ahí, consulta ejecutar un broker en Raspberry Pi; este artículo solo cubre el lado cliente.

1. Activa I2C y comprueba el sensor

Activar I2C y encontrar el sensor
sudo raspi-config nonint do_i2c 0
sudo apt update && sudo apt install -y i2c-tools python3-venv
i2cdetect -y 1

i2cdetect muestra una cuadrícula. Deberías ver 76 o 77: es la dirección del BME280. Una cuadrícula vacía indica un problema de cableado (SDA y SCL intercambiados es el clásico) o que I2C aún no está activo; reinicia una vez después de raspi-config si hace falta.

2. Crea un entorno virtual

Bookworm rechaza pip install a nivel de sistema con error: externally-managed-environment. Usa un venv en una carpeta de proyecto:

Preparar el proyecto
mkdir -p ~/sensors && cd ~/sensors
python3 -m venv .venv
.venv/bin/pip install paho-mqtt adafruit-circuitpython-bme280
# para un DHT22:
# .venv/bin/pip install paho-mqtt adafruit-circuitpython-dht

Los drivers de Adafruit instalan Blinka, la capa de compatibilidad que ofrece board y busio en Linux.

Una comprobación rápida antes de escribir código MQTT:

Leer el sensor una vez
.venv/bin/python -c "import board; from adafruit_bme280 import basic as b; s=b.Adafruit_BME280_I2C(board.I2C(), address=0x76); print(s.temperature, s.relative_humidity, s.pressure)"

Si obtienes ValueError: No I2C device at address: 0x76, prueba con address=0x77.

3. El script publicador

El script se conecta con un Last Will para que los suscriptores sepan cuándo la Pi se desconecta, publica un estado online retenido y luego envía una lectura JSON cada 10 segundos. Las credenciales vienen de variables de entorno, así que el mismo archivo sirve en tu escritorio y en producción.

publish.py
import json, os, socket, time
import board
import paho.mqtt.client as mqtt
from adafruit_bme280 import basic as adafruit_bme280

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "8883"))
DEVICE = socket.gethostname()
BASE = f"testmqtt/pi/{DEVICE}"
INTERVAL = 10

sensor = adafruit_bme280.Adafruit_BME280_I2C(board.I2C(), address=0x76)

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"conexión fallida: {reason_code}", flush=True)
        return
    print("conectado", flush=True)
    client.publish(f"{BASE}/status", "online", qos=1, retain=True)

def on_disconnect(client, userdata, flags, reason_code, properties):
    print(f"desconectado: {reason_code}", flush=True)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"pi-{DEVICE}")
client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
if PORT == 8883:
    client.tls_set()                      # almacén de CA del sistema
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.on_connect = on_connect
client.on_disconnect = on_disconnect

client.connect_async(HOST, PORT, keepalive=60)
client.loop_start()                       # hilo de red, reintenta hasta conectar

while True:
    reading = {
        "temperature": round(sensor.temperature, 2),
        "humidity": round(sensor.relative_humidity, 1),
        "pressure": round(sensor.pressure, 1),
        "ts": int(time.time()),
    }
    info = client.publish(f"{BASE}/bme280", json.dumps(reading), qos=1)
    print(f"publish mid={info.mid} rc={info.rc} {reading}", flush=True)
    time.sleep(INTERVAL)

Algunas decisiones que conviene explicar:

  • connect_async() + loop_start(): al arrancar, puede que la red aún no esté lista. connect() lanzaría una excepción al instante; connect_async() deja que el hilo en segundo plano de paho siga reintentando, y ese mismo hilo se reconecta tras cualquier caída posterior con el backoff definido en reconnect_delay_set().
  • QoS 1 para las lecturas, para que un corte breve no pierda datos en silencio. paho encola los mensajes QoS 1 mientras está desconectado y los envía al recuperar la conexión. QoS 0 sería más ligero, pero descarta todo lo enviado durante un corte.
  • Estado retenido + Last Will: cualquiera que se suscriba después ve de inmediato online u offline. El patrón se explica en MQTT Last Will.
  • Client ID a partir del hostname: estable y único por Pi. Dos dispositivos con el mismo ID se expulsan mutuamente del broker en bucle.

Usar un DHT22 en su lugar

El protocolo del DHT22 es sensible a los tiempos en Linux, así que las lecturas fallan con frecuencia con RuntimeError (por ejemplo "Checksum did not validate" o "A full buffer was not returned"). Es normal: descarta la muestra y vuelve a intentarlo:

lectura dht22 (sustituye la parte del sensor)
import adafruit_dht
dht = adafruit_dht.DHT22(board.D4)

def read_dht():
    for _ in range(5):
        try:
            return {"temperature": dht.temperature, "humidity": dht.humidity}
        except RuntimeError:
            time.sleep(2)              # el DHT22 necesita ~2 s entre lecturas
    return None                        # esta vuelta no se publica nada

Llama a read_dht() en el bucle principal y publica solo cuando devuelva un valor.

4. Pruébalo a mano

Guarda las credenciales en un archivo que solo tu usuario pueda leer y ejecuta el script en primer plano:

~/sensors/mqtt.env
MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USER=username
MQTT_PASS=password
Ejecutar una vez
chmod 600 ~/sensors/mqtt.env
set -a; . ~/sensors/mqtt.env; set +a
.venv/bin/python publish.py

Deberías ver conectado seguido de una línea publish mid=... cada 10 segundos. Ahora obsérvalo desde otra máquina:

Suscribirse desde tu portátil
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t 'testmqtt/pi/#' -v

O sáltate la instalación y abre el cliente MQTT online en tu navegador, conéctate al mismo broker y suscríbete a testmqtt/pi/#. Verás primero el mensaje status retenido y después un payload JSON bme280 nuevo en cada intervalo.

Ver las lecturas de tu Pi en el navegador

Si no llega nada, revisa el reason code impreso. Bad user name or password y Not authorized son rechazos del broker; búscalos en la herramienta de reason codes MQTT. Un timeout suele indicar que el puerto está bloqueado: la guía de errores de conexión repasa cada caso.

5. Ejecútalo como servicio systemd

Un script lanzado en una sesión SSH muere al cerrar la sesión. systemd lo inicia al arrancar, lo reinicia si falla y recoge su salida en el journal.

/etc/systemd/system/bme280-mqtt.service
[Unit]
Description=BME280 to MQTT publisher
Wants=network-online.target
After=network-online.target

[Service]
User=pi
WorkingDirectory=/home/pi/sensors
EnvironmentFile=/home/pi/sensors/mqtt.env
ExecStart=/home/pi/sensors/.venv/bin/python -u /home/pi/sensors/publish.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Sustituye pi por tu nombre de usuario real: las imágenes recientes de Raspberry Pi OS te obligan a elegir uno durante la configuración. El usuario debe pertenecer al grupo i2c (o gpio para un DHT22); el usuario por defecto ya está en ellos.

Activar y seguir los logs
sudo systemctl daemon-reload
sudo systemctl enable --now bme280-mqtt
journalctl -u bme280-mqtt -f

-u en la línea de comandos de Python desactiva el buffer de salida para que las líneas aparezcan en el journal al instante (el flush=True del script hace lo mismo).

Demuestra que sobrevive a los fallos

  1. sudo reboot: las lecturas deberían volver en menos de un minuto, sin ninguna sesión SSH abierta.
  2. Desconecta la red durante un minuto. Los suscriptores ven offline cuando el broker detecta la falta de keep-alive (hasta 1,5 × 60 segundos), luego online otra vez tras reconectar, seguido de las lecturas encoladas durante el corte.
  3. sudo systemctl kill bme280-mqtt: systemd reinicia el script a los 5 segundos.

Próximos pasos

  • Da forma al payload para dashboards y bases de datos: payloads JSON en Python.
  • Añade más sensores y una pantalla: la estación meteorológica MQTT parte de este script.
  • Alimenta Home Assistant publicando mensajes de discovery, como se explica en Home Assistant MQTT.

Una Pi, un sensor y cuarenta líneas de Python bastan para una telemetría fiable siempre que resuelvas las partes aburridas: un venv para las dependencias, un client ID estable, un Last Will para la presencia y systemd para los reinicios. Prueba cada fallo una vez en tu mesa y la Pi del jardín se comportará igual.

Preguntas frecuentes

¿Necesito ejecutar un broker MQTT en la propia Raspberry Pi?

No. La Pi solo necesita una biblioteca cliente como paho-mqtt. Puede publicar en cualquier broker accesible: uno instalado en la misma Pi, otra máquina de tu red local o un broker gestionado en la nube.

¿Por qué pip falla con "externally-managed-environment" en Raspberry Pi OS?

Raspberry Pi OS Bookworm y posteriores protegen el Python del sistema (PEP 668). Crea un entorno virtual con python3 -m venv, instala ahí paho-mqtt y las bibliotecas del sensor y apunta tu servicio systemd al python del venv.

BME280 o DHT22: ¿qué sensor uso con MQTT?

El BME280 usa I2C, lee de forma fiable siempre y añade la presión atmosférica. El DHT22 es barato, pero su protocolo de un solo hilo es sensible a los tiempos, así que hay lecturas que fallan y tu código debe reintentar. Para telemetría continua, el BME280 da menos problemas.