Use LVGL with Python3

The knob example works now well, but I needed to add import os at the beginning of the file as os is used now commonly in this example.

The errors for anim_*py remain, not sure if they should be fixed with the update?

I have not checked the other examples yet. I will add the import os to the top of the example. Thanks for catching that.

Unfortunatly I am not going to be able to provide wheel distributions for linux. the Python packaging index (Pypi) will not allow it unless a very specific set or rules is followed. One of the rules only allows specific linux packages to be used and SDL2 is not one of those packages. So windows has wheels and so does OSX.

lvgl is now available using pip install lvgl

I still have to write a proper readme file so it gets displayed. The license file should be good, I included the licenses for LVGL and also SDL2 in that with the MIT license (same as LVGL) for the binding.

While I am not statically linking to SDL2 I am packaging the DLL in the Windows build. This is due to complex issues surrounding compiling LVGL to work with the DLL and the lack of an installer for SDL2 for Windows. in Linux and OSX the library can be installed using the appropriate package manager for the OS so there are no complications with the binding locating it once it is installed.

All sounds very well, and thank for this great work!

It’s not working for me:

$ pip install lvgl
ERROR: Could not find a version that satisfies the requirement lvgl
ERROR: No matching distribution found for lvgl

I wonder if we can host the binding in the LVGL organization. Of course you will have all the permissions there.


Note that the current master version of LVGL is still under development so we should add some warnings and disclaimers.

@kisvegabor

read below…

I have to attach the source tarballs for the library to the package index so it will download that instead and then compile it. There is no way around having to do that on Linux. it really sucks not being able to provide pre built binaries for linux users but there are to many factors that can screw with it running properly.

I saw “There is no way around having to do that on Linux.” so I guess using wxWidgets by default instead of SDL won’t solve it.

I’ve tries the Python 3.9 whl file from the dist folder but got this error:

$python3 arc_1.py 
Traceback (most recent call last):
  File "/home/kisvegabor/projects/lvgl/python3/arc_1.py", line 2, in <module>
    import lvgl as lv
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/__init__.py", line 3, in <module>
    from . import __lib_lvgl as _lib_lvgl  # NOQA
ImportError: cannot import name '__lib_lvgl' from partially initialized module 'lvgl' (most likely due to a circular import) (/home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/__init__.py)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/kisvegabor/projects/lvgl/python3/arc_1.py", line 10, in <module>
    import lvgl as lv
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/__init__.py", line 3, in <module>
    from . import __lib_lvgl as _lib_lvgl  # NOQA
ImportError: cannot import name '__lib_lvgl' from partially initialized module 'lvgl' (most likely due to a circular import) (/home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/__init__.py)

you need to change the example code at the top where it imports LVGL. Change it to a plain ol “import lvgl”… If you are doing this using PyCharm it gets a little bit more complicated because LVGL adds the root folder of the project into the search path for modules. There is an empty “lvgl” that can cause some issues.

check it out…

LVGL using a transparent window

can you say On Screen Display?? OOoOooooO…LOL

knob with transparent window

3 Likes

Truly amazing! :tada: :tada: :tada:

With this
image

I got:

 $python3 arc_1.py 
Traceback (most recent call last):
  File "/home/kisvegabor/projects/lvgl/python3/arc_1.py", line 1, in <module>
    import lvgl as lv
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/__init__.py", line 3, in <module>
    from . import __lib_lvgl as _lib_lvgl  # NOQA
ImportError: cannot import name '__lib_lvgl' from partially initialized module 'lvgl' (most likely due to a circular import) (/home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/__init__.py)

Could you comment on this? :slight_smile:

Yeah we can do that if you like no worries there. I wanted to get it working properly before doing that tho. It is still very much a beta. I still also need to key up a readme for it that outlines how to compile for the different OS’s and how to use it and any special things that need to be done like creating buffers.

LVGL master is still far from beta so don’t worry about it :smiley:

