Page 1 of 1

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

Posted: Thu Mar 18, 2021 1:29 am
by Primesty
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.

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

Posted: Thu Mar 18, 2021 1:16 pm
by pythoncoder
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.

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

Posted: Thu Mar 18, 2021 7:42 pm
by Primesty
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.

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

Posted: Fri Mar 19, 2021 2:07 am
by Primesty
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