How to use lv.async_call?

I am trying to use lv.async_call to clean up the screen and draw a new one in a button callback from the same screen.
In C I usually do:

void callback(void * ptr){
// do stuff
}

lv_async_call(callback, NULL);

In micropython I tried similar approach:

def cb(ptr):
    # do stuff

lv.async_call(cb, None)

but it failed with error:
Cannot convert 'function' to pointer!

How can I pass a python function to the lv.async_call?

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.

If this is important to you, please open a new issue on https://github.com/littlevgl/lv_binding_micropython and we’ll try to implement a solution.

Thanks @amirgon,

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)

Thanks for showing us your workaround.

I still think we should solve it on lvgl/binding side, so I’ve opened the github issue:

@amirgon, thanks! It would be great — I will be happy to switch to lv.async_call as soon as it is available.

@Stepan_Snigirev it’s available now on dev-6.1 branch.

See https://github.com/littlevgl/lv_binding_micropython/issues/48#issuecomment-552146653

Awesome! Thank you very much!