MQTT es un protocolo ligero de mensajería basado en publicación y suscripción, pensado para intercambiar mensajes a través de un servidor central denominado broker.
Para el Internet de las Cosas, donde quizás queremos enviar la temperatura cada segundo o encender una luz instantáneamente, HTTP se queda corto. Aquí es donde entra MQTT (Message Queuing Telemetry Transport).
MQTT funciona como una radio.
- No envías el mensaje a un destinatario específico.
- Lo envías a un “canal” (Topic).
- Quien quiera escuchar ese canal, se suscribe.
- Un servidor central (Broker) se encarga de repartir el correo.
DeviceScript incluye un cliente MQTT en @devicescript/net que se conecta mediante TCP o TLS.
Configurar el broker
El cliente se crea con startMQTTClient. Para una prueba podemos indicar un broker público directamente; cualquier mensaje enviado será público, así que no incluyas información sensible.
- Host:
test.mosquitto.org - Protocol:
tcp - Port:
1883
Seguridad: En un proyecto real, usa un broker privado con autenticación y, si la placa dispone de memoria suficiente, TLS.
Publicar telemetría
Vamos a publicar la temperatura medida por un ESP32.
Primero creamos el cliente y después llamamos a publish(tema, mensaje).
import * as ds from "@devicescript/core"
import { startMQTTClient } from "@devicescript/net"
const mqtt = await startMQTTClient({
host: "broker.hivemq.com",
proto: "tcp",
port: 1883,
})
// Usamos un topic único para no mezclarlo con otros dispositivos.
// Estructura típica: casa / habitación / dispositivo / variable
const TOPIC_TELEMETRIA = "luisllamas/curso/esp32/estado"
console.log("Iniciando bucle MQTT...")
setInterval(async () => {
// 1. Creamos los datos
// Simulamos una temperatura (o leemos un sensor real AHT20 como vimos antes)
const temperatura = 20 + Math.random() * 5
// 2. Empaquetamos en JSON
// MQTT envía texto o bytes. JSON es el estándar para datos estructurados.
const payload = JSON.stringify({
temp: temperatura,
uptime: ds.millis(),
device: "ESP32-01"
})
// 3. Publicamos
try {
await mqtt.publish(TOPIC_TELEMETRIA, payload)
console.log(`Enviado: ${payload}`)
} catch (e) {
console.error("Error al publicar (¿Sin WiFi o Broker caído?)")
}
}, 5000) // Enviamos cada 5 segundosAl ejecutar el ejemplo, un cliente MQTT del ordenador podrá recibir los datos cada cinco segundos.
El cliente intenta reconectarse cuando detecta que ha perdido la conectividad. Puedes llamar a stop() si necesitas cerrar el socket y detener los reintentos.
Recibir comandos
Ahora queremos lo contrario: controlar un LED desde fuera. Para ello, nos suscribimos a un topic.
Cuando llegue un mensaje a ese canal, DeviceScript ejecutará nuestra función (callback).
import { startMQTTClient } from "@devicescript/net"
import { startLightBulb } from "@devicescript/servers"
import { pins } from "@dsboard/esp32_c3_devkit_m"
const mqtt = await startMQTTClient({
host: "broker.hivemq.com",
proto: "tcp",
port: 1883,
})
// Configuramos un LED
const led = startLightBulb({ pin: pins.GPIO2 })
// Topic de comandos
const TOPIC_COMANDOS = "luisllamas/curso/esp32/luz"
console.log(`Suscribiéndonos a ${TOPIC_COMANDOS}...`)
// Nos suscribimos. La función se ejecutará por cada mensaje recibido.
await mqtt.subscribe(TOPIC_COMANDOS, async (mensaje) => {
// 'mensaje' es un objeto Buffer con los datos recibidos
const texto = mensaje.content.toString()
console.log(`Comando recibido: ${texto}`)
// Lógica simple: "ON" / "OFF"
if (texto === "ON") {
await led.intensity.write(1)
} else if (texto === "OFF") {
await led.intensity.write(0)
} else {
// Si mandan un número (0-100), intentamos hacer dimming
const brillo = parseFloat(texto)
if (!isNaN(brillo)) {
await led.intensity.write(brillo / 100)
}
}
})Calidad de servicio y retención
Aunque DeviceScript abstrae mucho, a veces necesitamos afinar.
El método publish acepta opciones, aunque por defecto suele usar QoS 0 (Dispara y olvida).
La documentación archivada no promete todas las opciones de QoS y retención de un cliente MQTT completo. Comprueba la API de la versión que estés utilizando antes de basar en ellas un comportamiento crítico.
Ejemplo: termostato IoT
Vamos a juntar todo. Un dispositivo que:
- Lee temperatura (simulada o real).
- La envía por MQTT.
- Escucha un topic para saber la “temperatura objetivo” y enciende un relé si hace frío.
import { startMQTTClient } from "@devicescript/net"
import { startRelay } from "@devicescript/servers"
import { pins } from "@dsboard/esp32_c3_devkit_m"
const mqtt = await startMQTTClient({
host: "broker.hivemq.com",
proto: "tcp",
port: 1883,
})
// Hardware
const calefaccion = startRelay({ pin: pins.GPIO10 })
// Estado
let temperaturaActual = 20
let temperaturaObjetivo = 22 // Valor por defecto
// Topics
const TOPIC_STATUS = "casa/salon/termostato/status"
const TOPIC_SET = "casa/salon/termostato/set"
// 1. Suscripción: Escuchar cambios de consigna
await mqtt.subscribe(TOPIC_SET, async (msg) => {
const val = parseFloat(msg.content.toString())
if (!isNaN(val)) {
temperaturaObjetivo = val
console.log(`Nueva temperatura objetivo: ${temperaturaObjetivo}`)
// Forzamos una comprobación inmediata
await comprobarTermostato()
}
})
// 2. Lógica de control
async function comprobarTermostato() {
// Histéresis simple
if (temperaturaActual < temperaturaObjetivo - 0.5) {
await calefaccion.enabled.write(true)
} else if (temperaturaActual > temperaturaObjetivo + 0.5) {
await calefaccion.enabled.write(false)
}
}
// 3. Bucle principal (Lectura y Telemetría)
setInterval(async () => {
// Simular lectura de sensor (sube y baja aleatoriamente)
temperaturaActual += (Math.random() - 0.5)
await comprobarTermostato()
// Enviar estado
const estado = {
temp: temperaturaActual.toFixed(1),
target: temperaturaObjetivo,
calefaccion: await calefaccion.enabled.read()
}
await mqtt.publish(TOPIC_STATUS, JSON.stringify(estado))
}, 2000)