Exception MQTT non traitée?

Le langage MicroPython et les outils de communication
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
Répondre
PhRaucq
Messages : 6
Enregistré le : 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.

Répondre