Blynk (lib-python, i.e. blynklib_mp.mpy) in combination with uasyncio

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Primesty
Posts: 44
Joined: Sun Jun 28, 2020 11:06 pm

Blynk (lib-python, i.e. blynklib_mp.mpy) in combination with uasyncio

Post by Primesty » Thu Mar 18, 2021 1:29 am

Hi all,

I'm working on a project on an ESP32 with MicroPython 1.14, in which I would like to combine blynk and other asynchronous tasks. I'm using blynklib_mp.mpy to reduce the RAM usage. My problem arises when I try to combine blynk and uasyncio.

The 'pure' blynk code (based on their examples - https://github.com/blynkkk/lib-python/b ... ual_pin.py) works great and seamlessly with the app via my local blynk server, which is running on a Raspberry Pi 3+.

Code: Select all

import blynklib_mp as blynklib
import network
import time
from machine import Pin
import dht
import gc

sensor = dht.DHT22(Pin(23))
led = Pin(2, Pin.OUT)

WIFI_SSID = 'XXX'
WIFI_PASS = 'XXX'

wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(WIFI_SSID, WIFI_PASS)

# check if board connected 
connect_status = wifi.isconnected()

# initialize Blynk
blynk = blynklib.Blynk(token = "XXX", server = 'X.X.X.X, port = XX)

WRITE_EVENT_PRINT_MSG = "[WRITE_VIRTUAL_PIN_EVENT] Pin: V{} Value: '{}'"

T_COLOR = '#f5b041'
H_COLOR = '#85c1e9'
ERR_COLOR = '#444444'

T_VPIN = 3
H_VPIN = 4

# register handler for virtual pin V4 write event

@blynk.handle_event('read V{}'.format(T_VPIN))
def read_handler(vpin):
    temperature = 0
    humidity = 0

    # read sensor data
    try:
        sensor.measure()
        temperature = sensor.temperature()
        humidity = sensor.humidity()
    except OSError as o_err:
        print("Unable to get DHT22 sensor data: '{}'".format(o_err))

    # change widget values and colors according read results
    if temperature > 25 or humidity > 50:
        blynk.set_property(T_VPIN, 'color', 'red')
        blynk.set_property(H_VPIN, 'color', 'red')
        blynk.virtual_write(T_VPIN, temperature)
        blynk.virtual_write(H_VPIN, humidity)
    else:
        # show widgets aka 'disabled' that mean we had errors during read sensor operation
        blynk.set_property(T_VPIN, 'color', T_COLOR)
        blynk.set_property(H_VPIN, 'color', H_COLOR)


@blynk.handle_event('write V2')
def button_handler(pin, value):
    
        if value == ['1']:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED allumee')
                led(1)
        else:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED eteinte')
                led(0)

    

###########################################################
# infinite loop that waits for event
###########################################################

 while True:
        blynk.run()

Again, the above works fine. However, when I try something like this:

Code: Select all


import blynklib_mp as blynklib
import network
import time
from machine import Pin
import dht
import uasyncio
import gc

sensor = dht.DHT22(Pin(23))
led = Pin(2, Pin.OUT)

WIFI_SSID = 'XXX'
WIFI_PASS = 'XXX'

wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(WIFI_SSID, WIFI_PASS)

# check if board connected 
connect_status = wifi.isconnected()

# initialize Blynk
blynk = blynklib.Blynk(token = "XXX", server = 'X.X.X.X, port = XX)

WRITE_EVENT_PRINT_MSG = "[WRITE_VIRTUAL_PIN_EVENT] Pin: V{} Value: '{}'"

T_COLOR = '#f5b041'
H_COLOR = '#85c1e9'
ERR_COLOR = '#444444'

T_VPIN = 3
H_VPIN = 4
# register handler for virtual pin V4 write event


    
@blynk.handle_event('read V{}'.format(T_VPIN))
def read_handler(vpin):
    temperature = 0
    humidity = 0

    # read sensor data
    try:
        sensor.measure()
        temperature = sensor.temperature()
        humidity = sensor.humidity()
    except OSError as o_err:
        print("Unable to get DHT22 sensor data: '{}'".format(o_err))

    # change widget values and colors according read results
    if temperature > 25 or humidity > 50:
        blynk.set_property(T_VPIN, 'color', 'red')
        blynk.set_property(H_VPIN, 'color', 'red')
        blynk.virtual_write(T_VPIN, temperature)
        blynk.virtual_write(H_VPIN, humidity)
    else:
        # show widgets aka 'disabled' that mean we had errors during read sensor operation
        blynk.set_property(T_VPIN, 'color', T_COLOR)
        blynk.set_property(H_VPIN, 'color', H_COLOR)


@blynk.handle_event('write V2')
def button_handler(pin, value):
    
        if value == ['1']:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED allumee')
                led(1)
        else:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED eteinte')
                led(0)
                
async def run_blynk():
    while True:
        blynk.run()
        await uasyncio.sleep_ms(5)
    
async def read_temp(sensor):
    while True:
        sensor.measure()
        temperature = sensor.temperature()
        #humidity = sensor.humidity()
        print(temperature)
        await uasyncio.sleep_ms(1000)
    

###########################################################
# infinite loop that waits for event
###########################################################

# Setup async app-run

def set_global_exception():
    def handle_exception(loop, context):
        import sys
        sys.print_exception(context["exception"])
        sys.exit()
    loop = uasyncio.get_event_loop()
    loop.set_exception_handler(handle_exception)

async def main():
    
    set_global_exception()
    uasyncio.create_task(read_temp(sensor))

    await blynk.run()
    
try:
    asyncio.run(main())
finally:
    asyncio.new_event_loop()


gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())

I get this error

Code: Select all

Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "main.py", line 89, in read_temp
  File "dht.py", line 17, in measure
OSError: [Errno 110] ETIMEDOUT
MicroPython v1.14 on 2021-02-02; ESP32 module with ESP32
By playing around with the 'placement' of code, i.e. have blynk_run be in the create_task function vs. the await part and with different await ms timings in the async functions I've managed to get it running a little bit like this

Code: Select all


rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:5008
ho 0 tail 12 room 4
load:0x40078000,len:10600
ho 0 tail 12 room 4
load:0x40080400,len:5684
entry 0x400806bc

        ___  __          __
       / _ )/ /_ _____  / /__
      / _  / / // / _ \/  '_/
     /____/_/\_, /_//_/_/\_\
            /___/ for Python v0.2.6

21.2
21.3
21.2
21.3
21.3
21.2
21.3
21.3
21.3
[WRITE_VIRTUAL_PIN_EVENT] Pin: V2 Value: '['1']'
LED allumee
21.3
[WRITE_VIRTUAL_PIN_EVENT] Pin: V2 Value: '['0']'
LED eteinte
21.3
Unable to get DHT22 sensor data: '[Errno 110] ETIMEDOUT'
21.3
Traceback (most recent call last):
  File "main.py", line 105, in <module>
  File "uasyncio/core.py", line 1, in run
  File "uasyncio/core.py", line 1, in run_until_complete
  File "uasyncio/core.py", line 1, in run_until_complete
  File "main.py", line 102, in main
  File "main.py", line 87, in read_temp
  File "dht.py", line 17, in measure
OSError: [Errno 110] ETIMEDOUT
MicroPython v1.14 on 2021-02-02; ESP32 module with ESP32

While it read the temp for a little and responded to inputs in the app, it ended in the same error ultimately.

I'm at a loss here of making these two, blynk and async work together and would be grateful for any input or suggestions you have.

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

Re: Blynk (lib-python, i.e. blynklib_mp.mpy) in combination with uasyncio

Post by pythoncoder » Thu Mar 18, 2021 1:16 pm

I have no knowledge of blynk but a couple of points come to mind re uasyncio. You have

Code: Select all

async def main():
    set_global_exception()
    uasyncio.create_task(read_temp(sensor))
    await blynk.run()
The last line will only work if blynk.run is defined with async def.

A minor point is that the final two lines

Code: Select all

gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
will only run after your actual application terminates or is interrupted with ctrl-c. I doubt this was your intention.

Hopefully someone who actually knows blynk will pop up with something more concrete. Crucial to understanding this is knowing which blynk calls block.
Peter Hinch

Primesty
Posts: 44
Joined: Sun Jun 28, 2020 11:06 pm

Re: Blynk (lib-python, i.e. blynklib_mp.mpy) in combination with uasyncio

Post by Primesty » Thu Mar 18, 2021 7:42 pm

Thanks for the answer, Peter!
Yes, I removed the `gc` stuff. You're right that didn't really matter. It looks like the run() function is not written as an async function. So I re-wrote the run() function with async in the actual blynk lib:

Code: Select all


    async def run(self):
        while True:
            try:
                if not self.connected():
                    self.connect()
                else:
                    try:
                        self.read_response(timeout=self.SOCK_TIMEOUT)
                        if not self.is_server_alive():
                            self.disconnect('Server is offline')
                    except KeyboardInterrupt:
                        raise
                    except BlynkError as b_err:
                        self.log(b_err)
                        self.disconnect()
                    except Exception as g_exc:
                        self.log(g_exc)
            except:
                pass
            await uasyncio.sleep_ms(10)

And then in main.py, I'm running

Code: Select all


async def main():
    
    set_global_exception()  # Debug aid
    uasyncio.create_task(read_temp(sensor))

    await blynk.run()
    
try:
    uasyncio.run(main())
finally:
    uasyncio.new_event_loop()
    
    
And then I also made one of the blynk handler functions asyncronous

Code: Select all


@blynk.handle_event('read V{}'.format(T_VPIN))
async def read_handler(vpin):
    temperature = 0
    humidity = 0

    # read sensor data
    try:
        sensor.measure()
        temperature = sensor.temperature()
        humidity = sensor.humidity()
    except OSError as o_err:
        print("Unable to get DHT22 sensor data: '{}'".format(o_err))

    # change widget values and colors according read results
    if temperature > 25 or humidity > 50:
        blynk.set_property(T_VPIN, 'color', 'red')
        blynk.set_property(H_VPIN, 'color', 'red')
        blynk.virtual_write(T_VPIN, temperature)
        blynk.virtual_write(H_VPIN, humidity)
    else:
        # show widgets aka 'disabled' that mean we had errors during read sensor operation
        blynk.set_property(T_VPIN, 'color', T_COLOR)
        blynk.set_property(H_VPIN, 'color', H_COLOR)
    await uasyncio.sleep_ms(10) #if this is there button works

@blynk.handle_event('write V2')
def button_handler(pin, value):
    
        if value == ['1']:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED allumee')
                led(1)
        else:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED eteinte')
                led(0)
This way, I can open the Blynk app, which connects and I can turn the led on/off via the app while the temp/hum are printed in the console reliably. However, while the button in the app works, the temp and hum are *not* synced to the app.

Code: Select all



        ___  __          __
       / _ )/ /_ _____  / /__
      / _  / / // / _ \/  '_/
     /____/_/\_, /_//_/_/\_\
            /___/ for Python v0.2.6