I’d like to send a monthly newsletter in ~2 weeks. Do you think we can announce it in it?

it’s based off of 9.0. I would say that it will be released with 9.0

Also… I wrote a script that will be included in the build process that generates wrapper classes so a user can optionally use the MicroPython API instead if they want. Before they upload the script to an MCU running MicroPython they will only have to change a single thing which is the import for LVGL.

To activate the MicroPython API the user instead of doing this to import LVGL

import lvgl as lv

they do this

import lvgl.mpy as lv

and when they are ready to upload to the MCU they change it to the first one… Not a big deal to do and it offers the ability to prototype for MicroPython on a desktop.

The advantage to this is if the program is of a larger size they don’t have to sit there and wait until it uploads to test it out. They will be able to see if it works without having to upload.

1 Like

You should take a look at the copy of LVGL that is in the repository for the binding and compare it against what you have in the master branch for LVGL. There are a lot of changes that I have made and they need to be gone over. If I had an issue and I could make a change in LVGL that would solve the issue that is what I did if it was going to be more work in order to code around the problem in the binding. Some things I might be able to address differently with a little bit of input from you.

We are able to hash out the best possible way to handle things when we discuss it. 2 brains are better then one I guess. LOL

OH. I also forgot to mention the issues you are having with the wheel. You cannot use the Linux wheels they will not work properly That is the reason why Pypi will not allow them to be uploaded. The only way for you to be able to use it on a Linux system is you have to compile it. I didn’t make the rules with that I am only following them. Too many versions of what could be considered “standard” kind of things but they work differently based on the version on Linux you are using and the versions of other things that are installed into Linux as well.

I don’t believe that Python will even properly load an extension module that has ‘linux’ in it’s name

I also want to bring up setting a naming convention for the various bindings I used the same format that is used for the micropython binding which is lv_micropython. so the python binding is named lv_cpython. I know there is the rust binding and also the CPP binding (WIP) so lv_rust and lv_cpp would be the names for those. IDK what they are named or even if they have been added to the lvgl group of repositories.

I think it’d be useful to publish and announce the beta eariler to get feedback.

Wow, it’s really great! How did you verified that two APIs are really the same? MicroPython has a lot of tests, which could be used for this purpose. cc @amirgon

Could you send a pull request to better see the changes?

In this case what is the purpose of publishing the Linux wheels? I’ve pulled the repo to try teh latest version but I found that builder.py is gone. What is the new way to build it locally?

The convention is lv_binding_.... lv_micropyton is the repository of the actual MicroPython language. The MicroPython binding is in lv_binding_micropython. So I’d call this binding lv_binding_python3.

Naming convention for the lib can be handled when the repo gets moved over to LVGL so that’s not a big deal and we can do that any time you like.

I am not able to make the API work 100% like the MicroPython API and this is because of how CFFI works. As an example.

This is one of the animation examples for MicroPython

##### startup script #####

#!/opt/bin/lv_micropython -i

import lvgl as lv
import display_driver


##### main script #####

def anim_x_cb(label, v):
    label.set_x(v)

def sw_event_cb(e,label):
    sw = e.get_target_obj()

    if sw.has_state(lv.STATE.CHECKED):
        a = lv.anim_t()
        a.init()
        a.set_var(label)
        a.set_values(label.get_x(), 100)
        a.set_time(500)
        a.set_path_cb(lv.anim_t.path_overshoot)
        a.set_custom_exec_cb(lambda a,val: anim_x_cb(label,val))
        lv.anim_t.start(a)
    else:
        a = lv.anim_t()
        a.init()
        a.set_var(label)
        a.set_values(label.get_x(), -label.get_width())
        a.set_time(500)
        a.set_path_cb(lv.anim_t.path_ease_in)
        a.set_custom_exec_cb(lambda a,val: anim_x_cb(label,val))
        lv.anim_t.start(a)

#
# Start animation on an event
#

label = lv.label(lv.scr_act())
label.set_text("Hello animations!")
label.set_pos(100, 10)


