Use LVGL with Python3

I mention in a github issue but I thought I should mention it here as well. There is a fault in the LVGL code surrounding callbacks. There are structures that have more than one field that can be filled with a callback but there is only a single user_data field. This will not work because I am only able to fill that user_data slot with a single handle from a python function. that means whatever callback gets placed last is going to be the one that ends up getting called for all of the callbacks in that structure. There is no way to supply more than one handle and there is no way to tell which callback is actually being used.

instead of placing a function pointer directly into those structures a new structure needs to be created for each callback and in that structure would be the function pointer and also a dedicated user_data field for that callback. That is the only way I can see this working properly. The other option is going to be setting user_data as an array but it will become complex to keep track of the indexes in the array and marrying them to a specific callback… doing that would make the code surrounding the callbacks pretty complex.

I did do a kind of hack up job of making all of the callbacks declared function pointers outside of structures and functions. This gave me the ability to get the code written to handle them. So that aspect is all done. making changes from this point on however the user_data issue get handled hopefully won’t end up being a complete rewrite of my code.

Because of the dynamic nature of this binding and nothing really being hard coded there needed to be a way to make it easier to develop for. If using a Python IDE like PyCharm or VSCode all of the code completion and type hinting will work properly. Makes it easier having this.

I have not changed the API from being as close as possible to the C API. I am able to duplicate the MicroPython API if that is what is done. It will increase the code footprint by a large amount and it will also slow things down quite a bit. This is due to the lookups needing to be done while the code is running. It’s not something that can be compiled ahead of time.

I would have to check into it by I may be able to compile the Python code portion into a C extension using Cython. I am not using any metaclasses so I should be able to. This will speed up code execution by up to 200% if it can be done.

(The related GitHub discussion: LINK).

I think I can work around this in the same manner that is done in the MicroPython binding. it’s going to be harder to achieve because it has to be done on the python side where as in the MicroPython binding it is done on the C side which makes it easier to do.

1 Like

as an update. I have worked around the callback problems. Instead of having a dynamic approach and generating a stub file i went the route of a more static approach and instead of generating a stub file I am now generating a source file with the binding being hard coded. It is easier to follow a data path through and fix bugs/problems.
So the callback issues are almost all solved. still need to have function pointers instead of them being declared as a parameter and their needs to be names for the parameters in the function pointer. Nothing to horrible tho.

Then I ran into my second problem. This one seemed like a complete mystery and it would show up at random times and in random places. I finally narrowed it down to the built in memory allocation functions in LVGL. IDK if the issue was only when it was compiled with MSVC or not but after jumping through some hoops I was finally able to set the MSVC memory functions for LVGL to use instead of the builtin ones. The jumping through hoops is because MSVC doesn’t conform to C standards so things are not where they are in the standard C lib.

There are still some issues I have to hammer out with it and I am not sure why they are happening. Like corruption of an image when loading an image into an lv_img. I am sure I would manage to locate where the issues is and I am sure the issue is not in the LVGL code and more than likely is some kind of a glitch with how MSVC memory allocation works and it not playing nice nice with LVGL.

The last problem of the data corruption has been solved. I am really happy about this. Here is a video of it in action.

That example uses a lot of the mechanics in LVGL. from events to gradients and canvas rendering. Most of these things are done real time.currently I am using 2 images for the gradients, If there was a way to render a radial gradient in LVGL I would render the gradients instead so it would be 100% self contained. Keeping my fingers crossed with it getting added sooner than later.

You can see in the above example how good a GUI control can look using LVGL. High quality professional looking. Running the code for that example the Heap size used is a touch over 8mb. That is with almost 2 MB of that being the buffer for flushing the display (800 x 600 x 32bit). Python uses something like 5mb and there are also 2 images that are loaded from the file system which also adds about 200k to the total as well. SDL2 which is what handles the creation of the window adds a fairly decent amount to the ram use too.

Last thing I have to do is do some housekeeping, write up a CI script to compile it for the major OS’s and generate wheels to put up onto Pypi. Everything seems to be working properly at this point and I can fix any bugs should they come up.

I have elected to keep the API so it is as close as possible to the C API this way the documentation will largely apply to the binding and it will also make it easier for porting the code to C to be compiled for an MCU. Nice fast way to be able to develop for LVGL without the whole compile, flash, nope not working right, compile flash… which is enough to make a person go bananas doing that over and over again.

