Bonjour,
L'inscription sur ce forum et les premiers messages sont modérés par un Administrateur.
Ce n'est pas très convivial mais malheureusement nécessaire suite au spam intensif de nos amis russes.
Cordialement,
Dominique

Exception MQTT non traitée?

Le langage MicroPython, l'API machine (I2C, SPI, UART, Pin, etc)
Règles du forum
Vous aimez MicroPython? Nous aussi! Ces forums sont destinés aux amoureux de Python sur MicroContrôleur.
Le support se fait sur base volontaire et uniquement pour des produits officiels (pas de copie SVP).
Le forum de MicroPython.org sera une excellente référence documentaire (en anglais).

Merci de suivre les recommandations suivantes.
Pensez à:
  • Titre claire: permettant d'identifier le produit et le problème
  • Contenu complet: schema, message d'erreur, etc (tout ce qui permet d'aider à distance).
  • Fichier joint: Max 300 Ko
  • Courtoisie et précision
PhRaucq
Messages : 7
Inscription : lun. 24 août 2020 20:03

Exception MQTT non traitée?

Message par PhRaucq »

Bonsoir,
Toujours dans la lecture de "Python, Raspberry pi et Flask", après quelques tests, j'essaie de passer aux choses sérieuses et je bloque sur un problème de communication MQTT, à mon avis.

L'objet: un ESP8266 muni d'un DHT22 et d'un contacteur de porte.
L'idée est de le placer dans un abri chauffé, dans le jardin. Le DTH22 mesure la température etc. toutes les 10 minutes et publie les valeurs sur le serveur MQTT.
La température mesurée servira à un autre module pour allumer/éteindre un chauffage électrique.
Lorsque le chauffage fonctionne, la mesure de température se fait toutes les minutes.
En parallèle, un contacteur de porte permet d'annoncer sur MQTT l'ouverture intempestive de la porte.

Le boot.py est verbatim celui du livre.