20.8 45.7
20.8 45.6
20.8 45.6
20.8 45.6
20.8 45.6
[WRITE_VIRTUAL_PIN_EVENT] Pin: V2 Value: '['1']'
LED allumee
20.8 45.6
[WRITE_VIRTUAL_PIN_EVENT] Pin: V2 Value: '['0']'
LED eteinte
20.8 45.6
[WRITE_VIRTUAL_PIN_EVENT] Pin: V2 Value: '['1']'
LED allumee
20.8 45.6
[WRITE_VIRTUAL_PIN_EVENT] Pin: V2 Value: '['0']'
LED eteinte
20.9 45.6

If I don't add the await in the read handler, it doesn't work and I get an error as soon as the app tries to connect (see below)

Code: Select all



        ___  __          __
       / _ )/ /_ _____  / /__
      / _  / / // / _ \/  '_/
     /____/_/\_, /_//_/_/\_\
            /___/ for Python v0.2.6

20.8 46.6
20.9 45.9
20.8 45.8
20.8 45.7
20.9 45.8
Unable to get DHT22 sensor data: '[Errno 110] ETIMEDOUT'
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "main.py", line 91, in read_temp
  File "dht.py", line 17, in measure
OSError: [Errno 110] ETIMEDOUT
MicroPython v1.14 on 2021-02-02; ESP32 module with ESP32