I am going to push my most recent updates and also some more examples to the repo later on this evening.

EDIT

I just did some quick checks on memory use. and this is what I came up with.
Test was a blank screen with a resolution of 800 x 600 and a color depth of 32bits

# 953.90k heap just running Python and nothing else
# 1944k heap with lvgl module loaded
# 4166k heap with lvgl running a blank display
# ----------------------------------------------
# 1944 - 953.90 = 990.1
# LVGL CPython library uses 990.1k of memory, This is how much memory the 
# binding library uses.
#
# 4166 - 953.90 = 3212.1
# LVGL running uses 3212.1k of memory, This
# is the total memory consumption of the library and LVGL running 
#
# 800 * 600 * 4 = 1920
# the display buffer uses 1920k of heap memory
#
# 3212.1 - 1920 = 1,292.1
# if we take the frame buffer size out of the memory use.
# the buffer is a large size and would not be something most people running 
# LVGL would do, that places the heap at 1292.1k
#
# 1292.1 - 990.1 = 302
# Now we remove the amount used by the library to get the total heap cost 
# to run LVGL which is 302k. Now mind you this also has SDL2 added into 
# the mix which I am sure carries with it the majority of that 302k

I think we are on par with memory use from LVGL and that indicates no memory leaks so far. I have done an analysis and there are subtractions and additions to the heap. It is not growing in size, the subtractions and additions keep the size pretty consistent. so garbage collection is working properly and no memory leaks so far.

1 Like

It’s amazing!

We can easily do it.

Wow, looking really great!

:+1:

It’s almost the size of an embedded lib for a desktop app. Awesome! We need some LVGL features to make it really good for embedded, but all seems doable.


In general I’m really happy that you have created this binding. It paves the way for LVGL to the desktop market!

1 Like

Not just the desktop market. It also allows people to run it on say a Raspberry Pi without the need to run a limited Python. Thing like numpy can be used in conjunction with LVGL. What is really cool is the ability to us LVGL in conjunction with other GUI frameworks. Take wxWidgets as an example. There is a binding for it in Python called wxPython. This GUI framework is designed to display as close to what the OS is designed which is great and all but when you need to do anything in the way of custom rendering it is not the most user friendly at doing that. It is also slow and has glitches all of the place in it. LVGL can be used to handle that aspect if a person wanted to.

I provided an example that shows how to get LVGL and wxPython to work together. Keyboard and Mouse are coded in that example too.

I am not seeing any memory leaks at all. Everything is stable, no crashing or anything like that.

OH… I also added the benchmark to it. I wanted to see how well it was performing under some stress. I changed the number of objects from 15 to 30 with an 800 x 600 x 32bit display. it averaged something like 160 FPS.

gonna have to to get a little crazy with the tests. rotate, zoom, color change and position change on objects all at the same time. I am wondering where LVGL becomes the bottle neck.

2 Likes

One add-on I can write for the binding is it converting a python script to C code so long as the organization is pretty flat like how it’s done in C code.

another add-on that can be done would duplicate the MicroPython API. The ability to subclass things like structures can be done because of how I wrote the binding. so writing a wrapper around the binding to provide a MicroPython API would not be that difficult to do.

OK so I got the repo on par with how it should be. Proper setup script to compile and create wheels (for when I do a release to Pypi). Azure pipelines for the CI. So it will compile and generate wheels for Windows 2011, Windows 2010, Ubuntu 22.04, Ubuntu 20.04, OSX Big Sur, OSX Monterey and OSX Ventura. It will compile using Python 3.8, 3.9, 3.10 and 3.11 (only x64)

EDIT

I dropped building for Python 3.8… for whatever reason setuptools is a little funky with how it builds and it is not the same as later versions of Python. It was causing me a hard hour to get it right.

There is also some kind of issue with using Python 3.11 and compiling for OSX. for some crazy reason SDL2 pitches a fit about it not being an x86/x64 system. It simply will not compile. I think there might be something with the homebrew distribution of SDL2 and it not being correct or something is tweaky somewhere that is outside of my control.

