Ö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
#import webrepl
import lvgl as lv

from ili9XXX import ili9341
from xpt2046 import xpt2046


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)

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:

So the deinit is not needed anymore. But nothing else has changed from a user perspective, right?

Hmmm … I have problems with that setup and my httpd every now and then complains that the scheduler queue is full and subsequently crashes. For some reason it seems the scheduler is still being used even though lvesp32 is completely removed from my MP files and I am setting asynchronous=True and initialize=True. Odd. I’ll try to find out what’s going on …

It sounds like somewhere lvesp32 is still imported.
You can try removing it from lv_micropython (remove this line) and see where some script fails.

It seems noone imports it and everything looks good with that module removed.

Putting the line back gives me the problem back.

Actually putting it back in makes all animations run at double speed so it’s pretty obvious that the scheduler is active again and both methods run in parallel and advance ticks. You don’t see that effect?

Uhm, what about this one:

I wonder why that doesn’t trigger an exception now. I thought only viper’d stuff would lack exception support.

I don’t see this effect probably because mostly I don’t use xpt2046. I’m usually using raw touch driver.

I think you are right, and I think we can completely remove lvesp32 from xpt2046.py (including self.finalizer which is not in use anyway).

If this is working well for you I’ll remove them from xpt2046.py on the GitHub repo.

I think that incremental build is not working well for frozen modules such as xpt2046, so if you don’t clean before building you might still be using the old frozen xpt2046.py.

Yes, the xpt2046 without lvesp32 works fine for me.

No, I wasn’t touching xpt2046 at all. It still contained the lvesp32 import and it was still working although lvesp32 was gone. IMO it should have thrown an exception but it didn’t.

I’ve updated lv_micropython_bindings and lv_micropython with updated xpt2046 without lvesp32.

Works for me. Thanks!

I am in the process of porting the http server/micropython/lvgl/blockly setup to the latest micropython and ĺvgl.

How is async lvgl supposed to work in the latest version? I see that the lv_async module is gone. Is there a replacement?

The http server is not working anymore once I load the ili9341 driver. I have not debugged this further (need to remember how i debugged that in the first place) but this sounds like the scheduler problem is back.

Yes of course, LVGL v8 supports uasyncio.
In v7 we had a problem: each platform (stm32, esp32, unix) implemented its own LVGL event loop and uasyncio implemented another one.

In v8 we removed all platform specific event loops modules (lvesp32, lvstm32, lv_async) and replaced them by a single cross-platform LVGL event loop (event_loop from lv_utils) that supports all platforms and is configurable (allows selecting refresh rate, timer id etc.).
So we also removed lv_async which is now superseded by the same cross-platform event loop in asynchronous mode.
More details are in this GitHub issue.

Please have a look at lv_utils.py, this is where the new event loop implemented. It also includes some examples. The ili9XXX driver will initialize the event loop automatically if it wasn’t initialized yet. Make sure you set asynchronous=True to run it in uasyncio mode.
Also, please have a look at uasyncio_example1.py.

Please let me know if you need my help with this. I’m not aware of any special reason why it wouldn’t work on v8. Possibly it could somehow be related to the new event loop.
The ili9341 driver itself did not really change from v7.