sw = lv.switch(lv.scr_act())
sw.center()
sw.add_state(lv.STATE.CHECKED)
sw.add_event(lambda e: sw_event_cb(e,label), lv.EVENT.VALUE_CHANGED, None)

and this is the code for the MicroPython API in the CPython Binding.

##### startup script #####

# !/opt/bin/lv_micropython -i

import lvgl.mpy as lv

import time


lv.init()
disp = lv.sdl_window_create(480, 320)
# group = lv.group_create()
# lv.group_set_default(group)
mouse = lv.sdl_mouse_create()
keyboard = lv.sdl_keyboard_create()
# lv.indev_set_group(keyboard, group)


def anim_x_cb(label, v):
    label.set_x(v)


anim = None


def sw_event_cb(e):
    global anim
    sw = e.get_target_obj()

    if sw.has_state(lv.STATE.CHECKED):
        a = lv.anim_t()
        a.init()
        a.set_var(label)
        a.set_values(label.get_x(), 100)
        a.set_time(500)
        a.set_path_cb(lv.anim_t.path_overshoot)
        a.set_custom_exec_cb(lambda a,val: anim_x_cb(label,val))
        lv.anim_t.start(a)
    else:
        a = lv.anim_t()
        a.init()
        a.set_var(label)
        a.set_values(label.get_x(), -label.get_width())
        a.set_time(500)
        a.set_path_cb(lv.anim_t.path_ease_in)
        a.set_custom_exec_cb(lambda a,val: anim_x_cb(label,val))
        lv.anim_t.start(a)
    anim = a


#
# Start animation on an event
#
label = lv.label(lv.scr_act())
label.set_text("Hello animations!")
label.set_pos(100, 10)

sw = lv.switch(lv.scr_act())
sw.center()
sw.add_state(lv.STATE.CHECKED)
sw.add_event(sw_event_cb, lv.EVENT.VALUE_CHANGED, None)


start = time.time()

while True:
    stop = time.time()
    diff = int((stop * 1000) - (start * 1000))
    if diff >= 1:
        start = stop
        lv.tick_inc(diff)
        lv.task_handler()

The big differences are the import for lvgl which is not a huge deal. I can change that kind of stuff around pretty easily so if the MicroPython API is wanted as the “standard” API and the C API as the secondary I can flip flow them around without too much trouble.

The 2 really big things are how calls to lv_task_hander are done and the garbage collection.

see this function??

def sw_event_cb(e,label):
    sw = e.get_target_obj()

    if sw.has_state(lv.STATE.CHECKED):
        a = lv.anim_t()
        a.init()
        a.set_var(label)
        a.set_values(label.get_x(), 100)
        a.set_time(500)
        a.set_path_cb(lv.anim_t.path_overshoot)
        a.set_custom_exec_cb(lambda a,val: anim_x_cb(label,val))
        lv.anim_t.start(a)
    else:
        a = lv.anim_t()
        a.init()
        a.set_var(label)
        a.set_values(label.get_x(), -label.get_width())
        a.set_time(500)
        a.set_path_cb(lv.anim_t.path_ease_in)
        a.set_custom_exec_cb(lambda a,val: anim_x_cb(label,val))
        lv.anim_t.start(a)

In this function an lvgl object is created (lv_anim_t). Because this object is created inside of a function and doesn’t get set into a global variable at the module level it should be getting garbage collected at the end of the function call. It is not getting garbage collected. something in the C code for the binding is holding a reference to it. Because LVGL is single threaded the callback would have to exit before that code is able to run. which means that object would go out of scope and end up getting garbage collected, which doesn’t happen.

So that means each and every time that function gets called a new object gets created, What happens to the object that was stored in the previous function call? Technically speaking this is being handled incorrectly and it could be a possible memory leak.

That is why there is a global in the first script because it does get garbage collected and the program crashes.

