IoT e IA

Jetson Nano MQTT: Publique Resultados de Visão na Borda com GPU de Forma Segura

Monte um pipeline de visão na borda em uma NVIDIA Jetson: rode detecção acelerada por GPU, publique resultados JSON compactos e a saúde do dispositivo via MQTT com TLS e execute tudo como um serviço que sobrevive a reinicializações.

Ilustração Jetson Nano MQTT com uma placa NVIDIA Jetson, uma câmera CSI e resultados de detecção enviados via TLS a um broker MQTT na nuvem
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

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_clocks somente quando houver refrigeração adequada e monitore as temperaturas pelo topic de saúde.

Checklist de solução de problemas do Jetson Nano MQTT

  1. TypeError sobre CallbackAPIVersion. Você está no paho-mqtt 1.x; atualize ou, na Nano original, use a API 1.6.
  2. Erros de handshake TLS. Verifique a data do sistema (timedatectl) e se o ca-certificates está instalado.
  3. 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.
  4. 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.
  5. Inundação de mensagens. Limite a taxa de detecções; no servidor MQTT público, o limite é de 10 mensagens por segundo por cliente.
  6. 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.

Artigos relacionados

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.