I am a begginer to LVGL and I am struggling with understanding how should I use (or not?) event loop and lv_task_handler.
I am developing app GUI on RPI Zero 2 (so UNIX port) with framebuffer as display driver and evdev touchscreen driver. I am a little bit confused with the lack of visible event loop in examples like this one. When I write my application accordingly then it exits immediately, so I added a time.sleep(10) at the end and it started working for 10s, so I assume that there should be some kind of forever loop there? Another thing is if I should call lv.task_handler() ? Should I do it periodically? Is it done by framebuffer driver?
Also, I need to periodically update some data object which is used by GUI to determine state. Should I create task, use _threads or uasyncio? I really need some guidance here. Any help much appreciated
The frame buffer display driver itself, by default, drives the event loop.
Here it is:
If you provide auto_refresh=False to init it will override the default event loop and not start it.
Then you can either run your own event loop, or use a different event loop such as event_loop provided by lv_utils.py.
So this is done by the display driver automatically by default, unless you override it
You only need to call lv.task_handler() if you override it and want to run your own event loop for some reason. Otherwise you don’t need to and it is called automatically.
Usually you don’t access the UI from another thread.
You can use LVGL’s timer (formerly called “task”) if you want. If you do, your task would be called (indirectly) from lv.task_handler().
If you would like to use uasyncio you need the event loop itself to run through uasyncio. You can do that by disabling the automatic event loop (auto_refresh=False) and using event_loop from lv_utils.py with asynchronous=True parameter.
Here is an example of using LVGL this way with uasyncio:
Thank you @amirgon! That’s really helpful overview. I experimented a little and found out that using a timer to obtain the data would be the best option. Now I have a problem: how can I propagate new data to UI? My UI almost entirely uses data which should be in real time obtained from communication bus. I thought that if I design my whole UI basing on some data object (e.g. dictionary or custom class) then if I update the object, the UI will update accordingly. I have read this topic and from what I understood I think I was wrong.
So, do I need to periodically send refresh event to every object which relies on my data object? In 8.1 documentation I didn’t find anything about recursive event send (which was present in 7.11) so I assume that I cannot just send refresh event to main screen? Or maybe there is some better way to handle such cases? Or maybe LVGL designed to have frontend and backend bonded together (which would be very sad from my perspective)?
I found some C related topics about global variables that should be used for that. It it applicable to micropython?
Actually, I’m not sure I understand your question.
In LVGL there is no data-binding in the sense of reactive programming where the data is automatically reflected in the UI (although this was discussed in the past).
In LVGL the user is responsible of explicitly calling UI functions in order to change the data that is displayed.
So simply, when you obtain new data from whatever external source, just call the LVGL functions to update the relevant UI elements.
Generally you don’t need to send refresh events, this is done automatically when you call these UI functions.
Again, I’m not sure what you mean. Sure you can use global variables, but I don’t see the point or the relation to the problem you are describing.
That’s good indicator that I probably don’t understand this workflow at all
I’ll try to explain myself better:
My goal is to build something which could be described as CAN bus monitor. So basically, in my UI I want to just present in the real time the data which travels over bus in frames (it’s like 10-20 frames per second I my case). So if I get a specific frame, lets say every 200ms, and the data in the frame (e.g some sensor reading) changes in every frame, I want the UI to reflect this changes immediately. How can I achieve that?
I have read the issue on github you send me, so now I see that I have to handle data changes on my own. I am fine with that. I just don’t understand how can I do that?
Let’s assume the situation :
import lvgl as lv
# create some data source object
data = my_data_source.MyDataSource()
# get data from bus periodically
timer = lv.timer_create_basic()
timer.set_cb(lambda t: data.update())
disp_buf1 = lv.disp_draw_buf_t()
buf1_1 = bytes(480*10)
disp_buf1.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.draw_buf = disp_buf1
disp_drv.flush_cb = fb.flush
disp_drv.hor_res = 480
disp_drv.ver_res = 320
scr = lv.obj()
btn = lv.btn(scr)
btn.align(lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text(data.get("btn_label_text")) # use data object to set value
btn2 = lv.btn(scr)
btn2.align_to(btn, lv.ALIGN.OUT_BOTTOM_MID, 0, 0)
label2 = lv.label(btn2)
mouse = evdev.mouse_indev(scr)
What should I add to have my buttons labels updated every time the value changes? Something like this? Or maybe I should somewhere keep the references to all objects which are affected by data changes and then every data.update() I should update these objects (which seem hard at first, because in my case it’s not only about labels but also some parts (objects) of the UI are displayed (or not) accordingly to the data incoming from bus)? How to handle this conditional UI changes?
You defined a timer and you call data.update().
On the same timer callback, right after calling data.update() you should call LVGL functions to update your UI objects according to the updated data.
I don’t see why you need to trigger or register “refresh” events for your use case.
You should keep all your UI objects somewhere, for example as data members of a python object (or in a list, or in any other way you want) and update them (call LVGL functions) whenever you want them to change or dispay something differently.
You can either check if data changed before updating a UI element, or you can update all of them anyway whether data changed or not. This Depends on your implementation details, number of objects, type of updates etc.
This is the meaning of what I mentioned above - that “In LVGL there is no data-binding in the sense of reactive programming where the data is automatically reflected in the UI”.
It means that the UI doesn’t update automatically when your data changes. The UI is not aware of your data at all. You need to explicitly tell each UI object what to disply and when to display by calling an LVGL function of that object. So obviously you need to keep track of your UI objects somewhere.
Obviously, it could be easier to just bind the data to an UI object and only change the data without caring about the UI object any more. But this is not supported by LVGL today.
First of all - thank you for your help! I really appreciate the time you spent answering my questions. I think that I know the direction I should follow with my implementation.
I have some final (hopefully) doubts. You mentioned:
Is there any significant downside of this approach? For now I started experimenting with it and it seems to work as expected. In my UI I have multiple screens with very different content on them so I assumed that it would be better to update only these objects which are visible (or better: exist, because I am deleting screen and all its children when I am switching to another one). When I am building new screen I simply collect references to all objects which could be affected by data change. For all these objects I have created refresh event callback functions. Then I am starting timer with desired period to send refresh events to collected objects. This seemed easier to me, since I don’t have to know in “main” timer (with data.update()) which screen is visible and therefore which objects, along all, should be updated. This way I can also differ refresh period for different screens, according to their characteristic. When I am switching the screen I am stopping the timer (since I don’t know how to delete it - I couldn’t find equivalent to void lv_timer_del(lv_timer_t *timer) in micopython API). Seems to work smooth, but I would be glad to hear your opinion on this.
I think I understand now what you are trying to do.
Instead of individually refreshing each UI element, you send an event to a group of elements and update them with a custom event callback.
This can work.
The downsides of this approch:
Event callback might be more expensive (consume more cycles) than updating the elements directly. The callback itself has some overhead even if it does nothing.
“Refresh” event might be sent by LVGL, not only by you, and your callbacks would be called although there is nothing new to update.
So just make sure that you don’t get performance hit because of lots of Micropython callbacks that are called in high rate.
If you don’t delete timers then they still consume memory and maintained by LVGL.
So if you are constantly creating new timers that’s a memory leak (and could also hit performance at some point).
In general, the entire LVGL API is available in Micropython.
To delete a timer you call timer._del() where timer is the struct returned by lv.timer_create_basic().
It’s underscored to avoid ambiguity, because del is reserved and has special meaning in Python.