IoT et IA

Jetson Nano MQTT : publier en toute sécurité les résultats de vision edge sur GPU

Construisez un pipeline de vision edge sur NVIDIA Jetson : exécutez une détection accélérée par GPU, publiez des résultats JSON compacts et l'état de santé de l'équipement via MQTT avec TLS, et faites-en un service qui survit aux redémarrages.

Illustration Jetson Nano MQTT avec une carte NVIDIA Jetson, une caméra CSI et des résultats de détection envoyés en TLS vers un broker MQTT cloud
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Un pipeline Jetson Nano MQTT vous permet d'exécuter une vision accélérée par GPU en edge et de n'envoyer que les résultats vers le cloud. Dans ce tutoriel, vous allez exécuter une détection d'objets sur un NVIDIA Jetson, publier les détections et l'état de santé de l'équipement en JSON via MQTT avec TLS, et transformer le script en service qui démarre au boot et se reconnecte tout seul.

Pourquoi associer Jetson et MQTT

Les cartes NVIDIA Jetson associent un CPU Arm à un GPU compatible CUDA dans un module compact et basse consommation. Elles sont donc un choix populaire pour les caméras qui doivent détecter des personnes, des véhicules ou des défauts en temps réel sans envoyer de vidéo vers un data center.

La sortie d'une telle caméra est la partie intéressante : labels d'objets, positions, comptages et alertes. MQTT est un moyen naturel de livrer cette sortie, car il est léger, gère bien les liaisons peu fiables et permet à autant de consommateurs que nécessaire de s'abonner. Le Jetson publie une seule fois, et tableaux de bord, outils d'alerte, historians et autres services d'IA reçoivent tous les mêmes messages.

Choisir un Jetson et une pile logicielle

Carte Génération JetPack Python par défaut Prise en charge de paho-mqtt 2.x Remarques
Jetson Nano (original, 4 Go) JetPack 4.x 3.6 Non, utilisez 1.6.x Encore très répandu, pas de JetPack plus récent
Jetson Orin Nano JetPack 5 ou 6 3.8 ou 3.10 Oui Choix d'entrée de gamme actuel
Jetson Orin NX / AGX Orin JetPack 5 ou 6 3.8 ou 3.10 Oui Plus de GPU pour les modèles plus grands

