flexway
FRENParler à un expert

Brancher un appareil MQTT

Brancher un capteur, un automate ou un nœud Sparkplug B en MQTT sur la passerelle : identité, topics, codes de refus, contre-pression.

ConceptDéveloppeursDepuis la version 1.7.0

Markdown

Sur cette page

Un capteur, un automate ou une passerelle d'atelier se branche sur la passerelle en MQTT 5 ou 3.1.1, avec n'importe quel client MQTT, sans changer de code. Disponible depuis 1.7.0.

Côté produit : Appareils et IoT ; côté exploitation : Appareils MQTT : l'écouteur en proxy.

Se connecter

Propriété Valeur
Adresse mqtts://<hôte>:8883, ou wss://<hôte>:<port>/mqtt sur WebSocket - donnée par l'exploitant
Versions MQTT 5 (conseillé : des codes de refus précis) ou 3.1.1
Identité un certificat d'appareil ; ou un mot de passe : une clé d'API (son identifiant en nom d'utilisateur) ou un jeton de votre fournisseur d'identité (le nom d'utilisateur est libre)
Client id libre : la passerelle écrit le sien chez le courtier (<tenant>/<identité>) ; un second appareil de la même identité prend la session du premier
Topics ceux du contrat, sous votre tenant et votre identité (plant/acme/press-07/telemetry)
Keep-alive MQTT 5 : le Server Keep Alive du CONNACK l'emporte ; 3.1.1 : entre 1 s et le max_keepalive de l'exploitant (1 200 s par défaut) - 0 est refusé
Durée d'une connexion au plus max_session_lifetime (24 h par défaut) : puis DISCONNECT 0xA0, reconnectez-vous
Alias de topic jusqu'à la valeur annoncée dans le CONNACK (MQTT 5)
Taille, QoS, en vol le CONNACK dit Maximum Packet Size, Maximum QoS et Receive Maximum : les respecter
# MQTT 5, an API key: its id as the user name, the key as the password
mosquitto_pub -V mqttv5 -h mqtt.plant.example.com -p 8883 --cafile ca.crt \
  -i press-07 -u press-07 -P "$DEVICE_KEY" \
  -t plant/acme/press-07/telemetry -q 1 -m '{"v":21.5}'

# A device certificate instead (no password)
mosquitto_sub -V mqttv5 -h mqtt.plant.example.com -p 8883 --cafile ca.crt \
  --cert device.crt --key device.key -t 'plant/acme/press-07/cmd' -q 1

Go (paho.golang, MQTT 5, reconnexion automatique) :

u, _ := url.Parse("tls://mqtt.plant.example.com:8883")
cm, err := autopaho.NewConnection(ctx, autopaho.ClientConfig{
	ServerUrls:      []*url.URL{u},
	TlsCfg:          &tls.Config{RootCAs: pool},
	KeepAlive:       30,
	ConnectUsername: "press-07",
	ConnectPassword: []byte(deviceKey), // or a token of your identity provider
	WillMessage:     &paho.WillMessage{Topic: "plant/acme/press-07/state", QoS: 1, Payload: []byte("offline")},
	ClientConfig:    paho.ClientConfig{ClientID: "press-07"},
})
if err != nil {
	return err
}
pr, err := cm.Publish(ctx, &paho.Publish{Topic: "plant/acme/press-07/telemetry", QoS: 1, Payload: []byte(`{"v":21.5}`)})
if pr != nil && pr.ReasonCode >= 0x80 {
	// refused by the gateway: 0x87 not authorized, 0x99 payload invalid, 0x97 quota exceeded
}

Python (paho-mqtt 2) :

import ssl
import paho.mqtt.client as mqtt

c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="press-07", protocol=mqtt.MQTTv5)
c.tls_set(ca_certs="ca.crt")                       # add certfile=, keyfile= for a device certificate
c.username_pw_set("press-07", device_key)          # or a token as the password
c.will_set("plant/acme/press-07/state", "offline", qos=1)

def on_publish(client, userdata, mid, reason_code, properties):
    if reason_code.is_failure:                     # 0x87, 0x99, 0x97...
        print("refused:", reason_code)

c.on_publish = on_publish
c.connect("mqtt.plant.example.com", 8883, keepalive=30)
c.loop_start()
c.publish("plant/acme/press-07/telemetry", '{"v": 21.5}', qos=1)

Publier, s'abonner

  • Un PUBLISH admis reçoit son PUBACK après celui du courtier : la passerelle ne retient rien.
  • Un filtre peut porter un joker à l'intérieur de votre canal (plant/acme/fleet/#), jamais au-delà (fleet/#) ; $share/<groupe>/<filtre> seulement où le canal l'admet - votre groupe ne rencontre jamais celui d'un autre tenant.
  • Le testament (will) est jugé au CONNECT comme une publication : sur un topic que vous pouvez publier.
  • Un Response Topic doit être un topic auquel vous pouvez vous abonner.

Débits : vous êtes ralenti, jamais refusé

Au-delà de votre débit (messages ou octets par seconde) ou de celui de votre tenant, la passerelle cesse de lire votre connexion le temps nécessaire : vos PUBLISH partent plus lentement, aucun n'est refusé, la connexion n'est pas coupée et votre testament ne part pas. Un client qui attend ses PUBACK (QoS 1) se cale tout seul. Seul le quota du mois ou du jour de votre tenant refuse (0x97).

Arrêts et reconnexions

À l'arrêt d'une réplique, MQTT 5 reçoit DISCONNECT 0x9C et une Server Reference ; 3.1.1 est simplement fermé. Reconnectez-vous avec le même identifiant dans les 30 secondes (la Server Reference, ou la même adresse) : votre session reprend chez le courtier. Pour que votre testament ne parte pas non plus à la reprise, déclarez en MQTT 5 un délai de testament (Will Delay Interval) plus long que votre reconnexion et une expiration de session qui le couvre ; sans délai (et toujours en 3.1.1), le courtier le publie quand votre nouvelle connexion reprend la session. Vérifié de bout en bout avec paho.golang (autopaho) et paho.mqtt.golang en reconnexion automatique, qui reviennent par la même adresse.

Sparkplug B et OPC UA PubSub JSON

Un nœud Sparkplug B (Eclipse Tahu, Ignition Edge, une passerelle d'atelier) se branche comme tout appareil, avec les topics de l'espace spBv1.0 :

Propriété Nœud (edge_node) Application hôte (host, state)
Groupe Le nom de votre tenant (spBv1.0/acme/...) Idem
Identifiant L'identifiant du nœud = votre identité : l'identifiant de la clé, le sujet du jeton ou le champ du certificat que l'exploitant a lié -
Publie spBv1.0/acme/NBIRTH/<nœud>, NDATA, NDEATH, et DBIRTH, DDATA, DDEATH de ses appareils NCMD, DCMD du groupe ; son STATE sur spBv1.0/STATE/acme - l'identifiant d'hôte est le nom du tenant
Reçoit Ses NCMD, DCMD ; le STATE de l'hôte de son tenant Tout le groupe
Testament Son NDEATH (avec le bdSeq de son NBIRTH), rien d'autre Son STATE {"online": false, ...}, rien d'autre
Charge Protobuf Sparkplug B (sparkplug_b.proto), vérifiée si l'exploitant l'a choisi STATE en JSON {"online": true, "timestamp": ...}
  • La passerelle ne demande jamais de renaissance et n'émet aucun message Sparkplug : c'est le travail de votre application hôte.
  • Un écart de seq ou de bdSeq est compté, jamais refusé.
  • Après un DISCONNECT 0x9C, reconnectez-vous avec le même identifiant : la session reprend, le nœud reste en ligne.
Ce que vous recevez Pourquoi Que faire
CONNACK 0x87 Le testament n'est pas le NDEATH du nœud (ou le STATE de l'hôte) Déclarer le NDEATH comme testament
PUBACK 0x87 Un type que votre rôle ne publie pas (une naissance par l'hôte, une commande par le nœud), ou un autre groupe ou nœud Publier sous votre groupe et votre identifiant
PUBACK 0x99 Charge hors de sparkplug_b.proto, ou STATE hors de son schéma Corriger le codage

Un éditeur OPC UA PubSub publie ses NetworkMessage en JSON (partie 14) sur <préfixe>/json/data/<PublisherId>/<WriterGroup> : le préfixe porte votre tenant (opcua/acme/...), le PublisherId est votre identité ; chaque message est vérifié par le schéma du contrat (0x99 sinon). Le codage binaire UADP n'est pas servi.

Codes de refus

Ce que vous recevez Pourquoi Que faire
CONNACK 0x86 (3.1.1 0x04) Clé ou jeton refusé ; nom d'utilisateur qui n'est pas l'identifiant de la clé Corriger le justificatif ; ne pas réessayer en boucle
CONNACK 0x87 (3.1.1 0x05) L'identité n'ouvre aucun topic, ou le testament vise un topic qu'elle ne peut publier Demander les opérations à l'exploitant
CONNACK 0x88, 0x89 (3.1.1 0x03) Le courtier est indisponible Réessayer avec recul
CONNACK 0x9C / DISCONNECT 0x9C + Server Reference La réplique s'arrête ou est pleine Se reconnecter à la Server Reference, ou à la même adresse
CONNACK 0x9F Trop de connexions par seconde pour cette identité ou ce tenant Attendre au moins une seconde
CONNACK 0x97 La réplique, votre tenant ou votre identité (per_client_id) tient son maximum de connexions Fermer une connexion, ou réessayer avec recul
3.1.1 : code de retour 0x05 Un keep-alive de 0, ou au-delà du maximum de l'exploitant Poser un keep-alive (30 à 300 s est courant)
PUBACK 0x87 Topic hors de vos canaux, ou d'un autre appareil, ou Response Topic interdit Publier sous votre tenant et votre identité
PUBACK 0x99 La charge ne respecte pas le schéma du contrat, ou est déclarée UTF-8 sans l'être Corriger le message
PUBACK 0x97 Le quota du mois (ou du jour) de votre tenant est épuisé Attendre la fenêtre suivante
PUBACK ou PUBREC 0x9B QoS au-delà de ce que le canal admet Publier en QoS plus basse
SUBACK 0x87 (3.1.1 0x80) Filtre plus large que vos canaux (plant/#) ou d'un autre tenant Restreindre le filtre
DISCONNECT 0xA0 Votre jeton (ou clé, ou certificat) a expiré, ou la connexion a atteint sa durée maximale Se reconnecter avec un justificatif neuf
DISCONNECT 0x87 Votre clé, certificat ou vos opérations ont été retirés Demander à l'exploitant ; ne pas réessayer en boucle
DISCONNECT 0x96 Trop de paquets refusés en une minute (60 par défaut) Corriger les topics ou charges refusés avant de vous reconnecter
DISCONNECT 0x8E Une autre connexion de la même identité a pris la session Un appareil par identité, ou demander per_client_id
3.1.1 : connexion fermée après un PUBLISH MQTT 3.1.1 ne sait pas dire pourquoi une publication est refusée Lire le même tableau en MQTT 5

Étapes suivantes