I've noticed two things: (1) the button_handler doesn't have to be async for this to work - why I don't know (yet) and (2) the error seems to be coming from the read_handler, which can't read the sensor data, which is also not synced to the app in this case as well. I'm wondering if reading the sensor in a single async function into global vars and then making these available to the read_handler alleviates this. Making run() async seems to have been a step in the right direction though.

There is also the uPyBlynk lib (https://github.com/lemariva/uPyBlynk) which seems a little more straightforward and works in test. It also doesn't rely on declaring handlers with '@' - maybe this is a little more amenable to being re-written with async. I really want to make the official blynk lib work though because it seems it allows more customization and is the 'official' supported lib.

Primesty
Posts: 44
Joined: Sun Jun 28, 2020 11:06 pm

Re: Blynk (lib-python, i.e. blynklib_mp.mpy) in combination with uasyncio

Post by Primesty » Fri Mar 19, 2021 2:07 am

So, I think I figured it out!!!!

First, I re-wrote the run() function in blynk-lib to include async functionality.

Code: Select all


    async def run(self):
        while True:
            try:
                if not self.connected():
                    self.connect()
                else:
                    try:
                        self.read_response(timeout=self.SOCK_TIMEOUT)
                        if not self.is_server_alive():
                            self.disconnect('Server is offline')
                    except KeyboardInterrupt:
                        raise
                    except BlynkError as b_err:
                        self.log(b_err)
                        self.disconnect()
                    except Exception as g_exc:
                        self.log(g_exc)
            except:
                pass
            await uasyncio.sleep_ms(10)

10 ms seems to work fine. I haven't had any failures because of that. Then I pulled apart that initial long @blynk.handle_event('read V4') statement and broke it up into two individual ones as well as having a designated async read_temp function that reads the sensor data into two empty lists.

Code: Select all


temp = []
hum = []

Code: Select all


@blynk.handle_event('read V4')
def read_handler(vpin):
    if hum[0] > 60:
        blynk.set_property(H_VPIN, 'color', '#FF0000')
        blynk.virtual_write(H_VPIN, hum[0])
        print(READ_PRINT_MSG.format(vpin))
    else:
        blynk.virtual_write(H_VPIN, hum[0])
        blynk.set_property(H_VPIN, 'color', H_COLOR)
   
   
@blynk.handle_event('read V3')
def read_handler_2(vpin):
    if temp[0] > 22:
        blynk.set_property(T_VPIN, 'color', '#FF0000')
        blynk.virtual_write(T_VPIN, temp[0])
        print(READ_PRINT_MSG.format(vpin))
    else:
        blynk.virtual_write(T_VPIN, temp[0])
        blynk.set_property(T_VPIN, 'color', T_COLOR)
        print(READ_PRINT_MSG.format(vpin))

This now works beautifully! No failures so far. The code now runs the sensor measurement and the blynk run function asynchronously. Everything is pretty much updated in real-time and even the conditional color parameters work flawlessly and show up in the blynk app reliably. Interestingly enough, the handler functions don't need async capabilities. I assume this is now handled through the async run() function.

The next step is to package my testing setup into an blynklib_mp.mpy version, which includes the new async run function to save memory and space.

Here is the final main.py code

Code: Select all


#import blynklib_mp as blynklib

import blynk_test as blynklib

import network

import time

from machine import Pin
import dht

import uasyncio
import gc
import random

sensor = dht.DHT22(Pin(23))
led = Pin(2, Pin.OUT)

WIFI_SSID = 'XXX'
WIFI_PASS = 'XXX'

wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(WIFI_SSID, WIFI_PASS)

# check if board connected 
connect_status = wifi.isconnected()

# initialize Blynk
blynk = blynklib.Blynk(token = "XXX", server = 'X.X.X.X', port = XXXX)


T_COLOR = '#f5b041'
H_COLOR = '#85c1e9'
ERR_COLOR = '#444444'

T_VPIN = 3
H_VPIN = 4

temp = []
hum = []

READ_PRINT_MSG = "[READ_VIRTUAL_PIN_EVENT] Pin: V{}"
    
async def read_temp(sensor):
    while True:
        sensor.measure()
        temperature = sensor.temperature()
        humidity = sensor.humidity()
        temp.clear()
        temp.append(temperature)
        hum.clear()
        hum.append(humidity)
        print(temperature, humidity)
        await uasyncio.sleep_ms(500)
        
@blynk.handle_event('read V4')
def read_handler(vpin):
    if hum[0] > 60:
        blynk.set_property(H_VPIN, 'color', '#FF0000')
        blynk.virtual_write(H_VPIN, hum[0])
        print(READ_PRINT_MSG.format(vpin))
    else:
        blynk.virtual_write(H_VPIN, hum[0])
        blynk.set_property(H_VPIN, 'color', H_COLOR)
   
   
@blynk.handle_event('read V3')
def read_handler_2(vpin):
    if temp[0] > 22:
        blynk.set_property(T_VPIN, 'color', '#FF0000')
        blynk.virtual_write(T_VPIN, temp[0])
        print(READ_PRINT_MSG.format(vpin))
    else:
        blynk.virtual_write(T_VPIN, temp[0])
        blynk.set_property(T_VPIN, 'color', T_COLOR)
        print(READ_PRINT_MSG.format(vpin))


@blynk.handle_event('write V2')
def button_handler(pin, value):
    
        if value == ['1']:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED on')
                led(1)
        else:
                print(WRITE_EVENT_PRINT_MSG.format(pin, value))
                print('LED off')
                led(0)
                


###########################################################
# infinite loop that waits for event
###########################################################

# Setup async app-run

def set_global_exception():
    def handle_exception(loop, context):
        import sys
        sys.print_exception(context["exception"])
        sys.exit()
    loop = uasyncio.get_event_loop()
    loop.set_exception_handler(handle_exception)

async def main():
    
    set_global_exception()  # Debug aid
    uasyncio.create_task(read_temp(sensor))

    await blynk.run()
    
try:
    uasyncio.run(main())
finally:
    uasyncio.new_event_loop()  # Clear retained state


Post Reply