Cette différence de version est le piège le plus courant. paho-mqtt 2.x nécessite Python 3.7 ou plus récent : sur un Jetson Nano original sous JetPack 4.6, vous devez donc soit installer paho-mqtt 1.6.1 (et supprimer l'argument CallbackAPIVersion ainsi que le paramètre properties des callbacks), soit exécuter votre code dans un conteneur doté d'un Python plus récent. Les exemples ci-dessous ciblent JetPack 5 ou 6 avec paho-mqtt 2.x.

Pour l'inférence, ce tutoriel utilise la bibliothèque open source jetson-inference de NVIDIA, qui encapsule des modèles de détection optimisés par TensorRT derrière une courte API Python et gère les caméras CSI et USB. La partie MQTT fonctionne de la même façon avec n'importe quel autre framework d'inférence, y compris YOLO via Ultralytics.

Étape 1 : préparer le Jetson

Installez jetson-inference en suivant sa documentation (compilation depuis les sources ou utilisation de son conteneur Docker), puis ajoutez le client MQTT :

sudo apt update
sudo apt install -y python3-pip mosquitto-clients
pip3 install "paho-mqtt>=2.0"

Vérifiez que la caméra fonctionne. Avec jetson-inference installé :

video-viewer csi://0          # caméra CSI comme le Raspberry Pi Camera Module v2
video-viewer /dev/video0      # webcam USB

Vérifiez ensuite que le Jetson peut joindre le broker. Pour un premier test, utilisez le serveur MQTT public gratuit broker.connectmq.com en TLS, réservé aux tests et aux prototypes :

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"

En cas d'échec, vérifiez les règles de pare-feu sortantes pour le port 8883 et l'exactitude de l'horloge système ; le TLS échoue sur un Jetson dont l'horloge a plusieurs années de retard.

Étape 2 : le publieur de détections

Enregistrez ce fichier sous jetson_mqtt.py. Il capture les images, exécute detectNet sur le GPU, publie des détections à cadence limitée, des comptages par classe retenus et un heartbeat avec la fréquence d'images et les performances côté 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  # "YOUR_USERNAME", "YOUR_PASSWORD" pour un broker privé
CLIENT_ID = f"jetson-cam1-{uuid.uuid4().hex[:6]}"  # utilisez YOUR_CLIENT_ID sur un broker privé
PUBLISH_EVERY = 0.5
HEARTBEAT_EVERY = 30

net = detectNet("ssd-mobilenet-v2", threshold=0.5)
camera = videoSource("csi://0")  # ou "/dev/video0", ou "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:  # délai de capture dépassé
        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

Suivez les résultats depuis votre ordinateur portable :

mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -i jet-watch-7d1e -t "connectmq/demo/jet-7d1e/#" -v

connect_async associé à loop_start permet au script de poursuivre l'inférence même lorsque le réseau est coupé ; paho se reconnecte en arrière-plan, et le topic de statut retenu ainsi que le Last Will indiquent à tous si la caméra est joignable.

Étape 3 : ajouter l'état de santé de l'équipement

Une caméra qui surchauffe ou se bride sans prévenir est une panne courante sur le terrain. Les cartes Jetson exposent leurs températures via sysfs, que vous pouvez publier avec le 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

Ajoutez "temps": read_temps() au payload de santé. Sur un broker privé, une règle d'alerte à seuil sur un champ de température vous avertit avant que la carte ne commence à se brider.

Étape 4 : exécuter en tant que service

Créez /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

Concevoir les topics pour de nombreuses caméras

Un Jetson, c'est facile ; vingt répartis sur trois sites exigent un plan. Intégrez la hiérarchie des emplacements dans le topic afin que les consommateurs puissent s'abonner au bon niveau : site/area/line-or-gate/camera/.... Un tableau de bord de sécurité s'abonne à site1/+/+/alerts, un rapport d'exploitation à +/+/+/counts, et un ingénieur qui débogue une caméra s'abonne uniquement à celle-ci. C'est la même idée qu'un Unified Namespace fondé sur ISA-95, et ConnectMQ reconnaît ces structures de topics dans sa vue des équipements.

Séparez les types de messages comme dans le script : détections à haute fréquence, comptages retenus publiés sur changement, santé à basse fréquence et statut retenu avec le Last Will. Les mélanger dans un seul topic oblige chaque consommateur à tout analyser et filtrer, et empêche d'attribuer des niveaux de QoS différents aux différents types de messages.

Conseils de performance

L'essentiel de la fréquence d'images sur un Jetson dépend du moteur d'inférence, et non de Python ou de MQTT. Quelques habitudes gardent le pipeline rapide :

  • Utilisez des modèles optimisés par TensorRT ; jetson-inference construit et met en cache le moteur à la première exécution, si bien que le premier démarrage est lent et les suivants rapides.
  • Évitez de dessiner des surimpressions (overlay="none") lorsque personne ne regarde la vidéo.
  • Limitez la cadence de publication comme dans le script ; encoder du JSON pour chaque image à 30 fps ajoute de la charge CPU sans aucun bénéfice.
  • N'utilisez sudo jetson_clocks que si le refroidissement est suffisant, et surveillez les températures via le topic de santé.

Liste de dépannage Jetson Nano MQTT

  1. TypeError concernant CallbackAPIVersion. Vous utilisez paho-mqtt 1.x ; mettez à jour, ou sur le Nano original, utilisez l'API 1.6.
  2. Erreurs de handshake TLS. Vérifiez la date du système (timedatectl) et que ca-certificates est installé.
  3. Déconnexions répétées avec la même raison. Deux équipements partagent un client ID ; chaque client doit avoir un identifiant unique, et sur ConnectMQ le client ID est lié aux identifiants émis.
  4. Fréquence d'images en baisse au bout d'un moment. Vérifiez les températures publiées ; ajoutez un ventilateur ou choisissez délibérément un mode de puissance inférieur avec nvpmodel.
  5. Avalanche de messages. Limitez la cadence des détections ; sur le serveur MQTT public gratuit, la limite est de 10 messages par seconde et par client.
  6. Caméra introuvable sous systemd. L'utilisateur du service doit appartenir au groupe video.

Du prototype au parc

Une fois qu'une caméra fonctionne, les questions deviennent opérationnelles : identifiants par équipement, chiffrement, et visibilité sur les caméras en ligne. Passez du serveur MQTT public gratuit à un broker MQTT managé privé dès que de vraies images ou de vrais emplacements sont en jeu. ConnectMQ attribue à chaque Jetson ses propres identifiants générés, accepte le TLS sur le port 8883, liste les équipements par niveau ISA-95 et affiche chaque détection dans l'explorateur de topics en direct. Lorsque de nombreux Jetson répartis sur différents sites remontent vers un même point, un broker MQTT cloud évite d'ouvrir des ports entrants sur chaque site.

Pour une version de ce pipeline indépendante du modèle, consultez la détection d'objets YOLO via MQTT ; pour une alternative à l'échelle d'un microcontrôleur, consultez la détection de personnes avec l'ESP32-CAM ; et pour choisir entre Jetson, Coral et d'autres accélérateurs, lisez le matériel pour l'edge AI. Le guide IA et IoT offre une vue d'ensemble.

Connectez votre Jetson à ConnectMQ

Créez un broker avec l'offre gratuite Serverless sur app.connectmq.com, sans carte bancaire, puis renseignez dans le script BROKER, USERNAME, PASSWORD et CLIENT_ID avec les valeurs générées. Les détections de votre Jetson apparaîtront dans le portail en quelques secondes.

Questions fréquentes

paho-mqtt fonctionne-t-il sur le Jetson Nano original ?

Oui, mais JetPack 4.x est fourni avec Python 3.6 : utilisez donc paho-mqtt 1.6.1 et son ancienne API de callbacks, ou exécutez un conteneur avec Python 3.7 ou plus récent pour paho-mqtt 2.x.

Le Jetson doit-il publier des images vidéo via MQTT ?

En général, non. Publiez les détections, les comptages et des instantanés occasionnels dans la limite de taille de message, et utilisez un protocole de streaming si vous avez besoin de vidéo en direct.

Quel port un Jetson doit-il utiliser pour MQTT ?

Utilisez le port 8883 avec TLS pour tout ce qui dépasse un test rapide. Le port 1883 n'est pas chiffré et doit être réservé au développement avec des données synthétiques.

Puis-je exécuter MQTT et l'inférence dans le même processus Python ?

Oui. La boucle réseau de paho s'exécute dans un thread d'arrière-plan grâce à loop_start, de sorte que l'inférence dans la boucle principale n'est pas bloquée par les opérations réseau.

Articles liés

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.