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
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 1Go (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
PUBLISHadmis reçoit sonPUBACKaprè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é auCONNECTcomme 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
seqou debdSeqest 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 |