Python Paho MQTT cliente websockets certificados autofirmados, howto
El uso de Paho en Python con websockets y certificados autofirmados para una conexión de websocket https:// tiene un par de escollos, por lo que estoy abordando esto con un código de ejemplo aquí:
importar ssl
tiempo de importación
importar paho.mqtt.client como mqttclase 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
Si no:
self.connected_flag = False
self.connection_error_flag = True# llamado cuando un mensaje que iba a ser enviado mediante publish()
# ha completado la transmisión al corredor
# para qos=0 -> el mensaje ha salido del cliente
# para qos=1 y 2 -> los apretones de manos se han completado.
# variable media coincide con la variable media que devuelve publish()
def on_publish(self, client, userdata, mid):
si self.verbose:
print("publicar callback mid: " + 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 la prueba:
print("Conectando a MQTT", end=")
self.connection_status = -1 # no hay intentos de conexión todavía
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() # según Steve mejor AFTER connect
continue_flag = False
keep_connection_flag = no test
timeout_counter = 0
mientras no continue_flag:
time.sleep(1)
timeout_counter += 1
si la prueba:
print(".", end=") # end=" suprimirá la nueva línea
si self.connected_flag:
continue_flag = True
si timeout_counter > 6:
continue_flag = True
keep_connection_flag = False
code = self.connection_status
éxito = Falso
si código == 0:
éxito = Verdadero
mensaje = "¡Conexión exitosa!"
self.connected_flag = True
¡si no keep_connection_flag: # necesario ser esto, para detectar si no somos capaces de conectar!
self.disconnect() # desconecta limpiamente del servidor
elif code == -1:
message = "¿Conexión agotada - servidor no conectado? ¿Error de certificado (intente -selfsigned)?"
elif code == 1:
mensaje = "Conexión rechazada - versión de protocolo incorrecta"
elif code == 2:
mensaje = "Conexión rechazada - identificador de cliente no válido"
elif code == 3:
mensaje = "Conexión rechazada - servidor no disponible"
elif code == 4:
mensaje = "Conexión rechazada - nombre de usuario o contraseña incorrectos"
elif code == 5:
mensaje = "Conexión rechazada - no autorizada"
Si no:
message = "Se ha producido otro error " + str(code) + ", por favor, consulte la documentación de Paho"
print(")
return {"éxito": éxito, "código": código, "mensaje": mensaje}def publish(self, topic, message, qos=0, retain=False):
éxito = Falso
code = 'PCP_WRONG_INPUTFORMAT'
error_message = 'Formato de entrada de datos incorrecto (por ejemplo, el tema no puede ser Null)'
si el tema:
pubresultado = self.mqttc.publish(
topic=tema,
payload=mensaje,
qos=qos,
retener=retención)
# proceso pubresultado
si pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
code = 'MQTT_ERR_SUCCESS'
error_message = 'Mensaje enviado con éxito al corredor.'
éxito = Verdadero
elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
code = 'MQTT_ERR_NO_CONN'
error_message = 'No hay conexión con el servidor MQTT, intente reconectarse'
elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
code = 'MQTT_ERR_QUEUE_SIZE'
error_message = 'El mensaje no está en cola ni se ha enviado'.return {"success": éxito, "code": código, "message": error_message, "mid": pubresult.mid}
def disconnect(self):
# según 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=sesión limpia,
userdata=Ninguna,
protocol=mqtt.MQTTv311,
transporte=conexióndatos['transporte'])
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("### habilitando el registro para el cliente MQTT ##")
self.mqttc.on_log = self.on_log
self.mqttc.username_pw_set(
username=nombre de usuario['username'],
contraseña=datosdeconexión['contraseña'])
si connectiondata['transport'] == "websockets":
self.mqttc.ws_set_options(path=self.connectiondata['path'])
si self.verbose:
print("### conectándose con los siguientes parámetros ###")
print("* client_id: " + connectiondata['client_id'])
print("* sesión_limpia: " + str(clean_session))
print("* transporte: " + connectiondata['transporte'])
print("* ruta: " + connectiondata['ruta'])
print("* nombre de usuario: " + connectiondata['username'])
print("* contraseña: (¡no se muestra!)")
# self.mqttc.tls_set_context()
si es inseguro:
self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
si self.verbose:
print("¡### modo de operación inseguro (-selfsigned) establecido! ###")
self.mqttc.tls_insecure_set(True)
Si no:
self.mqttc.tls_set()
Como puedes ver, es importante hacer DOS cosas para una conexión insegura:
- tls_set(cert_reqs=ssl.CERT_NONE)
- tls_insecure_set(True)
La llamada tls_insecure_set(True) le permitirá omitir la comprobación del nombre del certificado. Aún así, intentará asegurarse de que el certificado es válido según la cadena de certificados.
Por lo tanto, creo que es un nombre un poco equivocado.
El código de ejemplo que se muestra arriba es parte de un desarrollo en picockpit-client, para el Plataforma picockpit.com
Paho / Websockets
connectiondata['transport'] se establece en otra parte del código. Puede ser (una cadena) "tcp" o "websockets".
Si especifica websockets, puede utilizar adicionalmente ws_set_options para establecer una ruta, por ejemplo "/mqtt".
host=self.connectiondata['host'],
port=self.connectiondata['port'],
el host es su webhost - sin prefijo http / https. Para el desarrollo local, por ejemplo, utilizo "picockpit.local" (proxy a través de envoy, ver mis otros artículos).
el puerto, en el caso de una conexión TLS / SSL sobre el puerto https por defecto es 443
Sugerencia para facilitar la depuración:
Para una mejor depuración, cambie el código de conexión asíncrona por una conexión síncrona (sustituya connect_async):
self.mqttc.connect(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_address="")
Referencias:
- 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 documentación oficial de Paho Python