MQTT is a lightweight messaging protocol based on publish and subscribe, designed to exchange messages through a central server called the broker.
For the Internet of Things, where we might want to send temperature every second or instantly turn on a light, HTTP falls short. This is where MQTT (Message Queuing Telemetry Transport) comes in.
MQTT works like a radio.
- You don’t send a message to a specific recipient.
- You send it to a “channel” (Topic).
- Whoever wants to listen to that channel subscribes to it.
- A central server (Broker) is responsible for delivering the mail.
DeviceScript includes an MQTT client in @devicescript/net that connects via TCP or TLS.
Configure the Broker
The client is created with startMQTTClient. For a test, we can specify a public broker directly; any message sent will be public, so don’t include sensitive information.
- Host:
test.mosquitto.org - Protocol:
tcp - Port:
1883
Security: In a real project, use a private broker with authentication and, if the board has enough memory, TLS.
Publishing Telemetry
We’ll publish the temperature measured by an ESP32.
First, we create the client and then call publish(topic, message).
import * as ds from "@devicescript/core"
import { startMQTTClient } from "@devicescript/net"
const mqtt = await startMQTTClient({
host: "broker.hivemq.com",
proto: "tcp",
port: 1883,
})
// We use a unique topic to avoid mixing it with other devices.
// Typical structure: house / room / device / variable
const TOPIC_TELEMETRY = "luisllamas/curso/esp32/status"
console.log("Starting MQTT loop...")
setInterval(async () => {
// 1. Create the data
// Simulate a temperature (or read a real AHT20 sensor as we saw earlier)
const temperature = 20 + Math.random() * 5
// 2. Package in JSON
// MQTT sends text or bytes. JSON is the standard for structured data.
const payload = JSON.stringify({
temp: temperature,
uptime: ds.millis(),
device: "ESP32-01"
})
// 3. Publish
try {
await mqtt.publish(TOPIC_TELEMETRY, payload)
console.log(`Sent: ${payload}`)
} catch (e) {
console.error("Error publishing (WiFi down or Broker down?)")
}
}, 5000) // Send every 5 secondsWhen running the example, an MQTT client on the computer will be able to receive data every five seconds.
The client attempts to reconnect when it detects lost connectivity. You can call stop() if you need to close the socket and stop retries.
Receiving Commands
Now we want the opposite: to control an LED from outside. To do this, we subscribe to a topic.
When a message arrives on that channel, DeviceScript will execute our function (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,
})
// Configure an LED
const led = startLightBulb({ pin: pins.GPIO2 })
// Command topic
const TOPIC_COMMANDS = "luisllamas/curso/esp32/light"
console.log(`Subscribing to ${TOPIC_COMMANDS}...`)
// Subscribe. The function will execute for each received message.
await mqtt.subscribe(TOPIC_COMMANDS, async (message) => {
// 'message' is a Buffer object with the received data
const text = message.content.toString()
console.log(`Command received: ${text}`)
// Simple logic: "ON" / "OFF"
if (text === "ON") {
await led.intensity.write(1)
} else if (text === "OFF") {
await led.intensity.write(0)
} else {
// If they send a number (0-100), try dimming
const brightness = parseFloat(text)
if (!isNaN(brightness)) {
await led.intensity.write(brightness / 100)
}
}
})Quality of Service and Retention
Although DeviceScript abstracts a lot, sometimes we need to fine-tune.
The publish method accepts options, although by default it usually uses QoS 0 (Fire and forget).
The archived documentation doesn’t promise all the QoS and retention options of a full MQTT client. Check the API of the version you are using before basing critical behavior on it.
Example: IoT Thermostat
Let’s put it all together. A device that:
- Reads temperature (simulated or real).
- Sends it via MQTT.
- Listens to a topic to know the “target temperature” and turns on a relay if it’s cold.
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 heating = startRelay({ pin: pins.GPIO10 })
// State
let currentTemperature = 20
let targetTemperature = 22 // Default value
// Topics
const TOPIC_STATUS = "house/livingroom/thermostat/status"
const TOPIC_SET = "house/livingroom/thermostat/set"
// 1. Subscription: Listen for setpoint changes
await mqtt.subscribe(TOPIC_SET, async (msg) => {
const val = parseFloat(msg.content.toString())
if (!isNaN(val)) {
targetTemperature = val
console.log(`New target temperature: ${targetTemperature}`)
// Force an immediate check
await checkThermostat()
}
})
// 2. Control logic
async function checkThermostat() {
// Simple hysteresis
if (currentTemperature < targetTemperature - 0.5) {
await heating.enabled.write(true)
} else if (currentTemperature > targetTemperature + 0.5) {
await heating.enabled.write(false)
}
}
// 3. Main loop (Reading and Telemetry)
setInterval(async () => {
// Simulate sensor reading (goes up and down randomly)
currentTemperature += (Math.random() - 0.5)
await checkThermostat()
// Send status
const status = {
temp: currentTemperature.toFixed(1),
target: targetTemperature,
heating: await heating.enabled.read()
}
await mqtt.publish(TOPIC_STATUS, JSON.stringify(status))
}, 2000)