The other piece is how lv_task_handler is dealt with. There is no way to easily do this in CPython because there are no interrupts or timers that don’t use threads. There is no way to keep the calls to lvgl in the main thread unless the code loops like you see at the end of the script.

Now I can pretty it up and hide all of the back end of the display driver and the loop for calling lv_task_handler but there is going to need to be a function call at the end in order to achieve that. No way really to get around it.

I did fix the import issue on linux. I just pushed the changes this morning. I still have to merge the PR with the changes I am waiting for the CI to finish the build to make sure it succeeds.

I do not have the source distributions up on pypi just yet. I have to remove the linux wheels and add the source distribution (sdist) Once that is done you will be able to do pip install lvgl on linux and it will download, compile and install LVGL on linux.

For the time being you can clone the repo, go into the root folder for the repo and type in python -m pip install . (don’t forget the dot as that is important.

I had to fix the pip installation so that would work which I did last night. I will get the changes merged today.

pip install lvgl should now be working for linux

Can’t Python’s extension library be used?

I see. Actually only the animations are using this “create a variable and init it technique”. It would be more consistent if lv_anim_create() returned an lv_anim_t * just like lv_btn_create() returns an lv_obj_t *. I think it should help to avoid the special handling of animations.

I’ve tried pip on Linux and got these error:

$pip uninstall lvgl
Found existing installation: lvgl 0.1.0
Uninstalling lvgl-0.1.0:
  Would remove:
    /home/kisvegabor/.local/lib/python3.9/site-packages/__lib_lvgl.cpython-39-x86_64-linux-gnu.so
    /home/kisvegabor/.local/lib/python3.9/site-packages/lvgl-0.1.0.dist-info/*
    /home/kisvegabor/.local/lib/python3.9/site-packages/lvgl/*
Proceed (y/n)? y
  Successfully uninstalled lvgl-0.1.0
$pip install lvgl
Collecting lvgl
  Downloading lvgl-0.1.1b0.tar.gz (3.6 MB)
     |████████████████████████████████| 3.6 MB 3.4 MB/s 
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  ERROR: Command errored out with exit status 1:
   command: /usr/bin/python3 /tmp/tmp7mxjq_uk_in_process.py get_requires_for_build_wheel /tmp/tmpkeebd2k0
       cwd: /tmp/pip-install-e33wntg4/lvgl_be8638c5bff94ff88cf8b32bc457d1d8
  Complete output (15 lines):
  Traceback (most recent call last):
    File "/tmp/tmp7mxjq_uk_in_process.py", line 280, in <module>
      main()
    File "/tmp/tmp7mxjq_uk_in_process.py", line 263, in main
      json_out['return_val'] = hook(**hook_input['kwargs'])
    File "/tmp/tmp7mxjq_uk_in_process.py", line 114, in get_requires_for_build_wheel
      return hook(config_settings)
    File "/tmp/pip-build-env-qra0581r/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 341, in get_requires_for_build_wheel
      return self._get_build_requires(config_settings, requirements=['wheel'])
    File "/tmp/pip-build-env-qra0581r/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 323, in _get_build_requires
      self.run_setup()
    File "/tmp/pip-build-env-qra0581r/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 338, in run_setup
      exec(code, locals())
    File "<string>", line 11, in <module>
  ModuleNotFoundError: No module named 'builder'
  ----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/cb/18/159d7a3efe6f2024926ecf59020f429c04e58e4ab95b645b66ac43a9baef/lvgl-0.1.1b0.tar.gz#sha256=a6c10df340e3e6c11e83bce58b61f06155296cc4541ba80eb914c9884bef34c9 (from https://pypi.org/simple/lvgl/) (requires-python:>=3.6). Command errored out with exit status 1: /usr/bin/python3 /tmp/tmp7mxjq_uk_in_process.py get_requires_for_build_wheel /tmp/tmpkeebd2k0 Check the logs for full command output.
ERROR: Could not find a version that satisfies the requirement lvgl
ERROR: No matching distribution found for lvgl