One build is failing and that is for Python 3.11 on Windows and this is because I have to make a change to pyMSVC which is a nice little bit of code I wrote that sets up a perfect build environment when compiling under Windows. This script has helped me more than any other piece of code I have written. It’s a small thing to fix I just have to bump versions, make the fix and update the build system for the binding so it uses the newest version of pyMSVC.

So the final outcome is.

Ubuntu 20.04, Python 3.9, 3.10, 3.11
Ubuntu 22.04, Python 3.9, 3.10, 3.11
Windows 10, Python 3.9, 3.10, 3.11
Windows 11, Python 3.9, 3.10, 3.11
OSX 11, Python 3.9, 3.10
OSX 12, Python 3.9, 3.10
OSX 13, Python 3.9, 3.10

I think that is a decent enough spread. other operating systems people can compile it. Too many OS’s to provide compiled binaries for all of them. Those are pretty much the majors and I am happy with that.

1 Like

I did want to mention that the fix I used for the memory allocation problems I was having is only used on Windows builds. If the issue also exists in the other builds I am able to use the same solution. I think it is going to only be with Windows because the compiler is not Ansi C compliant. it’s close to being compliant but maybe not close enough. clang and gcc are both compliant and I suspect that no issues will exist with those builds.

I forgot to post where the wheels can be gotten from. This is for those that want to give it a test run.

https://dev.azure.com/drschlosser/drschlosser/_build/results?buildId=115&view=artifacts&pathAsName=false&type=publishedArtifacts

click on “dist” and then click on “dist” again. Download the appropriate wheel for your python version and OS. to install the wheel use

python -m pip install {wheel} where {wheel} is the downloaded file name.

@kdschlosser thank you so much for your efforts! This is really great stuff.

click on “dist” and then click on “dist” again.

for some reason, this does not work for me (just nothing happens when clicking on first “dist”
However, i can use the drop down menu on the right side do download the whole thing, which includes the second “dist” and the wheels contained there.

When you click on the first “dist” it should show an entire folder tree in the window. You would have to possibly scroll down to see the second dist.

Yes, clear - it just does not work (for me) as the first “dist” is not a link.
If it is only for me, no issue - i can manage.

I am curious to know.

This is what I see

also there are examples you can use to try it out. they are located here

Some of the examples are not yet finished. I still have a lot of work to do with the binding. this is an alpha test version that I wanted to share with those willing to give it a go. Need to know if there are any bugs or problems that do not show up on my system.

That’s what i see.

as you can see, difference is no “>” and “dist” is not clickable. I would imagine, it’s a permission issue :wink:

For my initial use-case (i’m happy to test for) i need do make it run directly on the linux framebuffer.
However, i’m happy to test as well with what is in the repo. Again, many thanks for your effort.

EDIT:
Tested the binary on my Win11 / Python3.10.11 Setup.
Most of the examples work well.
anim_1 raises exception (at mouse or keyboard input):

    res = _lib_lvgl.lib.lv_task_handler()  # NOQA
TypeError: an integer is required
"anim_path_cb_t" is not registered
Exception ignored from cffi callback <function __anim_path_cb_t_callback_func at 0x000001F3C10980D0>, trying to convert the result back to C:
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\lvgl\__init__.py", line 46822, in task_handler

anim_2 raises continously the same exception as above.

knob.py refers to the not existent ‘knob_gradient.png’

I just fixed the knob example. make sure you download the assets folder and keep it in the same directory as the knob script.

I also have to check on the latest build. There was a goof in my code that I needed to fix. I need to check and see if it compiled properly.

Attached is the latest wheel for Windows running Python 3.10

You have to take the .txt off the end of the filename. Forum only allows .txt and image extensions.

lvgl-0.1.0-cp310-cp310-win_amd64.whl.txt (1.7 MB)

EDIT

I finally got the Python 3.11 to compile properly. Goofy issue in my pyMSVC script and also the varying compiler versions that are being used to compile Python. It seems there is no rhyme or reason to the versions being used. one release will be one version and then the next release can either be the same version, a newer version and get this… sometimes it is an older version. So I changed the pyMSVC script to collect from Python the version of the compiler that was used to compile it. Now the script will correctly pick the MSVC compiler to use. Ideally the same compiler is wanted, of that is not available then anything newer will be used. If there is not one available but an older one is available and the “strict” setting has not been set then a warning gets printed out… if strict is set then the script will error.