Updating screen content from asynchronous callback

I’m trying to use lvgl in async mode to connect to a wifi network.
I use classes to create the individual screens as shown in the advanced example on github.
The classes hold the individual widgets and the corresponding callbacks of a screen.
My problem lies in updating the screens contents in the callback functions with data from blocking funtions.

This code snippet shows a callback function using data from a blocking function (last elif block). I’m trying to disable a text area, retrive data from the blocking function (could be a network-scan function),add the data to the screen and enable the TA again. However, the states of the widgets won’t update correctly (it looks like the update style is only applied partially).

The i tried to create an async task from an external function, to which I forward the handles of the elements I want to modify and update the data (first if block). This is working as expected and all element states get updated as supposed to. But I think it’s inconvenient to use external functions outside the screen-classes.

A similar problem arises when I want to load a new screen in the button-callback (middle elif block):
It only works when I use an external task). If I try to use the update function of the app directly I recieve an error message (Error: can’t cancel self).

Does anyone have an idea how I can keep the update functions in the screen classes and update the screen with data from a blocking function?

def cb(self, e):
        obj = e.get_target()
        
        if obj == self.asyncButton:
            task = asyncio.create_task(returnDelayAsync(self.app, self, 3))
            # res await returnDelayAsync(self.app, self, 3)
            #print(res) # Not working
            
        elif obj == self.nextButton:
            task = asyncio.create_task(loadScreenAsync(self.app, 'WifiSuccess'))
            #self.app.loadScreen('WifiSuccess') # not working
            
        elif obj == self.syncButton: # Widget states won't update properly
            disable(self.pwdTa)
            res = returnDelay(3)
            self.titleLbl.set_text(res)
            enable(self.pwdTa)
Summary
import usys as sys
sys.path.append('')

from machine import SoftSPI, Pin
from utime import ticks_us
import lv_utils
import lvgl as lv
import uasyncio as asyncio
from time import sleep, sleep_ms


from xpt2046_cyd import xpt2046
import ili9XXX


def enable(el):
    if el.has_state(lv.STATE.DISABLED):
        el.clear_state(lv.STATE.DISABLED)

def disable(el):
    if not el.has_state(lv.STATE.DISABLED):
        el.add_state(lv.STATE.DISABLED)

def returnDelay(delay):
    for i in range(delay):
        print('### Sleep ###')
        sleep(1)
    return 'sync Data'

async def returnDelayAsync(app, screen, delay):
    disable(screen.pwdTa)
    for i in range(delay):
        print('### Sleep Async ###')
        await asyncio.sleep(1)
    screen.titleLbl.set_text('async Data')
    enable(screen.pwdTa)

async def loadScreenAsync(app, sid):
    await asyncio.sleep(0)
    app.loadScreen(sid)

class ScreenWifiConnect(lv.obj):
    def __init__(self, app, *args, **kwds):
        self.app = app
        super().__init__(*args, **kwds)
        self.set_style_bg_color(lv.color_white(), lv.PART.MAIN)
        
        
        self.kb = lv.keyboard(self)
        self.kb.set_size(lv.pct(100), lv.pct(50))
        self.kb.align(lv.ALIGN.TOP_LEFT, lv.pct(0), lv.pct(50))
        
        self.syncButton = lv.btn(self)
        self.syncButton.set_size(80, 30)
        self.syncButton.align_to(self.kb, lv.ALIGN.OUT_TOP_RIGHT, -15, -15)
        self.syncButton.add_event_cb(self.cb, lv.EVENT.CLICKED, None)

        self.syncButtonLbl = lv.label(self.syncButton)
        self.syncButtonLbl.set_text('Sync')
        self.syncButtonLbl.center()
        
        self.asyncButton = lv.btn(self)
        self.asyncButton.set_size(60, 30)
        self.asyncButton.align_to(self.syncButton, lv.ALIGN.OUT_LEFT_TOP, -5, 0)
        self.asyncButton.add_event_cb(self.cb, lv.EVENT.CLICKED, None)

        self.asyncButtonLbl = lv.label(self.asyncButton)
        self.asyncButtonLbl.set_text('Async')
        self.asyncButtonLbl.center()
        
        self.nextButton = lv.btn(self)
        self.nextButton.set_size(60, 30)
        self.nextButton.align_to(self.asyncButton, lv.ALIGN.OUT_LEFT_TOP, -5, 0)
        self.nextButton.add_event_cb(self.cb, lv.EVENT.CLICKED, None)

        self.nextButtonLbl = lv.label(self.nextButton)
        self.nextButtonLbl.set_text('Next')
        self.nextButtonLbl.center()
        
        self.pwdTa = lv.textarea(self)
        self.pwdTa.set_placeholder_text('Enter Password')
        self.pwdTa.set_password_mode(True)
        self.pwdTa.set_one_line(True)
        self.pwdTa.set_width(lv.pct(90))
        self.pwdTa.align_to(self.syncButton, lv.ALIGN.OUT_TOP_RIGHT, 0, -5)
        self.pwdTa.add_state(lv.STATE.FOCUSED)
        self.kb.set_textarea(self.pwdTa)
        
        self.titleLbl = lv.label(self)
        self.titleLbl.set_text('Standby')
        self.titleLbl.align_to(self.pwdTa,lv.ALIGN.OUT_TOP_LEFT, 0, -5)
        
    def cb(self, e):
        obj = e.get_target()
        
        if obj == self.asyncButton:
            task = asyncio.create_task(returnDelayAsync(self.app, self, 3))
            # res await returnDelayAsync(self.app, self, 3)
            #print(res) # Not working
            
        elif obj == self.nextButton:
            task = asyncio.create_task(loadScreenAsync(self.app, 'WifiSuccess'))
            #self.app.loadScreen('WifiSuccess') # not working
            
        elif obj == self.syncButton: # Widget states won't update properly
            disable(self.pwdTa)
            res = returnDelay(3)
            self.titleLbl.set_text(res)
            enable(self.pwdTa)


