Um pipeline Jetson Nano MQTT permite rodar visão acelerada por GPU na borda e enviar apenas os resultados para a nuvem. Neste tutorial, você vai rodar detecção de objetos em uma NVIDIA Jetson, publicar detecções e a saúde do dispositivo em JSON via MQTT com TLS e transformar o script em um serviço que inicia no boot e se reconecta sozinho.
Por que Jetson mais MQTT
As placas NVIDIA Jetson combinam uma CPU Arm com uma GPU compatível com CUDA em um módulo pequeno e de baixo consumo. Isso as torna uma escolha popular para câmeras que precisam detectar pessoas, veículos ou defeitos em tempo real sem enviar vídeo para um data center.
A saída dessa câmera é a parte interessante: rótulos de objetos, posições, contagens e alertas. O MQTT é uma forma natural de entregar essa saída, porque é leve, lida bem com conexões instáveis e permite que qualquer número de consumidores assine. A Jetson publica uma vez, e dashboards, ferramentas de alerta, historiadores e outros serviços de IA recebem as mesmas mensagens.
Escolhendo uma Jetson e a pilha de software
| Placa | Geração do JetPack | Python padrão | Suporte a paho-mqtt 2.x | Observações |
|---|---|---|---|---|
| Jetson Nano (original, 4 GB) | JetPack 4.x | 3.6 | Não, use 1.6.x | Ainda muito usada, sem JetPack mais novo |
| Jetson Orin Nano | JetPack 5 ou 6 | 3.8 ou 3.10 | Sim | Escolha de entrada atual |
| Jetson Orin NX / AGX Orin | JetPack 5 ou 6 | 3.8 ou 3.10 | Sim | Mais GPU para modelos maiores |
Essa diferença de versão é a armadilha mais comum. O paho-mqtt 2.x exige Python 3.7 ou mais recente, então, em uma Jetson Nano original com JetPack 4.6, você instala o paho-mqtt 1.6.1 (e remove o argumento CallbackAPIVersion e o parâmetro properties dos callbacks) ou roda o seu código em um contêiner com um Python mais novo. Os exemplos abaixo visam o JetPack 5 ou 6 com paho-mqtt 2.x.
Para a inferência, este tutorial usa a biblioteca open source jetson-inference da NVIDIA, que encapsula modelos de detecção otimizados com TensorRT por trás de uma API Python curta e lida com câmeras CSI e USB. A parte MQTT funciona da mesma forma com qualquer outro framework de inferência, incluindo o YOLO via Ultralytics.
Passo 1: prepare a Jetson
Instale o jetson-inference seguindo a documentação dele (compilando a partir do código-fonte ou usando o contêiner Docker) e depois adicione o cliente MQTT:
sudo apt update
sudo apt install -y python3-pip mosquitto-clients
pip3 install "paho-mqtt>=2.0"
Confirme que a câmera funciona. Com o jetson-inference instalado:
video-viewer csi://0 # CSI camera such as a Raspberry Pi Camera Module v2
video-viewer /dev/video0 # USB webcam
Em seguida, verifique se a Jetson alcança o broker. Para um primeiro teste, use o servidor MQTT público em broker.connectmq.com via TLS, apenas com dados de teste:
mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
-i jetson-test-7d1e -t "connectmq/demo/jet-7d1e/hello" -m "jetson online"
Se falhar, verifique as regras de firewall de saída para a porta 8883 e se o relógio do sistema está correto; o TLS falha em uma Jetson cujo relógio esteja anos atrasado.
Passo 2: o publicador de detecções
Salve como jetson_mqtt.py. Ele captura frames, roda o detectNet na GPU e publica detecções com limite de taxa, contagens retidas por classe e um heartbeat com a taxa de frames e o desempenho do lado da GPU.
import json
import ssl
import time
import uuid
import paho.mqtt.client as mqtt
from jetson_inference import detectNet
from jetson_utils import videoSource
BROKER, PORT = "broker.connectmq.com", 8883
PREFIX = "connectmq/demo/jet-7d1e/site1/gate/cam1"
USERNAME, PASSWORD = None, None # set to "YOUR_USERNAME", "YOUR_PASSWORD" for a private broker
CLIENT_ID = f"jetson-cam1-{uuid.uuid4().hex[:6]}" # use YOUR_CLIENT_ID on a private broker
PUBLISH_EVERY = 0.5
HEARTBEAT_EVERY = 30
net = detectNet("ssd-mobilenet-v2", threshold=0.5)
camera = videoSource("csi://0") # or "/dev/video0", or "rtsp://..."
def on_connect(client, userdata, flags, reason_code, properties):
print("connected:", reason_code)
client.publish(f"{PREFIX}/status",
json.dumps({"online": True, "model": "ssd-mobilenet-v2"}),
qos=1, retain=True)
def on_disconnect(client, userdata, flags, reason_code, properties):
print("disconnected:", reason_code)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID,
protocol=mqtt.MQTTv5)
if USERNAME:
client.username_pw_set(USERNAME, PASSWORD)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.will_set(f"{PREFIX}/status", json.dumps({"online": False}), qos=1, retain=True)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.connect_async(BROKER, PORT, keepalive=30)
client.loop_start()
last_pub = last_beat = 0.0
last_counts = None
frames = 0
while True:
img = camera.Capture()
if img is None: # capture timeout
continue
detections = net.Detect(img, overlay="none")
frames += 1
objects, counts = [], {}
for d in detections:
label = net.GetClassDesc(d.ClassID)
counts[label] = counts.get(label, 0) + 1
objects.append({
"label": label,
"conf": round(d.Confidence, 2),
"box": [round(d.Left / img.width, 3), round(d.Top / img.height, 3),
round(d.Right / img.width, 3), round(d.Bottom / img.height, 3)],
})
now = time.time()
if now - last_pub >= PUBLISH_EVERY:
last_pub = now
client.publish(f"{PREFIX}/detections",
json.dumps({"ts": now, "objects": objects}), qos=0)
if counts != last_counts:
last_counts = counts
client.publish(f"{PREFIX}/counts", json.dumps({"ts": now, **counts}),
qos=1, retain=True)
if now - last_beat >= HEARTBEAT_EVERY:
fps = frames / (now - last_beat) if last_beat else 0.0
client.publish(f"{PREFIX}/health", json.dumps({
"ts": now, "fps": round(fps, 1),
"networkFps": round(net.GetNetworkFPS(), 1)}), qos=0)
last_beat, frames = now, 0
Acompanhe os resultados a partir do seu notebook:
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
-i jet-watch-7d1e -t "connectmq/demo/jet-7d1e/#" -v
connect_async mais loop_start fazem com que o script continue rodando a inferência mesmo quando a rede cai; o paho se reconecta em segundo plano, e o topic de status retido mais o Last Will informam a todos se a câmera está acessível.
Passo 3: adicione a saúde do dispositivo
Uma câmera que superaquece ou sofre throttling em silêncio é uma falha comum em campo. As placas Jetson informam temperaturas pelo sysfs, que você pode publicar junto com o heartbeat:
import glob
def read_temps():
temps = {}
for zone in glob.glob("/sys/class/thermal/thermal_zone*"):
try:
with open(f"{zone}/type") as t, open(f"{zone}/temp") as v:
temps[t.read().strip()] = int(v.read()) / 1000
except OSError:
pass
return temps
Adicione "temps": read_temps() ao payload de saúde. Em um broker privado, uma regra de alerta por limite em um campo de temperatura avisa você antes que a placa comece a reduzir o desempenho.
Passo 4: rode como serviço
Crie /etc/systemd/system/jetson-mqtt.service:
[Unit]
Description=Jetson vision to MQTT
After=network-online.target
Wants=network-online.target
[Service]
User=jetson
WorkingDirectory=/home/jetson/vision
ExecStart=/usr/bin/python3 /home/jetson/vision/jetson_mqtt.py
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now jetson-mqtt
journalctl -u jetson-mqtt -f
Projetando topics para muitas câmeras
Uma Jetson é fácil; vinte em três unidades exigem um plano. Coloque a hierarquia de localização no topic para que os consumidores possam assinar no nível certo: site/area/line-or-gate/camera/.... Um dashboard de segurança assina site1/+/+/alerts, um relatório de operações assina +/+/+/counts e um engenheiro depurando uma câmera assina apenas aquela câmera. É a mesma ideia de um Unified Namespace baseado em ISA-95, e o ConnectMQ reconhece essas estruturas de topics na sua visão de dispositivos.
Mantenha os tipos de mensagem separados, como mostrado no script: detecções de alta frequência, contagens retidas publicadas na mudança, saúde em baixa frequência e um status retido com o Last Will. Misturá-los em um único topic obriga todo consumidor a interpretar e filtrar tudo e impede que você atribua níveis de QoS diferentes a tipos de mensagem diferentes.
Dicas de desempenho
A maior parte da taxa de frames em uma Jetson vem do motor de inferência, não do Python nem do MQTT. Alguns hábitos mantêm o pipeline rápido:
- Use modelos otimizados com TensorRT; o jetson-inference compila e armazena o engine em cache na primeira execução, então a primeira inicialização é lenta e as seguintes são rápidas.
- Evite desenhar overlays (
overlay="none") quando ninguém estiver assistindo ao vídeo. - Limite a taxa de publicação como no script; codificar JSON a cada frame a 30 fps aumenta a carga da CPU sem nenhum benefício.
- Use
sudo jetson_clockssomente quando houver refrigeração adequada e monitore as temperaturas pelo topic de saúde.
Checklist de solução de problemas do Jetson Nano MQTT
TypeErrorsobreCallbackAPIVersion. Você está no paho-mqtt 1.x; atualize ou, na Nano original, use a API 1.6.- Erros de handshake TLS. Verifique a data do sistema (
timedatectl) e se oca-certificatesestá instalado. - Desconexões repetidas com o mesmo motivo. Dois dispositivos compartilham um client ID; todo cliente precisa de um ID único e, no ConnectMQ, o client ID fica vinculado às credenciais emitidas.
- Taxa de frames baixa depois de um tempo. Confira as temperaturas publicadas; adicione um ventilador ou selecione deliberadamente um modo de energia mais baixo com
nvpmodel. - Inundação de mensagens. Limite a taxa de detecções; no servidor MQTT público, o limite é de 10 mensagens por segundo por cliente.
- Câmera não encontrada sob o systemd. O usuário do serviço precisa estar no grupo
video.
Do protótipo à frota
Quando uma câmera estiver funcionando, as perguntas passam a ser operacionais: credenciais por dispositivo, criptografia e saber quais câmeras estão online. Saia do servidor MQTT público para um broker MQTT gerenciado privado assim que imagens ou localizações reais estiverem envolvidas; imagens de pessoas, por exemplo, são dados pessoais sob a LGPD. O ConnectMQ dá a cada Jetson as suas próprias credenciais geradas, aceita TLS na porta 8883, lista os dispositivos por nível ISA-95 e mostra cada detecção no explorador de topics ao vivo. Quando muitas Jetsons em unidades diferentes, como frigoríficos ou centros de distribuição em vários estados, reportam para um só lugar, um broker MQTT na nuvem evita abrir portas de entrada em qualquer unidade.
Para uma versão deste pipeline independente de modelo, veja detecção de objetos com YOLO via MQTT; para uma alternativa do tamanho de um microcontrolador, veja detecção de pessoas com ESP32-CAM; e, para escolher entre Jetson, Coral e outros aceleradores, leia hardware para edge AI. O guia de IA e IoT cobre o panorama completo.
Conecte a sua Jetson ao ConnectMQ
Crie um broker MQTT gerenciado no plano Serverless gratuito em app.connectmq.com, sem cartão de crédito, e depois defina BROKER, USERNAME, PASSWORD e CLIENT_ID no script com os valores gerados. As detecções da sua Jetson aparecerão no portal em segundos.
Perguntas frequentes
O paho-mqtt funciona na Jetson Nano original?
Sim, mas o JetPack 4.x vem com Python 3.6, então use o paho-mqtt 1.6.1 e a sua API de callbacks mais antiga, ou rode um contêiner com Python 3.7 ou mais recente para usar o paho-mqtt 2.x.
A Jetson deve publicar frames de vídeo via MQTT?
Em geral, não. Publique detecções, contagens e snapshots ocasionais dentro do limite de tamanho de mensagem e use um protocolo de streaming se precisar de vídeo ao vivo.
Qual porta uma Jetson deve usar para MQTT?
Use a 8883 com TLS para qualquer coisa além de um teste rápido. A porta 1883 não é criptografada e deve se limitar ao desenvolvimento com dados sintéticos.
Posso rodar o MQTT e a inferência no mesmo processo Python?
Sim. O loop de rede do paho roda em uma thread em segundo plano via loop_start, então a inferência no loop principal não é bloqueada pelas operações de rede.



