El seguimiento de vehículos con MQTT funciona bien porque el protocolo se diseñó exactamente para este tipo de enlace: dispositivos pequeños que envían mensajes pequeños con frecuencia por redes poco fiables y de pago por uso. En esta guía diseñarás una estructura de topics para una flota, elegirás payloads GPS compactos, ajustarás el keep alive y las sesiones para rastreadores celulares, detectarás vehículos desconectados y construirás un mapa en vivo en el navegador que se actualiza directamente desde suscripciones MQTT en ConnectMQ.
Por qué MQTT encaja con la telemática de flotas
Un rastreador celular en una furgoneta de reparto, ya sea en Bogotá, Ciudad de México o Valencia, se pasa la vida cambiando de celda, entrando en túneles y perdiendo cobertura en estacionamientos subterráneos. Cada reconexión cuesta tiempo, energía y datos. MQTT ayuda de varias formas:
- Una conexión persistente en lugar de una nueva petición HTTP por posición, de modo que cabeceras y handshakes se pagan una sola vez.
- Sobrecarga fija pequeña por mensaje, algo que importa cuando se factura cada kilobyte.
- Keep alive y Last Will dan al servidor una señal clara cuando un vehículo se queda en silencio.
- QoS 1 con sesiones persistentes permite que los comandos lleguen a un vehículo que perdió cobertura brevemente.
- Fan-out mediante suscripciones: la misma posición alimenta el mapa de un despachador, un servicio de geocercas y un escritor de histórico sin que el rastreador haga nada extra.
Para repasar las garantías de entrega, consulta los niveles de QoS de MQTT explicados.
Diseño de topics para el seguimiento de vehículos
Empieza con una jerarquía que refleje cómo consulta la gente los datos: por flota, después por vehículo y después por tipo de dato.
fleet/{fleetId}/vehicle/{vehicleId}/position posiciones GPS en vivo
fleet/{fleetId}/vehicle/{vehicleId}/status online/offline (retenido)
fleet/{fleetId}/vehicle/{vehicleId}/event contacto, frenada brusca, puerta abierta
fleet/{fleetId}/vehicle/{vehicleId}/diag tensión de batería, calidad de señal
fleet/{fleetId}/vehicle/{vehicleId}/cmd comandos al rastreador
Así, el mapa de un despachador puede suscribirse a fleet/north/vehicle/+/position, un servicio de alertas a fleet/+/vehicle/+/event y un panel de operaciones a fleet/+/vehicle/+/status. Mantén los identificadores cortos y estables: un ID de vehículo como v0142 en lugar de una matrícula completa con espacios. Evita poner información de identificación personal, como el nombre de un conductor, en los nombres de los topics.
Diseño del payload
Los mensajes de posición son la mayor parte de tu tráfico, así que merecen atención.
| Campo | Clave | Ejemplo | Notas |
|---|---|---|---|
| Marca de tiempo | t |
1792137600 |
Segundos Unix según la hora GNSS, no la hora de recepción del servidor |
| Latitud | la |
41.01224 |
5 decimales equivalen aproximadamente a un metro |
| Longitud | lo |
28.97601 |
Misma precisión |
| Velocidad | s |
48 |
km/h, entero |
| Rumbo | h |
270 |
Grados, entero |
| Calidad del fix | q |
3 |
0 = ninguno, 2 = 2D, 3 = 3D |
| Satélites | n |
9 |
Útil para filtrar fixes malos |
El payload resultante ocupa unos 90 bytes:
{"t":1792137600,"la":41.01224,"lo":28.97601,"s":48,"h":270,"q":3,"n":9}
Reglas prácticas:
- Incluye siempre la marca de tiempo del dispositivo. Los mensajes almacenados durante una zona sin cobertura llegan tarde; el mapa y el histórico deben ordenar por la hora del fix.
- Agrupa al vaciar el buffer. Al salir de un túnel, publica un mensaje con un array de fixes almacenados en lugar de cientos de mensajes individuales.
- Informa de forma adaptativa. Cada 5 a 15 segundos en movimiento, cada pocos minutos con el vehículo estacionado y de inmediato ante cambios de rumbo significativos.
- Descarta los fixes malos en el dispositivo. Si la calidad del fix es 0, no publiques ninguna posición.
Keep alive, sesiones y QoS en enlaces celulares
- Keep alive: de 60 a 120 segundos es un punto de partida razonable en redes celulares. Los valores más cortos detectan antes las caídas, pero mantienen el módem despierto; los timeouts del NAT del operador también pueden cerrar en silencio las conexiones inactivas, así que no lo alargues tanto que el mapeo NAT caduque antes.
- QoS: QoS 0 es aceptable para posiciones de alta frecuencia en las que el siguiente fix sustituye al anterior; QoS 1 para eventos y comandos que no se pueden perder.
- Sesiones persistentes: conéctate con clean start a false y una caducidad de sesión (MQTT 5), por ejemplo de 10 a 30 minutos, para que los comandos enviados durante una caída corta se entreguen al reconectar.
- Caducidad de mensajes: pon caducidad a comandos como "desbloquear puertas" para que un vehículo que vuelve una hora después no ejecute una instrucción antigua.
- Last Will: registra un will
offlineretenido en el topic de estado y publica unonlineretenido después de conectarte.
Simular un rastreador en Python
Antes de que llegue el hardware, simula un vehículo. Este script circula en línea, usa un Last Will y publica posiciones con paho-mqtt 2.x. Apunta al servidor MQTT público gratuito para que puedas ejecutarlo de inmediato; ese servidor es para pruebas y prototipos, así que para uso real cambia a tus propias credenciales y al puerto 8883.
import json
import math
import time
import uuid
import paho.mqtt.client as mqtt
HOST = "broker.connectmq.com"
PORT = 1883
PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:6]}"
VEHICLE = "v0142"
BASE = f"{PREFIX}/fleet/north/vehicle/{VEHICLE}"
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"sim-{VEHICLE}-{uuid.uuid4().hex[:6]}",
protocol=mqtt.MQTTv5)
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code, "| topic prefix:", PREFIX)
client.publish(f"{BASE}/status", "online", qos=1, retain=True)
client.on_connect = on_connect
client.connect(HOST, PORT, keepalive=90)
client.loop_start()
lat, lon, heading = 41.0100, 28.9700, 45.0
try:
while True:
lat += 0.0003 * math.cos(math.radians(heading))
lon += 0.0003 * math.sin(math.radians(heading))
heading = (heading + 3) % 360
fix = {"t": int(time.time()), "la": round(lat, 5), "lo": round(lon, 5),
"s": 42, "h": int(heading), "q": 3, "n": 9}
client.publish(f"{BASE}/position", json.dumps(fix), qos=0)
time.sleep(5)
except KeyboardInterrupt:
client.publish(f"{BASE}/status", "offline", qos=1, retain=True).wait_for_publish()
client.loop_stop()
client.disconnect()
Apunta el prefijo que se imprime; lo necesitarás para el mapa. El broker público permite 10 mensajes por segundo por cliente y 8 niveles de topic, así que el topic completo connectmq/demo/xxxxxx/fleet/north/vehicle/v0142/position (8 niveles) cabe justo. Acorta la jerarquía si añades más niveles. Recuerda que el área pública puede leerla cualquiera: usa solo coordenadas simuladas.
Un mapa de flota en vivo a partir de suscripciones
El mapa no es más que un cliente MQTT en el navegador que se suscribe a las posiciones y mueve marcadores. Esta página usa MQTT.js sobre Secure WebSocket y Leaflet.
// map.js - carga Leaflet y MQTT.js en tu página y añade un contenedor con id "map"
const PREFIX = "connectmq/demo/REPLACE_WITH_PREFIX";
const map = L.map("map").setView([41.01, 28.97], 13);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "OpenStreetMap contributors",
}).addTo(map);
const markers = new Map();
const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
clientId: "fleet-map-" + Math.random().toString(16).slice(2, 10),
protocolVersion: 5,
});
client.on("connect", () => {
client.subscribe(`${PREFIX}/fleet/+/vehicle/+/position`);
client.subscribe(`${PREFIX}/fleet/+/vehicle/+/status`);
});
client.on("message", (topic, payload) => {
const parts = topic.split("/");
const vehicleId = parts[parts.length - 2];
const kind = parts[parts.length - 1];
if (kind === "position") {
const fix = JSON.parse(payload.toString());
let m = markers.get(vehicleId);
if (!m) {
m = L.marker([fix.la, fix.lo]).addTo(map).bindTooltip(vehicleId);
markers.set(vehicleId, m);
}
m.setLatLng([fix.la, fix.lo]);
m.setTooltipContent(`${vehicleId} - ${fix.s} km/h`);
} else if (kind === "status") {
const m = markers.get(vehicleId);
if (m) m.setOpacity(payload.toString() === "online" ? 1 : 0.4);
}
});
En producción, autentica el mapa con credenciales emitidas por tu backend en lugar de incrustar una contraseña compartida en la página y, si hay muchos espectadores del mapa abiertos a la vez, plantéate un pequeño backend que se suscriba una sola vez y sirva las posiciones a los navegadores.
Almacenar el histórico y las geocercas
ConnectMQ es el broker, no una base de datos, así que el histórico y las geocercas se ejecutan como tus propios clientes MQTT. Un servicio en Python o Node.js suscrito a fleet/+/vehicle/+/position puede escribir en PostgreSQL con PostGIS o en una base de datos de series temporales, y un segundo servicio puede comprobar cada fix contra los polígonos de las geocercas y publicar en el topic de eventos. Con Serverless (Paid) o superior, las suscripciones compartidas como $share/geo/fleet/+/vehicle/+/position permiten que varias instancias se repartan la carga. Las reglas de alerta por umbral del portal también resultan prácticas para comprobaciones sencillas, como que la tensión de batería del diagnóstico baje de un límite.
Gestionar la falta de cobertura en el dispositivo
Los rastreadores pasan bastante tiempo sin cobertura, así que el firmware necesita un plan para ello. Mantén un buffer circular de fixes recientes en flash o en RAM, dimensionado para la ausencia de cobertura más larga que esperes, por ejemplo una hora de fixes a una frecuencia reducida. Cuando vuelva la conexión, publica primero la posición actual para que el mapa en vivo se actualice de inmediato y después vacía el buffer por lotes con sus marcas de tiempo originales. Los consumidores que escriben histórico deben hacer upsert por vehículo y marca de tiempo, para que un lote entregado dos veces con QoS 1 no cree puntos duplicados.
Checklist para producción
- Credenciales y client ID únicos por rastreador, TLS en el 8883.
- Marca de tiempo del dispositivo en cada fix; envío por lotes tras las caídas.
- Intervalo de envío adaptativo.
- Estado retenido con Last Will.
- Sesiones persistentes y caducidad de mensajes para los comandos.
- Nada de datos personales en los nombres de los topics.
- Backoff con jitter en la reconexión para toda la flota.
La parte ESP32 de un rastreador se trata en rastreador GPS con ESP32 y MQTT, y MQTT para apps móviles cubre las apps para conductores o despachadores. Para las opciones de hosting, consulta la guía de broker MQTT en la nube.
Sigue a tu flota con ConnectMQ
Crea un broker Serverless gratuito en app.connectmq.com sin tarjeta de crédito, apunta a él tus rastreadores o el simulador por TLS y observa cómo llegan las posiciones en el explorador de topics en vivo. ConnectMQ es un broker MQTT gestionado, con planes Serverless (Paid) y Dedicated a medida que crece tu flota.
Preguntas frecuentes
¿Con qué frecuencia debe publicar su posición por MQTT un rastreador de vehículos?
Cada 5 a 15 segundos en movimiento y cada pocos minutos con el vehículo estacionado es un punto de partida habitual. Ajústalo según tu caso de uso y tu plan de datos.
¿Qué QoS debo usar para las posiciones GPS?
QoS 0 suele bastar para posiciones frecuentes, porque el siguiente fix sustituye al anterior. Usa QoS 1 para eventos y comandos que deben llegar.
¿Cómo sé cuándo un vehículo se desconecta?
Registra un Last Will retenido con offline en el topic de estado del vehículo y publica un online retenido después de conectarte.
¿Puede ConnectMQ almacenar el histórico de ubicaciones?
No, el broker enruta mensajes. Ejecuta tu propio servicio suscriptor para escribir las posiciones en una base de datos como PostgreSQL con PostGIS.