Mon main.py, écrit au départ de plusieurs projets du livre est le suivant:
(j'ai décommenté certaines impressions pour essayer de comprendre ce qui bloquait)

Code : Tout sélectionner

# coding: utf8
""" La Sorcière Pythonic - Object Abri-Sud v0.1 

	Relève et envoie la température, l'humidité, le point de rosée et un indice de satisfaction
	pour l'Abri Sud, via le serveur MQTT
	Le capteur effectue une mesure toutes les 10 minutes, sauf si le chauffage de l'Abri Sud est
	fonctionnement. A ce moment, il effectue une mesure à chaque minute.
	L'objet gère également un contacteur vérifiant l'état de la porte de l'abri des chats.
 """ 

from machine import Pin, reset
import time
from ubinascii import hexlify
from network import WLAN


CLIENT_ID = 'AbriSud'

MQTT_SERVER = "192.168.1.xx"

MQTT_USER = 'Pxxxxxxxs'
MQTT_PSWD = 'xxxxxxxx'


# redemarrage auto après erreur 
ERROR_REBOOT_TIME = 3600 # 1 h = 3600 sec

# Broches utilisées et variables 
 
Etat_Chauf = "OFF"

# Senseur Temp. DHT22
DHT_PIN = 13  


CONTACT_PIN = 14
last_contact_state = 0 # 0 = Fermé - 1 = Ouvert


# --- Demarrage conditionnel ---
runapp = Pin( 12,  Pin.IN, Pin.PULL_UP )
led = Pin( 2, Pin.OUT )
led.value( 1 ) # eteindre

def led_error( step ):
	global led
	t = time.time()
	while ( time.time()-t ) < ERROR_REBOOT_TIME:
		for i in range( 20 ):
			led.value(not(led.value()))
			time.sleep(0.100)
		led.value( 1 ) # eteindre
		time.sleep( 1 )
		# clignote nbr fois
		for i in range( step ):
			led.value( 0 ) 
			time.sleep( 0.5 )
			led.value( 1 )
			time.sleep( 0.5 )
		time.sleep( 1 )
	# Re-start the ESP
	reset()

if runapp.value() != 1:
	from sys import exit
	exit(0)

led.value( 0 ) # allumer

# --- Programme Pincipal ---
def sub_cb( topic, msg ):
	""" fonction de rappel pour souscriptions MQTT """
	# debogage
	print( '-'*20 )
	print ('réception message')
        print( topic )
	print( msg )
	global Etat_Chauf
	
	# bytes -> str
	t = topic.decode( 'utf8' )
	m = msg.decode('utf8')
	try:
		if t == "Maison/Abri/Sud/Etat_chauf":
			Etat_Chauf = m
			print("Etat du chauffage:")
			print(Etat_Chauf)
	except Exception as e:
		# Capturer TOUTE exception sur souscription
		# Ne pas crasher check_mqtt_sub et 
		#    asyncio.run_until_complete et l'ESP!

		# Info debug sur REPL
		print( "="*20 )
		print( "Subscriber callback (sub_cb) catch an exception:" )
		print( e )
		print( "for topic and message" )
		print( t )
		print( m )
		print( "="*20 )

from umqtt.simple import MQTTClient
try: 
	q = MQTTClient( client_id = CLIENT_ID, 
		server = MQTT_SERVER, 
		user = MQTT_USER, password = MQTT_PSWD )
	q.set_callback( sub_cb )

	if q.connect() != 0:
		led_error( step=1 )
	
	q.subscribe( 'Maison/Abri/Sud/Etat_chauf' )
except Exception as e:
	print( e )
	# check MQTT_SERVER, MQTT_USER, MQTT_PSWD
	led_error( step=2 ) 

# chargement des bibliotheques
try:
	from dht import DHT22
	from machine import Pin
	from math import log
except Exception as e:
	print( e )
	led_error( step=3 )

# declare le bus i2c (if any)
#
# i2c = I2C( sda=Pin(4), scl=Pin(5) )

# créer les senseurs
try:
	# Senseur temp&Co DHT22
	DHT = DHT22(Pin(DHT_PIN))
	
	# Contact de porte et lecture
	contact = Pin( CONTACT_PIN, Pin.IN, Pin.PULL_UP )
	last_contact_state = contact.value()

except Exception as e:
	print( e )
	led_error( step=4 )

try:
	# annonce connexion objet
	sMac = hexlify( WLAN().config( 'mac' ) ).decode()
	q.publish( "connect/%s" % CLIENT_ID , sMac )
	# Annonce l'état
except Exception as e:
	print( e )
	led_error( step=5 )

import uasyncio as asyncio

def capture_10min():
    """ Executé pour capturer la temperature toutes les 10 minutes """
    global DHT
    DHT.measure()
    temp = DHT.temperature()
    hum = DHT.humidity()
    #print('Temperature: %3.1f °C' %temp)
    #print('Humidité: %3.1f%%' %hum)
    if hum < 30:
        feel = 2
        # feel = "Sec"
    elif hum < 45:
        feel= 0
        # feel = "Normal"
    elif hum < 70:
        feel = 1
        # feel = "Agréable"
    else:
        feel = 3
        #feel =  "Humide"
    alpha = 17.27 * temp/(237.7+temp)+log(hum/100)
    dp = 237.7 * alpha / (17.27 - alpha)
    dew = "{0:.2f}".format(dp)
    t = "{0:.2f}".format(temp)
    h = "{0:.2f}".format(hum)
    #print('Feel ' + str(feel))
    #print('point de rosée : ' + dew)
    #message = '{ "idx" : '+str(sensor_idx)+', "nvalue" : 0, "svalue" : "'+ t +';'+ h +';'+str(feel)+'"}'
    #q.publish( "domoticz/in", message )
    q.publish("Maison/Abri/Sud/Temp", t)
    time.sleep(0.5)
    q.publish("Maison/Abri/Sud/Hum", h)
    time.sleep(0.5)
    q.publish("Maison/Abri/Sud/Feel", str(feel))
    time.sleep(0.5)
    q.publish ("Maison/Abri/Sud/DewP", dew)

def capture_1min():
	""" Capture de la temperature toutes les minutes (mais 
	    dans lorsque le chauffage est ON) """
	global Etat_Chauf
	 
	if Etat_Chauf == "ON":
		# execution par la routine de capture 
		capture_10min()

def heartbeat():
	""" Led eteinte 200ms toutes les 10 sec """
	# PS: LED déjà éteinte par run_every!
	time.sleep( 0.2 )

def check_mqtt_sub():
        global q
        # Non-Blocking wait_msg(). Will call mqtt callback  
        # (sub_cb) when a message is received for subscription
        try:
                q.check_msg()
        except Exception as e:
                print(e)
                led_error(step=7)

def check_contact():
	""" Publie un message chaque fois que le contact change d'état """
	global q
	global last_contact_state
	# si rien n'a changé
	if contact.value()==last_contact_state:
		return
	# état différent -> deparasitage logiciel
	time.sleep( 0.100 )
	# relire l'état et s'assurer qu'il n'a pas changé
	valeur = contact.value()  
	if valeur != last_contact_state:
		q.publish( "Maison/Abri/Sud/Porte", 
			"OUVERTE" if valeur==1 else "FERMEE" )
		last_contact_state = valeur
                 
async def run_every( fn, min= 1, sec=None):
	""" Execute a function fn every min minutes 
	    or sec secondes"""
	global led
	wait_sec = sec if sec else min*60
	while True:
		led.value( 1 ) # eteindre pendant envoi/traitement
		try:
			fn()
		except Exception:
			print( "run_every catch exception for %s" % fn)
			raise # quitter loop
		led.value( 0 ) # allumer
		await asyncio.sleep( wait_sec )

async def run_app_exit():
	""" fin d'execution lorsque quitte la fonction """
	global runapp
	while runapp.value()==1:
		await asyncio.sleep( 10 )
	return 

loop = asyncio.get_event_loop()
loop.create_task( run_every(capture_10min    , min=10) ) 
loop.create_task( run_every(capture_1min   , min=1) )
loop.create_task( run_every(heartbeat     , sec=10 ) )
loop.create_task( run_every(check_contact, sec=2 ) )
loop.create_task( run_every(check_mqtt_sub, sec=2.5) )

try:
	# Annonce l'etat initial
	q.publish( "Maison/Abri/Sud/Porte", "OUVERTE" if last_contact_state==1 else "FERMEE" )

	# Execution du scheduler
	loop.run_until_complete( run_app_exit() )
except Exception as e :
	print( e )
	led_error( step=6 )

loop.close()
led.value( 1 ) # eteindre 
print( "Fin!")
Tout démarre bien et un souscripteur MQTT tournant sur un autre PC lit bien les messages de connection de l'objet au système de messagerie puis les informations de température, humidité etc., ainsi que les ouvertures ou fermetures de portes.
De même, si un message est publié, correspondant à l'allumage du chauffage, la fréquence de mesure passe bien de 10 à 1 minute.

Par contre, au bout d'un temps variable, le système se bloque et plus rien n'est publié.
La led passe à un niveau de clignotement "7", ce qui correspond à une activation par la fonction "check_mqtt_sub()"
A ce moment, dans le terminal, j'ai les messages suivants:

Code : Tout sélectionner

[Errno 103] ECONNABORTED
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "main_AbriSud.py", line 266, in <module>
  File "uasyncio/core.py", line 1, in run_until_complete
  File "uasyncio/core.py", line 1, in run_until_complete
  File "main_AbriSud.py", line 240, in run_every
  File "main_AbriSud.py", line 214, in check_mqtt_sub
  File "main_AbriSud.py", line 57, in led_error
Cette version de "check_mqtt_sub()" est la deuxième.
La première n'avait pas de section try/exception et à ce moment, les messages étaient les suivants:

Code : Tout sélectionner

run_every catch exception for <function check_mqtt_sub at 0x3fff04d0>
Task exception wasn't retrieved
future: <Task> coro= <generator object 'run_every' at 3fff0eb0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "main_AbriSud.py", line 237, in run_every
  File "main_AbriSud.py", line 211, in check_mqtt_sub
  File "umqtt/simple.py", line 215, in check_msg
  File "umqtt/simple.py", line 179, in wait_msg
OSError: [Errno 103] ECONNABORTED
C'est la mention du problème de gestion des exception qui m'avait incité à modifier le code.

Voyez-vous ce qui cloche?
Merci d'avance pour toute piste.
PhRaucq
Messages : 7
Inscription : lun. 24 août 2020 20:03

Re: Exception MQTT non traitée?

Message par PhRaucq »

Bonjour,

Je n'ai toujours pas résolu mon problème.
(Il faut dire que j'ai été pas mal occupé sur d'autres fronts)

Mais sur le forum de MIcropython.org, on m'a répondu ceci:
"In general umqtt.simple is not well suited to asynchronous code. See mqtt_as for an alternative."

Qu'en pensez-vous? Et avez-vous une expérience ou des exemples de codes fonctionnant avec mqtt_as?

Merci d'avance.
Avatar de l’utilisateur
Dominique
Administrateur du site
Messages : 109
Inscription : dim. 17 mai 2020 22:43

Re: Exception MQTT non traitée?

Message par Dominique »

Bonsoir,
Je ne connaissait pas la version asynchrone du client MQTT (mqtt_as) de Peter Hinch.
C'est quand même lui qui est de développeur initial de asynch sur MicroPython... donc oui, la proposition fait sens.

https://github.com/peterhinch/micropyth ... mqtt_as.py

Il faudra juste employer sa classe mqtt_as.MqttClient . Dans le code, j'ai repéré que Peter Hinch fait une requête sur le serveur DNS de Google pour vérifier si la connexion réseau est toujours active.... pas bête.

Dominique
Dominique, MC Hobby.
sensor56
Messages : 41
Inscription : jeu. 1 avr. 2021 07:14

Re: Exception MQTT non traitée?

Message par sensor56 »

Désolé, dans le genre "déterrage de vieux post"... mais bon, je note çà à toutes fins utiles :

De mémoire, j'avais vu cette façon de faire pour gérer les pertes de connexion MQTT :

Création d'une fonction de redémarrage

Code : Tout sélectionner

def restart_and_reconnect():
  print('Failed to connect to MQTT broker. Reconnecting...')
  time.sleep(10)
  machine.reset()
Dans la gestion de l'exception, on appelle cette fonction :

Code : Tout sélectionner

try:

	....
	
except OSError as e:
  restart_and_reconnect()
+1 sinon pour mqtt_as... je note sur mes tablettes.

voir aussi : <https://randomnerdtutorials.com/micropy ... 2-esp8266/>
Avatar de l’utilisateur
Dominique
Administrateur du site
Messages : 109
Inscription : dim. 17 mai 2020 22:43

Re: Exception MQTT non traitée?

Message par Dominique »

Dans ce ticket, je proposais de ratisser plus large avec

Code : Tout sélectionner

loop = asyncio.get_event_loop()
loop.create_task( run_every(capture_1min  , min=1  ) )
loop.create_task( run_every(heartbeat     , sec=10 ) )
try:
	# Execution du scheduler
	loop.run_until_complete( run_app_exit() )
except (OSError, Exception) as e :
	print( e )
	led_error( step=6 )
Dominique, MC Hobby.
Répondre