Python Paho MQTT client websockets certificats auto-signés, howto
L'utilisation de Paho en Python avec des websockets et des certificats auto-signés pour une connexion websocket https:// présente quelques pièges, c'est pourquoi j'y remédie avec quelques exemples de code ici :
Importer ssl
temps d'importation
import paho.mqtt.client as mqttclasse PCPMQTTClient :
def on_connect(self, client, userdata, flags, rc) :
self.connection_status = rc
si rc == 0 :
self.connected_flag = True
self.connection_error_flag = False
autre :
self.connected_flag = False
self.connection_error_flag = True# appelé lorsqu'un message qui devait être envoyé en utilisant publish()
# a terminé la transmission au courtier.
# pour qos=0 -> le message a quitté le client
# pour qos=1 & 2 -> la poignée de main est terminée.
La variable médiane # correspond à la variable médiane que renvoie publish()
def on_publish(self, client, userdata, mid) :
si self.verbose :
print("publier le milieu du rappel : " + str(mid))def on_log(client, userdata, level, buf) :
print(buf)def testconnection(self) :
return self.connect(test=True)def connect(self, test=False) :
si le test :
print("Connexion à MQTT", end=")
self.connection_status = -1 # pas encore de tentative de connexion
self.connection_error_flag = False
self.mqttc.connect_async(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_address="")self.mqttc.loop_start() # selon Steve mieux APRÈS la connexion
continue_flag = False
keep_connection_flag = not test
compteur_d'attente = 0
tant que le drapeau ne continue pas :
temps.sleep(1)
compteur de temps d'attente += 1
si le test :
print(".", end=") # end=" supprimera la nouvelle ligne
si self.connected_flag :
continue_flag = True
si le compteur de délai d'attente > 6 :
continue_flag = True
keep_connection_flag = False
code = self.connection_status
succès = Faux
si le code == 0 :
succès = Vrai
message = "Connexion réussie !"
self.connected_flag = True
if not keep_connection_flag : # nécessaire pour être ceci, pour détecter si nous ne sommes pas capables de nous connecter !
self.disconnect() # déconnexion propre du serveur
elif code == -1 :
message = "Connection timed out - server not online ? Erreur de certificat (essayez -selfsigned) ?"
elif code == 1 :
message = "Connexion refusée - version de protocole incorrecte".
elif code == 2 :
message = "Connexion refusée - identifiant client non valide".
elif code == 3 :
Message = "Connexion refusée - serveur indisponible".
elif code == 4 :
Message = "Connexion refusée - mauvais nom d'utilisateur ou mot de passe".
elif code == 5 :
message = "Connexion refusée - non autorisée
autre :
message = "Une autre erreur " + str(code) + " s'est produite, veuillez consulter la documentation de Paho".
print(")
return {"success" : success, "code" : code, "message" : message}def publish(self, topic, message, qos=0, retain=False) :
succès = Faux
Code = 'PCP_WRONG_INPUTFORMAT'.
error_message = 'Mauvais format d'entrée des données (par exemple, le sujet ne peut pas être Null)'.
si le sujet :
pubresult = self.mqttc.publish(
topic=topic,
payload=message,
qos=qos,
retain=retain)
Publication du processus #
si pubresult.rc == mqtt.MQTT_ERR_SUCCESS :
code = "MQTT_ERR_SUCCESS".
error_message = 'Successfully sent message to broker'.
succès = Vrai
elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN :
code = "MQTT_ERR_NO_CONN
error_message = 'Pas de connexion au serveur MQTT, essayez de vous reconnecter'.
elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE :
code = "MQTT_ERR_QUEUE_SIZE
error_message = 'Le message n'est ni en file d'attente ni envoyé'.return {"success" : success, "code" : code, "message" : error_message, "mid" : pubresult.mid}
def disconnect(self) :
# selon Steve http://www.steves-internet-guide.com/client-connections-python-mqtt/
self.mqttc.loop_stop()
self.mqttc.disconnect()
self.connected_flag = Falsedef __init__(self, connectiondata, verbose=False, insecure=False) :
self.verbose = verbose
clean_session = True
self.mqttc = mqtt.Client(
client_id=connectiondata['client_id'],
clean_session=clean_session,
userdata=None,
protocole=mqtt.MQTTv311,
transport=connectiondata['transport'])
self.connectiondata = connectiondata
self.connected_flag = False
self.mqttc.on_connect = self.on_connect
self.mqttc.on_publish = self.on_publish
si self.verbose :
print("### activant la journalisation pour le client MQTT ###")
self.mqttc.on_log = self.on_log
self.mqttc.username_pw_set(
username=connectiondata['username'],
password=connectiondata['password'])
si connectiondata['transport'] == "websockets" :
self.mqttc.ws_set_options(path=self.connectiondata['path'])
si self.verbose :
print("### se connectant avec les paramètres suivants ###")
print("* client_id : " + connectiondata['client_id'])
print("* clean_session : " + str(clean_session))
print("* transport : " + connectiondata['transport'])
print("* chemin : " + connectiondata['chemin'])
print("* nom d'utilisateur : " + connectiondata['username'])
print("* password : (not being shown !)")
# self.mqttc.tls_set_context()
en cas d'insécurité :
self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
si self.verbose :
print("### insecure operation mode (-selfsigned) set ! ###")
self.mqttc.tls_insecure_set(True)
autre :
self.mqttc.tls_set()
Comme vous pouvez le constater, il est important de faire DEUX choses pour une connexion non sécurisée :
- tls_set(cert_reqs=ssl.CERT_NONE)
- tls_insecure_set(True)
L'appel tls_insecure_set(True) vous permettra d'omettre la correspondance du nom du certificat. Il essaiera tout de même de s'assurer que le certificat est valide selon la chaîne de certificats !
Il s'agit donc d'un terme un peu mal choisi, je suppose.
L'exemple de code présenté ci-dessus fait partie d'un développement sur picockpit-client, pour l'application plate-forme picockpit.com
Paho / Websockets
connectiondata['transport'] est défini ailleurs dans le code. Il peut être soit (une chaîne) "tcp" ou "websockets".
Si vous spécifiez des websockets, vous pouvez utiliser en plus ws_set_options pour définir un chemin, par exemple "/mqtt".
host=self.connectiondata['host'],
port=self.connectiondata['port'],
l'hôte est votre hébergeur - sans préfixe http / https. Pour le développement local, par exemple, j'utilise "picockpit.local" (proxié par envoy, voir mes autres articles).
le port, dans le cas d'une connexion TLS / SSL sur le port https par défaut est 443
Suggestion pour faciliter le débogage :
Pour un meilleur débogage, passer du code de connexion asynchrone à une connexion synchrone (remplacer connect_async) :
self.mqttc.connect(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_address="")
Références :
- https://github.com/eclipse/paho.mqtt.python/issues/85
- https://github.com/eclipse/paho.mqtt.python/issues/148
- https://aws.amazon.com/blogs/iot/how-to-implement-mqtt-with-tls-client-authentication-on-port-443-from-client-devices-python/
- https://www.eclipse.org/paho/clients/python/docs/ - la documentation officielle de Paho Python