Python Paho MQTT client autofirmato certificati websockets, howto
Usare Paho in Python con websockets e certificati autofirmati per una connessione websocket https:// ha un paio di insidie, così sto affrontando questo con un po' di codice di esempio qui:
importare ssl
tempo di importazione
importa paho.mqtt.client come mqttclasse PCPMQTTClient:
def on_connect(self, client, userdata, flags, rc):
self.connection_status = rc
se rc == 0:
self.connected_flag = True
self.connection_error_flag = False
altro:
self.connected_flag = False
self.connection_error_flag = True# chiamato quando un messaggio che doveva essere inviato usando publish()
# ha completato la trasmissione al broker
# per qos=0 -> il messaggio ha lasciato il cliente
# per qos=1 & 2 -> gli handshake sono stati completati.
# la variabile mid corrisponde alla variabile mid che publish() restituisce
def on_publish(self, client, userdata, mid):
se self.verbose:
print("pubblica callback mid: " + str(mid))def on_log(client, userdata, level, buf):
stampa(buf)def testconnection(self):
return self.connect(test=True)def connect(self, test=False):
se test:
print("Connessione a MQTT", end=")
self.connection_status = -1 # nessun tentativo di connessione ancora
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() # secondo Steve meglio DOPO la connessione
continue_flag = False
keep_connection_flag = non test
timeout_counter = 0
mentre non continua_flag:
time.sleep(1)
timeout_counter += 1
se test:
print(".", end=") # end=" sopprimerà la newline
se self.connected_flag:
continue_flag = True
se timeout_counter > 6:
continue_flag = True
keep_connection_flag = False
codice = self.connection_status
successo = Falso
se codice == 0:
successo = Vero
messaggio = "Connessione riuscita!"
self.connected_flag = True
if not keep_connection_flag: # necessario essere questo, per rilevare se non siamo in grado di connetterci!
self.disconnect() # disconnessione pulita dal server
elif code == -1:
message = "Connessione scaduta - server non online? Errore del certificato (prova -selfsigned)?"
elif code == 1:
messaggio = "Connessione rifiutata - versione di protocollo errata"
elif code == 2:
messaggio = "Connessione rifiutata - identificatore client non valido"
elif code == 3:
messaggio = "Connessione rifiutata - server non disponibile"
elif code == 4:
messaggio = "Connessione rifiutata - cattivo nome utente o password"
elif code == 5:
messaggio = "Connessione rifiutata - non autorizzato"
altro:
messaggio = "Si è verificato un altro errore " + str(codice) + ", si prega di controllare la documentazione Paho".
stampa(")
return {"successo": successo, "codice": codice, "messaggio": messaggio}def publish(self, topic, message, qos=0, retain=False):
successo = Falso
codice = 'PCP_WRONG_INPUTFORMAT'
error_message = 'Formato errato dei dati in ingresso (ad esempio, l'argomento non può essere Null)'
se argomento:
pubresult = self.mqttc.publish(
topic=topic,
payload=messaggio,
qos=qos,
retain=retain)
# processo pubresult
if pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
codice = 'MQTT_ERR_SUCCESS'
error_message = 'Messaggio inviato con successo al broker'.
successo = Vero
elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
codice = 'MQTT_ERR_NO_CONN'
error_message = 'Nessuna connessione al server MQTT, provare a riconnettersi'
elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
codice = 'MQTT_ERR_QUEUE_SIZE'
error_message = 'Il messaggio non è né accodato né inviato'.return {"success": successo, "code": codice, "message": error_message, "mid": pubresult.mid}
def disconnect(self):
# come per 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=Nessuno,
protocollo=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
se self.verbose:
print("### abilitando la registrazione per il client MQTT ###")
self.mqttc.on_log = self.on_log
self.mqttc.username_pw_set(
username=connectiondata['username'],
password=connectiondata['password'])
se connectiondata['transport'] == "websockets":
self.mqttc.ws_set_options(path=self.connectiondata['path'])
se self.verbose:
print("### che si collega con i seguenti parametri ###")
print("* client_id: " + connectiondata['client_id'])
print("* clean_session: " + str(clean_sessione))
print("* trasporto: " + connectiondata['transport'])
print("* percorso: " + connectiondata['path'])
print("* username: " + connectiondata['username'])
print("* password: (non viene mostrata!)")
# self.mqttc.tls_set_context()
se insicuro:
self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
se self.verbose:
print("### modalità di funzionamento insicura (-selfsigned) impostata! ###")
self.mqttc.tls_insecure_set(True)
altro:
self.mqttc.tls_set()
Come potete vedere, è importante fare DUE cose per una connessione insicura:
- tls_set(cert_reqs=ssl.CERT_NONE)
- tls_insecure_set(True)
La chiamata tls_insecure_set(True) vi permetterà di omettere la corrispondenza del nome del certificato. Cercherà comunque di assicurarsi che il certificato sia valido come da catena del certificato!
Quindi è un po' un termine improprio, credo.
Il codice di esempio mostrato sopra è parte di uno sviluppo su picockpit-client, per il piattaforma picockpit.com
Paho / Websockets
connectiondata['transport'] è impostato altrove nel codice. Può essere sia (una stringa) "tcp" o "websockets".
Se specificate i websockets, potete anche usare ws_set_options per impostare un percorso, per esempio "/mqtt".
host=self.connectiondata['host'],
port=self.connectiondata['port'],
l'host è il tuo webhost - senza prefisso http / https. Per lo sviluppo locale, per esempio, uso "picockpit.local" (proxied attraverso envoy, vedi altri miei articoli).
la porta, nel caso di una connessione TLS / SSL sulla porta https predefinita è 443
Suggerimento per un debug più facile:
Per un migliore debug passare dal codice di connessione asincrona a una connessione sincrona (sostituire connect_async):
self.mqttc.connect(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_address="")
Riferimenti:
- 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 documentazione ufficiale di Paho Python