Hi @Stepan_Snigirev!
When lvgl Micropython binding registers a callback, it must also register the callable micropython object.
Today the convention is that it expects the registering function to receive a struct with a field called user_data. More information on the calling conventions is on the README.
Unfortunately, lv_async_call fails to obey this calling convention.
This was done knowingly. Here is lv_async_call prototype - have a look at the comment above it:
/**
* Call an asynchronous function the next time lv_task_handler() is run. This function is likely to return
* **before** the call actually happens!
* @param task_xcb a callback which is the task itself.
* (the 'x' in the argument name indicates that its not a fully generic function because it not follows
* the `func_name(object, callback, ...)` convention)
* @param user_data custom parameter
*/
lv_res_t lv_async_call(lv_async_cb_t async_xcb, void * user_data);
In such case, the Micropython callback registration function (lv.async_call) would only accept a pointer to a real C function, which is not what you want.
There are couple of options to resolve this:
Add another flavor of lv_async_call which obeys the calling convention. @kisvegabor - Is that doable?
Enhance the Micropython binding to accept another calling convention. In this case the user_data is passed as the second argument so this might be doable.
Create a C wrapper function which we can pass to lv_async_call, and would cast user_data to a callable Micropython object. That’s a bit cumbersome, so I would try the previous options first.
I wrote a small workaround in micropython for this purpose - a decorator for functions that should be called after the next lvgl update and not during the callback. Maybe there is a more elegant way, but this works fine for me.
Decorator:
def queued(func):
"""A decorator to put a function in a queue
after lvgl update instead of calling it right away
"""
def wrapper(*args, **kwargs):
queue.append((func, args, kwargs))
return wrapper
Then I can specify a function that is dangerous to call from the callback:
@queued
def clean_screen():
"""An example of queued function"""
lv.scr_act().clean()
And in my ioloop where I update lvgl I check if the queue has any pending callbacks and call them:
def ioloop():
# other stuff
lv.task_handler()
while len(queue) > 0:
cb, args, kwargs = queue.pop()
cb(*args, **kwargs)