class ScreenWifiSuccess(lv.obj):
    def __init__(self, app, *args, **kwds):
        self.app = app
        super().__init__(*args, **kwds)
                
        self.title = lv.label(self)
        self.title.set_text('Settings')
        self.title.center()
        
        
class App:
    def __init__(self):
        self.actScreen = None
        self.prevScreen = None
        self.sharedData = {}
        
        lv.init()
        
        self.disp = ili9XXX.ili9341(clk=14, cs=15,
                           dc=2, rst=12, power=23, miso=12,
                           mosi=13, width = 320, height = 240,
                           rot = 0xC0, colormode=ili9XXX.COLOR_MODE_RGB,
                                    double_buffer = False, factor = 16, asynchronous=True)
    
        self.spiTouch = SoftSPI(baudrate = 2500000, sck = Pin(25),
                            mosi = Pin(32), miso = Pin(39))
        
        self.touch = xpt2046(spi = self.spiTouch, cs = Pin(33), cal_x0 = 3700,
                    cal_y0 = 3820, cal_x1 = 180, cal_y1 = 250, transpose=False)
        
        self.backlight = Pin(21, Pin.OUT)
        self.backlight(1)


        self.group = lv.group_create()
        self.group.set_default()
        
        self.screens = {'WifiConnect': ScreenWifiConnect,
                        'WifiSuccess': ScreenWifiSuccess
                        }
        
        self.loadScreen('WifiConnect')
        
    def loadScreen(self, screenName):
        if not(screenName in self.screens):
            print('Unknown screen!')
            return False
        
        if not(self.actScreen is None):
            self.prevScreen = self.actScreen
            
        self.actScreen = self.screens[screenName](self)
        lv.scr_load(self.actScreen)
        
        if not(self.prevScreen is None):
            self.prevScreen.del_async()

try:
    app = App()
    asyncio.Loop.run_forever()
except KeyboardInterrupt:
    print('Programm terminated by user.')
#finally:
    asyncio.new_event_loop()

Example LVGL with asyncio

import lv_config
import lvgl as lv
import asyncio
from asyncio import sleep, create_task, Loop

scr = lv.screen_active()
scr.set_style_bg_color(lv.color_hex(0x808080), 0) # gray
# scr.set_style_bg_color(lv.color_hex(0xffb6c1), 0) # pink
# import task_handler
# th = task_handler.TaskHandler()


async def btn_reset_task(obj=None, event=-1):

    print(f"Resetting... Taks rom: {obj}, event: {event}")
    scr.clean()


    rst_spinner = lv.spinner(scr)
    rst_spinner.set_size(100, 100)
    rst_spinner.center()
    rst_spinner.set_anim_params(10000, 60)
    rst_spinner.set_style_bg_color(lv.color_hex(0x808080), 0)  # gray

    rst_label = lv.label(scr)
    rst_label.center()
    rst_label.set_text("Progress...")

    print("Resetting...")
    await asyncio.sleep(5)
    print("Resetting... Done")

    # import machine
    # machine.reset()

    scr.clean()
    btn_reset_gen()

def btn_reset_task_act(arg1, arg2):
    print(f"Resetting... arg1: {arg1}, arg2: {arg2}")
    # print("Resetting...")

def btn_reset_gen():
    # Create a style for the button label

    style_btn_label = lv.style_t()
    style_btn_label.init()
    style_btn_label.set_text_color(lv.color_hex(0xFFFFFF))  # Set text color to white

    btn_reset = lv.button(scr)
    btn_reset.align(lv.ALIGN.TOP_MID, 0, 280)
    btn_reset.add_event_cb(lambda e: create_task(btn_reset_task()), lv.EVENT.CLICKED, None)
    label_reset = lv.label(btn_reset)
    label_reset.set_text('Reset Task')
    label_reset.add_style(style_btn_label, 0)  # Apply the style to the label

    btn_reset = lv.button(scr)
    btn_reset.align(lv.ALIGN.TOP_MID, 0, 10)
    btn_reset.add_event_cb(lambda e: print("Resetting..."), lv.EVENT.CLICKED, None)
    label_reset = lv.label(btn_reset)
    label_reset.set_text('Reset Print')
    label_reset.add_style(style_btn_label, 0)  # Apply the style to the label


btn_reset_gen()

import lv_utils
if not lv_utils.event_loop.is_running():
    eventloop = lv_utils.event_loop(asynchronous=True)

loop = asyncio.get_event_loop()
loop.run_forever()
1 Like

Thank you!

Bonjour,
Today this script IS Always ok ?
No success on esp32 ports and unix port.
One trick ?

I don’t understand your question.

The example works with LVGL8, maybe this is your problem?