MQTT exception or asyncio problem? Help needed.

All ESP8266 boards running MicroPython.
Official boards are the Adafruit Huzzah and Feather boards.
Target audience: MicroPython users with an ESP8266 board.
Post Reply
Phil_R
Posts: 1
Joined: Tue Jan 12, 2021 12:20 am

MQTT exception or asyncio problem? Help needed.

Post by Phil_R » Sun Jan 17, 2021 10:44 pm

Hello to every one.

I am quite new in micropython and ESP boards and I have the following problem:
The project is a ESP8266 board with a DHT22 and a door contact.
The idea is to read temperature and humidity every 10 minutes and publish them on a MQTT broker.
The temperature is read by another ESP8266 which, when needed, turns a heating ON.
When the heating is ON, the first ESP8266 read and publish the temperature every minute.
And of course, the board constantly check if the door opens and publish any modification of state.

There is also a button, allowing to avoid starting the program when pressed at start-up.

Connection to the Wi-Fi is performed with the following boot.py

Code: Select all

WIFI_SSID = "XXXXXXXXXX"
WIFI_PASSWORD = "XXXXXXXXXXXX"

def sta_connect( timeout = None, disable_ap = False ):
    import network, time
    from machine import Pin
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        # connecting to network...
        wlan.connect( WIFI_SSID, WIFI_PASSWORD )
        
        # Skip connexion wait when RunApp=False to avoids REPL timeout
        # testing machine.reset_cause() is not reliable on Huzza ESP8266
        runapp = Pin( 12,  Pin.IN, Pin.PULL_UP )
        if runapp.value() == 0:
            print( "WLAN: no wait!")
        else:
            ctime = time.time()
            while not wlan.isconnected():
                if timeout and ((time.time()-ctime)>timeout):
                    print( "WLAN: timeout!" )
                    break
                else:
                    # print(".")
                    time.sleep( 0.500 )

    # Decommenter pour obtenir des infos
    # print('network config:', wlan.ifconfig())
    if wlan.isconnected() and disable_ap:
        ap = network.WLAN(network.AP_IF)
        if ap.active():
            ap.active(False)
            print( "AP disabled!" )

    return wlan.isconnected()

# Disable Access Point if connected as STATION
# Connexion timeout of 40 sec
if sta_connect( disable_ap=True, timeout=20 ):
    print( "WLAN: connected!" )

import gc
#import webrepl
#webrepl.start()
gc.collect()
And here is the Main.py

Code: Select all

# coding: utf8


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'


# Auto restart after error 
ERROR_REBOOT_TIME = 3600 # 1 h = 3600 sec

# pins and variables 
 
Etat_Chauf = "OFF"

# Temp. DHT22
DHT_PIN = 13  

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


# --- Conditionnal start ---
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

# --- MAin programm ---
def sub_cb( topic, msg ):
	""" MQTT subscriptions callback """
	# 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 )

# create sensors
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():
    """ Read and send temp etc. every 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():
	""" When heating is ON, read and send temp etc. every minute """
	global Etat_Chauf
	 
	if Etat_Chauf == "ON":
		# execution par la routine de capture 
		capture_10min()

def heartbeat():
	""" Led OFF during 200ms every 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():
	""" Publish any state change of door contact """
	global q
	global last_contact_state
	# if nothing changed
	if contact.value()==last_contact_state:
		return
	# if satte changed -> soft clean-up
	time.sleep( 0.100 )
	# read again and see if changed
	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 initial state
	q.publish( "Maison/Abri/Sud/Porte", "OUVERTE" if last_contact_state==1 else "FERMEE" )

	# Execution of 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!")
Separately, the functions did their job without any problem.
But when I try to use all this together with uasyncio, it is quite erratic.
It starts well, the ESP publishes data as supposed to do. It reacts to the opening or closing of the door and change the frequency of temperature reading if MQTT announces that the heater is ON.
But after a certain time (from 5 to 45 minutes) nothing more is published and there is no more reactivity of the board.

With the above main.py running (renamed main_AbriSud.py for testing), I have these lines on the terminal:

Code: Select all

[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
On a previous version, without exception capture in the check_mqtt_sub() function, I had these outputs:

Code: Select all

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
Would anyone have an idea of what goes wrong and how I could fix it?
Thank you in advance.

User avatar
pythoncoder
Posts: 4921
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: MQTT exception or asyncio problem? Help needed.

Post by pythoncoder » Mon Jan 18, 2021 9:44 am

In general umqtt.simple is not well suited to asynchronous code. See mqtt_as for an alternative.
Peter Hinch

Post Reply