ÖLVGL using asyncio

I am starting a new thread for this to keep things seperate and to make it easier to follow and find stuff.

In the thread Console widget for running simple user scripts within lvgl it was suggested to use asnycio:

Using threads is one option, but it may have some disadvantages (heavy weight, unsafe, etc.)

Another option is using uasyncio. uasyncio allows you use the “async/await” concurrent programming scheme which is very popular today. Anyone who ever wrote JavaScript code would be very familiar with this. Python 3 also supports that.

On Micropython it’s relatively new, but I’m using it and it works well with LVGL. In my case I’m doing async network operations without blocking the GUI, but that would work the same way for IO operations.

The idea is that instead of threads you create “tasks” which are actually non-preepmtive co-routines. Under the hoods there is only a single thread, and tasks are scheduled on that thread.

To use LVGL with uasyncio you need to import async_utils.py 2 and create an lv_async object. lv_async creates a uasyncio task that calls the LVGL event loop (lv.tick_inc and lv.task_handler).

Do not import lvesp32 on esp32 , or lvstm32 on stm32 when using lv_async, since lvesp32/lvstm32 are scheduling LVGL event loop using preemptive threads. lv_async is replaces them.

On the unix port set auto_refresh to False on the SDL driver and set refresh_func like this: lv_async(refresh_func = SDL.refresh), since by default the SDL driver is doing both display handling and the LVGL event loop.

It was my assumption the lvesp32 has the magic to operate the gui in the background and it would stop working once i remove any reference to lvesp32. The I’d have to ass this asyncio helper to get stuff working afgain.

For me this doesn’t seem to be that case. The user interface reponse to clicks without any refrence to lvesp32 nor and asyncio helpers. These are my boot.py and main.py of a fully working setup:

# This file is executed on every boot (including wake-boot from deepsleep)
#import esp
#esp.osdebug(None)
#import webrepl
#webrepl.start()
import lvgl as lv

from ili9XXX import ili9341
from xpt2046 import xpt2046

lv.init()

disp = ili9341(miso=19, mosi=23, clk=18, cs=5, dc=32, rst=27, spihost=1, power=-1, backlight=33, backlight_on=1, mhz=80, factor=4, hybrid= True)

touch = xpt2046(cs=26, spihost=1, mhz=5, max_cmds=16, cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, transpose = True, samples = 3)

scr = lv.obj()
btn = lv.btn(scr)
btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text("Button")
lv.scr_load(scr)

I think it makes sense to understand this before proceeding with asyncio.

Copying my answer from the other thread:

To work around this you can call lvesp32.deinit after importing ili9XXX, before creating an instance of lv_async . That’s what I’m doing currently.
In the future it might be a good idea to stop importing lvesp32 from ili9XXX and requiring the users to import lvesp32 explcitly.

I’ve made some improvements to ili9xxx so it would work more smoothly with uasyncio.
Also, added an example of using uasyncio with LVGL: