From 465ef194752a81193631782a05562bc7457a5975 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Thu, 17 Oct 2024 13:03:53 +1100
Subject: [PATCH 01/18] gen_mpy: Update to support micropython 1.24

---
 gen/gen_mpy.py | 212 ++++++++++++++++++++++++-------------------------
 1 file changed, 103 insertions(+), 109 deletions(-)

diff --git a/gen/gen_mpy.py b/gen/gen_mpy.py
index 0b8437e27..af729e479 100644
--- a/gen/gen_mpy.py
+++ b/gen/gen_mpy.py
@@ -10,9 +10,7 @@
 from __future__ import print_function
 import collections
 import sys
-import struct
 import copy
-from itertools import chain
 from functools import lru_cache
 import json
 import os
@@ -797,10 +795,10 @@ def register_int_ptr_type(convertor, *types):
     const mp_obj_type_t *mp_obj_type;
 }} mp_lv_obj_type_t;
 
-STATIC const mp_lv_obj_type_t mp_lv_{base_obj}_type;
-STATIC const mp_lv_obj_type_t *mp_lv_obj_types[];
+static const mp_lv_obj_type_t mp_lv_{base_obj}_type;
+static const mp_lv_obj_type_t *mp_lv_obj_types[];
 
-STATIC inline const mp_obj_type_t *get_BaseObj_type()
+static inline const mp_obj_type_t *get_BaseObj_type()
 {{
     return mp_lv_{base_obj}_type.mp_obj_type;
 }}
@@ -839,28 +837,26 @@ def register_int_ptr_type(convertor, *types):
     void *lv_fun;
 } mp_lv_obj_fun_builtin_var_t;
 
-STATIC mp_obj_t lv_fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args);
-STATIC mp_int_t mp_func_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
+static mp_obj_t lv_fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args);
+static mp_int_t mp_func_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
 
-GENMPY_UNUSED STATIC MP_DEFINE_CONST_OBJ_TYPE(
+GENMPY_UNUSED static MP_DEFINE_CONST_OBJ_TYPE(
     mp_lv_type_fun_builtin_var,
     MP_QSTR_function,
     MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN,
     call, lv_fun_builtin_var_call,
-    unary_op, mp_generic_unary_op,
     buffer, mp_func_get_buffer
 );
 
-GENMPY_UNUSED STATIC MP_DEFINE_CONST_OBJ_TYPE(
+GENMPY_UNUSED static MP_DEFINE_CONST_OBJ_TYPE(
     mp_lv_type_fun_builtin_static_var,
     MP_QSTR_function,
     MP_TYPE_FLAG_BUILTIN_FUN,
     call, lv_fun_builtin_var_call,
-    unary_op, mp_generic_unary_op,
     buffer, mp_func_get_buffer
 );
 
-STATIC mp_obj_t lv_fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+static mp_obj_t lv_fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
     assert(MP_OBJ_IS_TYPE(self_in, &mp_lv_type_fun_builtin_var) ||
            MP_OBJ_IS_TYPE(self_in, &mp_lv_type_fun_builtin_static_var));
     mp_lv_obj_fun_builtin_var_t *self = MP_OBJ_TO_PTR(self_in);
@@ -868,7 +864,7 @@ def register_int_ptr_type(convertor, *types):
     return self->mp_fun(n_args, args, self->lv_fun);
 }
 
-STATIC mp_int_t mp_func_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
+static mp_int_t mp_func_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
     (void)flags;
     assert(MP_OBJ_IS_TYPE(self_in, &mp_lv_type_fun_builtin_var) ||
            MP_OBJ_IS_TYPE(self_in, &mp_lv_type_fun_builtin_static_var));
@@ -896,17 +892,17 @@ def register_int_ptr_type(convertor, *types):
     void *data;
 } mp_lv_struct_t;
 
-STATIC const mp_lv_struct_t mp_lv_null_obj;
+static const mp_lv_struct_t mp_lv_null_obj;
 
 #ifdef LV_OBJ_T
-STATIC mp_int_t mp_lv_obj_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
+static mp_int_t mp_lv_obj_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
 #else
-STATIC mp_int_t mp_lv_obj_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags){ return 0; }
+static mp_int_t mp_lv_obj_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags){ return 0; }
 #endif
 
-STATIC mp_int_t mp_blob_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
+static mp_int_t mp_blob_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
 
-STATIC mp_obj_t get_native_obj(mp_obj_t mp_obj)
+static mp_obj_t get_native_obj(mp_obj_t mp_obj)
 {
     if (!MP_OBJ_IS_OBJ(mp_obj)) return mp_obj;
     const mp_obj_type_t *native_type = ((mp_obj_base_t*)mp_obj)->type;
@@ -920,15 +916,15 @@ def register_int_ptr_type(convertor, *types):
     return mp_obj_cast_to_native_base(mp_obj, MP_OBJ_FROM_PTR(native_type));
 }
 
-STATIC mp_obj_t dict_to_struct(mp_obj_t dict, const mp_obj_type_t *type);
+static mp_obj_t dict_to_struct(mp_obj_t dict, const mp_obj_type_t *type);
 
-STATIC mp_obj_t make_new_lv_struct(
+static mp_obj_t make_new_lv_struct(
     const mp_obj_type_t *type,
     size_t n_args,
     size_t n_kw,
     const mp_obj_t *args);
 
-STATIC mp_obj_t cast(mp_obj_t mp_obj, const mp_obj_type_t *mp_type)
+static mp_obj_t cast(mp_obj_t mp_obj, const mp_obj_type_t *mp_type)
 {
     mp_obj_t res = NULL;
     if (mp_obj == mp_const_none && MP_OBJ_TYPE_GET_SLOT_OR_NULL(mp_type, make_new) == &make_new_lv_struct) {
@@ -962,7 +958,7 @@ def register_int_ptr_type(convertor, *types):
     LV_OBJ_T *callbacks;
 } mp_lv_obj_t;
 
-STATIC inline LV_OBJ_T *mp_to_lv(mp_obj_t mp_obj)
+static inline LV_OBJ_T *mp_to_lv(mp_obj_t mp_obj)
 {
     if (mp_obj == NULL || mp_obj == mp_const_none) return NULL;
     mp_obj_t native_obj = get_native_obj(mp_obj);
@@ -977,7 +973,7 @@ def register_int_ptr_type(convertor, *types):
     return mp_lv_obj->lv_obj;
 }
 
-STATIC inline LV_OBJ_T *mp_get_callbacks(mp_obj_t mp_obj)
+static inline LV_OBJ_T *mp_get_callbacks(mp_obj_t mp_obj)
 {
     if (mp_obj == NULL || mp_obj == mp_const_none) return NULL;
     mp_lv_obj_t *mp_lv_obj = MP_OBJ_TO_PTR(get_native_obj(mp_obj));
@@ -989,9 +985,9 @@ def register_int_ptr_type(convertor, *types):
     return mp_lv_obj->callbacks;
 }
 
-STATIC inline const mp_obj_type_t *get_BaseObj_type();
+static inline const mp_obj_type_t *get_BaseObj_type();
 
-STATIC void mp_lv_delete_cb(lv_event_t * e)
+static void mp_lv_delete_cb(lv_event_t * e)
 {
     LV_OBJ_T *lv_obj = e->current_target;
     if (lv_obj){
@@ -1002,7 +998,7 @@ def register_int_ptr_type(convertor, *types):
     }
 }
 
-STATIC inline mp_obj_t lv_to_mp(LV_OBJ_T *lv_obj)
+static inline mp_obj_t lv_to_mp(LV_OBJ_T *lv_obj)
 {
     if (lv_obj == NULL) return mp_const_none;
     mp_lv_obj_t *self = (mp_lv_obj_t*)lv_obj->user_data;
@@ -1036,9 +1032,9 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(self);
 }
 
-STATIC void* mp_to_ptr(mp_obj_t self_in);
+static void* mp_to_ptr(mp_obj_t self_in);
 
-STATIC mp_obj_t cast_obj_type(const mp_obj_type_t* type, mp_obj_t obj)
+static mp_obj_t cast_obj_type(const mp_obj_type_t* type, mp_obj_t obj)
 {
     mp_lv_obj_t *self = m_new_obj(mp_lv_obj_t);
     *self = (mp_lv_obj_t){
@@ -1050,12 +1046,12 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(self);
 }
 
-STATIC mp_obj_t cast_obj(mp_obj_t type_obj, mp_obj_t obj)
+static mp_obj_t cast_obj(mp_obj_t type_obj, mp_obj_t obj)
 {
     return cast_obj_type((const mp_obj_type_t *)type_obj, obj);
 }
 
-STATIC mp_obj_t make_new(
+static mp_obj_t make_new(
     const mp_lv_obj_fun_builtin_var_t *lv_obj_var,
     const mp_obj_type_t *type,
     size_t n_args,
@@ -1081,10 +1077,10 @@ def register_int_ptr_type(convertor, *types):
     return lv_obj;
 }
 
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(cast_obj_obj, cast_obj);
-STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(cast_obj_class_method, MP_ROM_PTR(&cast_obj_obj));
+static MP_DEFINE_CONST_FUN_OBJ_2(cast_obj_obj, cast_obj);
+static MP_DEFINE_CONST_CLASSMETHOD_OBJ(cast_obj_class_method, MP_ROM_PTR(&cast_obj_obj));
 
-STATIC mp_int_t mp_lv_obj_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
+static mp_int_t mp_lv_obj_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
     (void)flags;
     mp_lv_obj_t *self = MP_OBJ_TO_PTR(self_in);
 
@@ -1094,7 +1090,7 @@ def register_int_ptr_type(convertor, *types):
     return 0;
 }
 
-STATIC mp_obj_t mp_lv_obj_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in)
+static mp_obj_t mp_lv_obj_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in)
 {
     mp_lv_obj_t *lhs = MP_OBJ_TO_PTR(lhs_in);
     mp_lv_obj_t *rhs = MP_OBJ_TO_PTR(rhs_in);
@@ -1132,17 +1128,17 @@ def register_int_ptr_type(convertor, *types):
 
 #endif
 
-STATIC inline mp_obj_t convert_to_bool(bool b)
+static inline mp_obj_t convert_to_bool(bool b)
 {
     return b? mp_const_true: mp_const_false;
 }
 
-STATIC inline mp_obj_t convert_to_str(const char *str)
+static inline mp_obj_t convert_to_str(const char *str)
 {
     return str? mp_obj_new_str(str, strlen(str)): mp_const_none;
 }
 
-STATIC inline const char *convert_from_str(mp_obj_t str)
+static inline const char *convert_from_str(mp_obj_t str)
 {
     if (str == NULL || str == mp_const_none)
         return NULL;
@@ -1160,7 +1156,7 @@ def register_int_ptr_type(convertor, *types):
 
 // struct handling
 
-STATIC mp_lv_struct_t *mp_to_lv_struct(mp_obj_t mp_obj)
+static mp_lv_struct_t *mp_to_lv_struct(mp_obj_t mp_obj)
 {
     if (mp_obj == NULL || mp_obj == mp_const_none) return NULL;
     mp_obj_t native_obj = get_native_obj(mp_obj);
@@ -1171,7 +1167,7 @@ def register_int_ptr_type(convertor, *types):
     return mp_lv_struct;
 }
 
-STATIC inline size_t get_lv_struct_size(const mp_obj_type_t *type)
+static inline size_t get_lv_struct_size(const mp_obj_type_t *type)
 {
     mp_obj_dict_t *self = MP_OBJ_TO_PTR(MP_OBJ_TYPE_GET_SLOT(type, locals_dict));
     mp_map_elem_t *elem = mp_map_lookup(&self->map, MP_OBJ_NEW_QSTR(MP_QSTR___SIZE__), MP_MAP_LOOKUP);
@@ -1182,7 +1178,7 @@ def register_int_ptr_type(convertor, *types):
     }
 }
 
-STATIC mp_obj_t make_new_lv_struct(
+static mp_obj_t make_new_lv_struct(
     const mp_obj_type_t *type,
     size_t n_args,
     size_t n_kw,
@@ -1211,7 +1207,7 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(self);
 }
 
-STATIC mp_obj_t lv_struct_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in)
+static mp_obj_t lv_struct_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in)
 {
     mp_lv_struct_t *lhs = MP_OBJ_TO_PTR(lhs_in);
     mp_lv_struct_t *rhs = MP_OBJ_TO_PTR(rhs_in);
@@ -1226,7 +1222,7 @@ def register_int_ptr_type(convertor, *types):
     }
 }
 
-STATIC mp_obj_t lv_struct_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value)
+static mp_obj_t lv_struct_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value)
 {
     mp_lv_struct_t *self = mp_to_lv_struct(self_in);
 
@@ -1265,7 +1261,7 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(element_at_index);
 }
 
-GENMPY_UNUSED STATIC void *copy_buffer(const void *buffer, size_t size)
+GENMPY_UNUSED static void *copy_buffer(const void *buffer, size_t size)
 {
     void *new_buffer = m_malloc(size);
     memcpy(new_buffer, buffer, size);
@@ -1274,7 +1270,7 @@ def register_int_ptr_type(convertor, *types):
 
 // Reference an existing lv struct (or part of it)
 
-STATIC mp_obj_t lv_to_mp_struct(const mp_obj_type_t *type, void *lv_struct)
+static mp_obj_t lv_to_mp_struct(const mp_obj_type_t *type, void *lv_struct)
 {
     if (lv_struct == NULL) return mp_const_none;
     mp_lv_struct_t *self = m_new_obj(mp_lv_struct_t);
@@ -1285,7 +1281,7 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(self);
 }
 
-STATIC void call_parent_methods(mp_obj_t obj, qstr attr, mp_obj_t *dest)
+static void call_parent_methods(mp_obj_t obj, qstr attr, mp_obj_t *dest)
 {
     const mp_obj_type_t *type = mp_obj_get_type(obj);
     while (MP_OBJ_TYPE_HAS_SLOT(type, locals_dict)) {
@@ -1308,7 +1304,7 @@ def register_int_ptr_type(convertor, *types):
 
 // Convert dict to struct
 
-STATIC mp_obj_t dict_to_struct(mp_obj_t dict, const mp_obj_type_t *type)
+static mp_obj_t dict_to_struct(mp_obj_t dict, const mp_obj_type_t *type)
 {
     mp_obj_t mp_struct = make_new_lv_struct(type, 0, 0, NULL);
     mp_obj_t native_dict = cast(dict, &mp_type_dict);
@@ -1330,7 +1326,7 @@ def register_int_ptr_type(convertor, *types):
 
 // Convert mp object to ptr
 
-STATIC void* mp_to_ptr(mp_obj_t self_in)
+static void* mp_to_ptr(mp_obj_t self_in)
 {
     mp_buffer_info_t buffer_info;
     if (self_in == NULL || self_in == mp_const_none)
@@ -1374,14 +1370,14 @@ def register_int_ptr_type(convertor, *types):
 
 // Blob is a wrapper for void*
 
-STATIC void mp_blob_print(const mp_print_t *print,
+static void mp_blob_print(const mp_print_t *print,
     mp_obj_t self_in,
     mp_print_kind_t kind)
 {
     mp_printf(print, "Blob");
 }
 
-STATIC mp_int_t mp_blob_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
+static mp_int_t mp_blob_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
     (void)flags;
     mp_lv_struct_t *self = MP_OBJ_TO_PTR(self_in);
 
@@ -1391,13 +1387,13 @@ def register_int_ptr_type(convertor, *types):
     return 0;
 }
 
-STATIC const mp_obj_fun_builtin_var_t mp_lv_dereference_obj;
+static const mp_obj_fun_builtin_var_t mp_lv_dereference_obj;
 
 // Sometimes (but not always!) Blob represents a Micropython object.
 // In such cases it's safe to cast the Blob back to the Micropython object
 // cast argument is the underlying object type, and it's optional.
 
-STATIC mp_obj_t mp_blob_cast(size_t argc, const mp_obj_t *argv)
+static mp_obj_t mp_blob_cast(size_t argc, const mp_obj_t *argv)
 {
     mp_obj_t self = argv[0];
     void *ptr = mp_to_ptr(self);
@@ -1410,16 +1406,16 @@ def register_int_ptr_type(convertor, *types):
     return cast(MP_OBJ_FROM_PTR(ptr), type);
 }
 
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_blob_cast_obj, 1, 2, mp_blob_cast);
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_blob_cast_obj, 1, 2, mp_blob_cast);
 
-STATIC const mp_rom_map_elem_t mp_blob_locals_dict_table[] = {
+static const mp_rom_map_elem_t mp_blob_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR___dereference__), MP_ROM_PTR(&mp_lv_dereference_obj) },
     { MP_ROM_QSTR(MP_QSTR___cast__), MP_ROM_PTR(&mp_blob_cast_obj) },
 };
 
-STATIC MP_DEFINE_CONST_DICT(mp_blob_locals_dict, mp_blob_locals_dict_table);
+static MP_DEFINE_CONST_DICT(mp_blob_locals_dict, mp_blob_locals_dict_table);
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(
+static MP_DEFINE_CONST_OBJ_TYPE(
     mp_blob_type,
     MP_QSTR_Blob,
     MP_TYPE_FLAG_NONE,
@@ -1429,16 +1425,16 @@ def register_int_ptr_type(convertor, *types):
     buffer, mp_blob_get_buffer
 );
 
-STATIC const mp_lv_struct_t mp_lv_null_obj = { {&mp_blob_type}, NULL };
+static const mp_lv_struct_t mp_lv_null_obj = { {&mp_blob_type}, NULL };
 
-STATIC inline mp_obj_t ptr_to_mp(void *data)
+static inline mp_obj_t ptr_to_mp(void *data)
 {
     return lv_to_mp_struct(&mp_blob_type, data);
 }
 
 // Cast pointer to struct
 
-STATIC mp_obj_t mp_lv_cast(mp_obj_t type_obj, mp_obj_t ptr_obj)
+static mp_obj_t mp_lv_cast(mp_obj_t type_obj, mp_obj_t ptr_obj)
 {
     void *ptr = mp_to_ptr(ptr_obj);
     if (!ptr) return mp_const_none;
@@ -1452,21 +1448,21 @@ def register_int_ptr_type(convertor, *types):
 
 // Cast instance. Can be used in ISR when memory allocation is prohibited
 
-STATIC inline mp_obj_t mp_lv_cast_instance(mp_obj_t self_in, mp_obj_t ptr_obj)
+static inline mp_obj_t mp_lv_cast_instance(mp_obj_t self_in, mp_obj_t ptr_obj)
 {
     mp_lv_struct_t *self = MP_OBJ_TO_PTR(self_in);
     self->data = mp_to_ptr(ptr_obj);
     return self_in;
 }
 
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_lv_cast_obj, mp_lv_cast);
-STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(mp_lv_cast_class_method, MP_ROM_PTR(&mp_lv_cast_obj));
+static MP_DEFINE_CONST_FUN_OBJ_2(mp_lv_cast_obj, mp_lv_cast);
+static MP_DEFINE_CONST_CLASSMETHOD_OBJ(mp_lv_cast_class_method, MP_ROM_PTR(&mp_lv_cast_obj));
 
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_lv_cast_instance_obj, mp_lv_cast_instance);
+static MP_DEFINE_CONST_FUN_OBJ_2(mp_lv_cast_instance_obj, mp_lv_cast_instance);
 
 // Dereference a struct/blob. This allows access to the raw data the struct holds
 
-STATIC mp_obj_t mp_lv_dereference(size_t argc, const mp_obj_t *argv)
+static mp_obj_t mp_lv_dereference(size_t argc, const mp_obj_t *argv)
 {
     mp_obj_t self_in = argv[0];
     mp_obj_t size_in = argc > 1? argv[1]: mp_const_none;
@@ -1485,7 +1481,7 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(view);
 }
 
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_lv_dereference_obj, 1, 2, mp_lv_dereference);
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_lv_dereference_obj, 1, 2, mp_lv_dereference);
 
 // Callback function handling
 // Callback is either a callable object or a pointer. If it's a callable object, set user_data to the callback.
@@ -1493,7 +1489,7 @@ def register_int_ptr_type(convertor, *types):
 // In case of an lv_obj_t, user_data is mp_lv_obj_t which contains a member "callbacks" for that dict.
 // In case of a struct, user_data is a pointer to that dict directly
 
-STATIC mp_obj_t get_callback_dict_from_user_data(void *user_data)
+static mp_obj_t get_callback_dict_from_user_data(void *user_data)
 {
     if (user_data){
         mp_obj_t obj = MP_OBJ_FROM_PTR(user_data);
@@ -1511,7 +1507,7 @@ def register_int_ptr_type(convertor, *types):
 typedef void *(*mp_lv_get_user_data)(void *);
 typedef void (*mp_lv_set_user_data)(void *, void *);
 
-STATIC void *mp_lv_callback(mp_obj_t mp_callback, void *lv_callback, qstr callback_name,
+static void *mp_lv_callback(mp_obj_t mp_callback, void *lv_callback, qstr callback_name,
      void **user_data_ptr, void *containing_struct, mp_lv_get_user_data get_user_data, mp_lv_set_user_data set_user_data)
 {
     if (lv_callback && mp_obj_is_callable(mp_callback)) {
@@ -1543,7 +1539,7 @@ def register_int_ptr_type(convertor, *types):
 
 // Function pointers wrapper
 
-STATIC mp_obj_t mp_lv_funcptr(const mp_lv_obj_fun_builtin_var_t *mp_fun, void *lv_fun, void *lv_callback, qstr func_name, void *user_data)
+static mp_obj_t mp_lv_funcptr(const mp_lv_obj_fun_builtin_var_t *mp_fun, void *lv_fun, void *lv_callback, qstr func_name, void *user_data)
 {
     if (lv_fun == NULL)
         return mp_const_none;
@@ -1560,7 +1556,7 @@ def register_int_ptr_type(convertor, *types):
 
 // Missing implementation for 64bit integer conversion
 
-STATIC unsigned long long mp_obj_get_ull(mp_obj_t obj)
+static unsigned long long mp_obj_get_ull(mp_obj_t obj)
 {
     if (mp_obj_is_small_int(obj))
         return MP_OBJ_SMALL_INT_VALUE(obj);
@@ -1581,7 +1577,7 @@ def register_int_ptr_type(convertor, *types):
     bool is_signed;
 } mp_lv_array_t;
 
-STATIC void mp_lv_array_print(const mp_print_t *print,
+static void mp_lv_array_print(const mp_print_t *print,
     mp_obj_t self_in,
     mp_print_kind_t kind)
 {
@@ -1591,7 +1587,7 @@ def register_int_ptr_type(convertor, *types):
     mp_printf(print, "C Array (%sint%d[])", is_signed? "": "u", element_size*8);
 }
 
-STATIC mp_obj_t lv_array_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value)
+static mp_obj_t lv_array_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value)
 {
     mp_lv_array_t *self = MP_OBJ_TO_PTR(self_in);
 
@@ -1632,15 +1628,15 @@ def register_int_ptr_type(convertor, *types):
     return self_in;
 }
 
-STATIC const mp_rom_map_elem_t mp_base_struct_locals_dict_table[] = {
+static const mp_rom_map_elem_t mp_base_struct_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR___cast__), MP_ROM_PTR(&mp_lv_cast_class_method) },
     { MP_ROM_QSTR(MP_QSTR___cast_instance__), MP_ROM_PTR(&mp_lv_cast_instance_obj) },
     { MP_ROM_QSTR(MP_QSTR___dereference__), MP_ROM_PTR(&mp_lv_dereference_obj) },
 };
 
-STATIC MP_DEFINE_CONST_DICT(mp_base_struct_locals_dict, mp_base_struct_locals_dict_table);
+static MP_DEFINE_CONST_DICT(mp_base_struct_locals_dict, mp_base_struct_locals_dict_table);
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(
+static MP_DEFINE_CONST_OBJ_TYPE(
     mp_lv_base_struct_type,
     MP_QSTR_Struct,
     MP_TYPE_FLAG_NONE,
@@ -1651,7 +1647,7 @@ def register_int_ptr_type(convertor, *types):
 );
 
 // TODO: provide constructor
-STATIC MP_DEFINE_CONST_OBJ_TYPE(
+static MP_DEFINE_CONST_OBJ_TYPE(
     mp_lv_array_type,
     MP_QSTR_C_Array,
     MP_TYPE_FLAG_NONE,
@@ -1662,7 +1658,7 @@ def register_int_ptr_type(convertor, *types):
     locals_dict, &mp_base_struct_locals_dict
 );
 
-GENMPY_UNUSED STATIC mp_obj_t mp_array_from_ptr(void *lv_arr, size_t element_size, bool is_signed)
+GENMPY_UNUSED static mp_obj_t mp_array_from_ptr(void *lv_arr, size_t element_size, bool is_signed)
 {
     mp_lv_array_t *self = m_new_obj(mp_lv_array_t);
     *self = (mp_lv_array_t){
@@ -1673,7 +1669,7 @@ def register_int_ptr_type(convertor, *types):
     return MP_OBJ_FROM_PTR(self);
 }
 
-GENMPY_UNUSED STATIC void *mp_array_to_ptr(mp_obj_t *mp_arr, size_t element_size, GENMPY_UNUSED bool is_signed)
+GENMPY_UNUSED static void *mp_array_to_ptr(mp_obj_t *mp_arr, size_t element_size, GENMPY_UNUSED bool is_signed)
 {
     if (MP_OBJ_IS_STR_OR_BYTES(mp_arr) ||
         MP_OBJ_IS_TYPE(mp_arr, &mp_type_bytearray) ||
@@ -1706,11 +1702,11 @@ def register_int_ptr_type(convertor, *types):
 }
 
 #define MP_ARRAY_CONVERTOR(name, size, is_signed) \
-GENMPY_UNUSED STATIC mp_obj_t mp_array_from_ ## name(void *lv_arr)\
+GENMPY_UNUSED static mp_obj_t mp_array_from_ ## name(void *lv_arr)\
 {\
     return mp_array_from_ptr(lv_arr, size, is_signed);\
 }\
-GENMPY_UNUSED STATIC void *mp_array_to_ ## name(mp_obj_t mp_arr)\
+GENMPY_UNUSED static void *mp_array_to_ ## name(mp_obj_t mp_arr)\
 {\
     return mp_array_to_ptr(mp_arr, size, is_signed);\
 }
@@ -1963,7 +1959,7 @@ def try_generate_struct(struct_name, struct):
                 full_user_data = 'data->%s' % user_data
                 full_user_data_ptr = '&%s' % full_user_data
                 lv_callback = '%s_%s_callback' % (struct_name, func_name)
-                print('STATIC %s %s_%s_callback(%s);' % (get_type(arg_type.type, remove_quals = False), struct_name, func_name, gen.visit(arg_type.args)))
+                print('static %s %s_%s_callback(%s);' % (get_type(arg_type.type, remove_quals = False), struct_name, func_name, gen.visit(arg_type.args)))
             else:
                 full_user_data = 'NULL'
                 full_user_data_ptr = full_user_data
@@ -1999,9 +1995,9 @@ def try_generate_struct(struct_name, struct):
  * Struct {struct_name}
  */
 
-STATIC inline const mp_obj_type_t *get_mp_{sanitized_struct_name}_type();
+static inline const mp_obj_type_t *get_mp_{sanitized_struct_name}_type();
 
-STATIC inline void* mp_write_ptr_{sanitized_struct_name}(mp_obj_t self_in)
+static inline void* mp_write_ptr_{sanitized_struct_name}(mp_obj_t self_in)
 {{
     mp_lv_struct_t *self = MP_OBJ_TO_PTR(cast(self_in, get_mp_{sanitized_struct_name}_type()));
     return ({struct_tag}{struct_name}*)self->data;
@@ -2009,7 +2005,7 @@ def try_generate_struct(struct_name, struct):
 
 #define mp_write_{sanitized_struct_name}(struct_obj) *(({struct_tag}{struct_name}*)mp_write_ptr_{sanitized_struct_name}(struct_obj))
 
-STATIC inline mp_obj_t mp_read_ptr_{sanitized_struct_name}(void *field)
+static inline mp_obj_t mp_read_ptr_{sanitized_struct_name}(void *field)
 {{
     return lv_to_mp_struct(get_mp_{sanitized_struct_name}_type(), field);
 }}
@@ -2017,7 +2013,7 @@ def try_generate_struct(struct_name, struct):
 #define mp_read_{sanitized_struct_name}(field) mp_read_ptr_{sanitized_struct_name}(copy_buffer(&field, sizeof({struct_tag}{struct_name})))
 #define mp_read_byref_{sanitized_struct_name}(field) mp_read_ptr_{sanitized_struct_name}(&field)
 
-STATIC void mp_{sanitized_struct_name}_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest)
+static void mp_{sanitized_struct_name}_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest)
 {{
     mp_lv_struct_t *self = MP_OBJ_TO_PTR(self_in);
     GENMPY_UNUSED {struct_tag}{struct_name} *data = ({struct_tag}{struct_name}*)self->data;
@@ -2044,16 +2040,16 @@ def try_generate_struct(struct_name, struct):
     }}
 }}
 
-STATIC void mp_{sanitized_struct_name}_print(const mp_print_t *print,
+static void mp_{sanitized_struct_name}_print(const mp_print_t *print,
     mp_obj_t self_in,
     mp_print_kind_t kind)
 {{
     mp_printf(print, "struct {struct_name}");
 }}
 
-STATIC const mp_obj_dict_t mp_{sanitized_struct_name}_locals_dict;
+static const mp_obj_dict_t mp_{sanitized_struct_name}_locals_dict;
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(
+static MP_DEFINE_CONST_OBJ_TYPE(
     mp_{sanitized_struct_name}_type,
     MP_QSTR_{sanitized_struct_name},
     MP_TYPE_FLAG_NONE,
@@ -2067,7 +2063,7 @@ def try_generate_struct(struct_name, struct):
     parent, &mp_lv_base_struct_type
 );
 
-STATIC inline const mp_obj_type_t *get_mp_{sanitized_struct_name}_type()
+static inline const mp_obj_type_t *get_mp_{sanitized_struct_name}_type()
 {{
     return &mp_{sanitized_struct_name}_type;
 }}
@@ -2136,7 +2132,7 @@ def try_generate_array_type(type_ast):
  * Array convertors for {arr_name}
  */
 
-GENMPY_UNUSED STATIC {struct_tag}{type} *{arr_to_c_convertor_name}(mp_obj_t mp_arr)
+GENMPY_UNUSED static {struct_tag}{type} *{arr_to_c_convertor_name}(mp_obj_t mp_arr)
 {{
     mp_obj_t mp_len = mp_obj_len_maybe(mp_arr);
     if (mp_len == MP_OBJ_NULL) return mp_to_ptr(mp_arr);
@@ -2152,7 +2148,7 @@ def try_generate_array_type(type_ast):
     return ({struct_tag}{type} *)lv_arr;
 }}
 ''') + ('''
-GENMPY_UNUSED STATIC mp_obj_t {arr_to_mp_convertor_name}({qualified_type} *arr)
+GENMPY_UNUSED static mp_obj_t {arr_to_mp_convertor_name}({qualified_type} *arr)
 {{
     mp_obj_t obj_arr[{dim}];
     for (size_t i=0; i<{dim}; i++){{
@@ -2161,7 +2157,7 @@ def try_generate_array_type(type_ast):
     return mp_obj_new_list({dim}, obj_arr); // TODO: return custom iterable object!
 }}
 ''' if dim else '''
-GENMPY_UNUSED STATIC mp_obj_t {arr_to_mp_convertor_name}({qualified_type} *arr)
+GENMPY_UNUSED static mp_obj_t {arr_to_mp_convertor_name}({qualified_type} *arr)
 {{
     return {lv_to_mp_ptr_convertor}((void*)arr);
 }}
@@ -2251,7 +2247,7 @@ def try_generate_type(type_ast):
             try:
                 print("#define %s NULL\n" % func_ptr_name)
                 gen_mp_func(func, None)
-                print("STATIC inline mp_obj_t mp_lv_{f}(void *func){{ return mp_lv_funcptr(&mp_{f}_mpobj, func, NULL, MP_QSTR_, NULL); }}\n".format(
+                print("static inline mp_obj_t mp_lv_{f}(void *func){{ return mp_lv_funcptr(&mp_{f}_mpobj, func, NULL, MP_QSTR_, NULL); }}\n".format(
                     f=func_ptr_name))
                 lv_to_mp_funcptr[ptr_type] = func_ptr_name
                 # eprint("/* --> lv_to_mp_funcptr[%s] = %s */" % (ptr_type, func_ptr_name))
@@ -2414,7 +2410,7 @@ def gen_callback_func(func, func_name = None, user_data_argument = False):
  * {func_prototype}
  */
 
-GENMPY_UNUSED STATIC {return_type} {func_name}_callback({func_args})
+GENMPY_UNUSED static {return_type} {func_name}_callback({func_args})
 {{
     mp_obj_t mp_args[{num_args}];
     {build_args}
@@ -2525,7 +2521,7 @@ def build_mp_func_arg(arg, index, func, obj_name):
 
 def emit_func_obj(func_obj_name, func_name, param_count, func_ptr, is_static):
     print("""
-STATIC {builtin_macro}(mp_{func_obj_name}_mpobj, {param_count}, mp_{func_name}, {func_ptr});
+static {builtin_macro}(mp_{func_obj_name}_mpobj, {param_count}, mp_{func_name}, {func_ptr});
     """.format(
             func_obj_name = func_obj_name,
             func_name = func_name,
@@ -2602,7 +2598,7 @@ def gen_mp_func(func, obj_name):
  * {print_func}
  */
 
-STATIC mp_obj_t mp_{func}(size_t mp_n_args, const mp_obj_t *mp_args, void *lv_func_ptr)
+static mp_obj_t mp_{func}(size_t mp_n_args, const mp_obj_t *mp_args, void *lv_func_ptr)
 {{
     {build_args}
     {build_result}(({func_ptr})lv_func_ptr)({send_args});
@@ -2699,7 +2695,7 @@ def gen_obj(obj_name):
 
     # print([method.name for method in methods])
     ctor = """
-STATIC mp_obj_t {obj}_make_new(
+static mp_obj_t {obj}_make_new(
     const mp_obj_type_t *type,
     size_t n_args,
     size_t n_kw,
@@ -2718,13 +2714,13 @@ def gen_obj(obj_name):
             obj = obj_name))
 
     print("""
-STATIC const mp_rom_map_elem_t {obj}_locals_dict_table[] = {{
+static const mp_rom_map_elem_t {obj}_locals_dict_table[] = {{
     {locals_dict_entries}
 }};
 
-STATIC MP_DEFINE_CONST_DICT({obj}_locals_dict, {obj}_locals_dict_table);
+static MP_DEFINE_CONST_DICT({obj}_locals_dict, {obj}_locals_dict_table);
 
-STATIC void {obj}_print(const mp_print_t *print,
+static void {obj}_print(const mp_print_t *print,
     mp_obj_t self_in,
     mp_print_kind_t kind)
 {{
@@ -2733,7 +2729,7 @@ def gen_obj(obj_name):
 
 {ctor}
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(
+static MP_DEFINE_CONST_OBJ_TYPE(
     mp_lv_{obj}_type_base,
     MP_QSTR_{obj},
     MP_TYPE_FLAG_NONE,
@@ -2746,7 +2742,7 @@ def gen_obj(obj_name):
     locals_dict, &{obj}_locals_dict
 );
 
-GENMPY_UNUSED STATIC const mp_lv_obj_type_t mp_lv_{obj}_type = {{
+GENMPY_UNUSED static const mp_lv_obj_type_t mp_lv_{obj}_type = {{
 #ifdef LV_OBJ_T
     .lv_obj_class = {lv_class},
 #endif
@@ -2754,8 +2750,7 @@ def gen_obj(obj_name):
 }};
     """.format(
             module_name = module_name,
-            obj = sanitize(obj_name), base_obj = base_obj_name,
-            base_class = '&mp_%s_type' % base_obj_name if should_add_base_methods else 'NULL',
+            obj = sanitize(obj_name),
             locals_dict_entries = ",\n    ".join(gen_obj_methods(obj_name)),
             ctor = ctor.format(obj = obj_name, ctor_name = ctor_func.name) if has_ctor(obj_name) else '',
             make_new = 'make_new, %s_make_new,' % obj_name if is_obj else '',
@@ -2847,7 +2842,7 @@ def gen_global(global_name, global_type_ast):
  * {module_name} {global_name} global definitions
  */
 
-STATIC const mp_lv_struct_t mp_{global_name} = {{
+static const mp_lv_struct_t mp_{global_name} = {{
     {{ &mp_{struct_name}_type }},
     ({cast}*)&{global_name}
 }};
@@ -2855,7 +2850,6 @@ def gen_global(global_name, global_type_ast):
             module_name = module_name,
             global_name = global_name,
             struct_name = global_type,
-            sanitized_struct_name = sanitize(global_type),
             cast = gen.visit(global_type_ast)))
 
 generated_globals = []
@@ -2895,12 +2889,12 @@ def generate_struct_functions(struct_list):
         else:
             struct_size_attr = ''
         print('''
-STATIC const mp_rom_map_elem_t mp_{sanitized_struct_name}_locals_dict_table[] = {{
+static const mp_rom_map_elem_t mp_{sanitized_struct_name}_locals_dict_table[] = {{
     {struct_size}
     {functions}
 }};
 
-STATIC MP_DEFINE_CONST_DICT(mp_{sanitized_struct_name}_locals_dict, mp_{sanitized_struct_name}_locals_dict_table);
+static MP_DEFINE_CONST_DICT(mp_{sanitized_struct_name}_locals_dict, mp_{sanitized_struct_name}_locals_dict_table);
         '''.format(
             struct_size = struct_size_attr,
             sanitized_struct_name = sanitized_struct_name,
@@ -2986,7 +2980,7 @@ def generate_struct_functions(struct_list):
  * {module_name} module definitions
  */
 
-STATIC const mp_rom_map_elem_t {module_name}_globals_table[] = {{
+static const mp_rom_map_elem_t {module_name}_globals_table[] = {{
     {{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_{module_name}) }},
     {objects}
     {functions}
@@ -3019,7 +3013,7 @@ def generate_struct_functions(struct_list):
 
 
 print("""
-STATIC MP_DEFINE_CONST_DICT (
+static MP_DEFINE_CONST_DICT (
     mp_module_{module_name}_globals,
     {module_name}_globals_table
 );
@@ -3039,7 +3033,7 @@ def generate_struct_functions(struct_list):
 
 if len(obj_names) > 0:
     print('''
-STATIC const mp_lv_obj_type_t *mp_lv_obj_types[] = {{
+static const mp_lv_obj_type_t *mp_lv_obj_types[] = {{
     {obj_types},
     NULL
 }};

From 1fb42244b12cefa12dad1587d11dc46f570db999 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Fri, 18 Oct 2024 09:26:59 +1100
Subject: [PATCH 02/18] Support building as micropython USER_C_MODULE.

---
 bindings.cmake    |   5 ++-
 manifest.py       |  22 ++++++++++
 micropython.cmake |   8 ++++
 micropython.mk    | 103 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 137 insertions(+), 1 deletion(-)
 create mode 100644 manifest.py
 create mode 100644 micropython.cmake
 create mode 100644 micropython.mk

diff --git a/bindings.cmake b/bindings.cmake
index 5d196d381..24df4a75b 100644
--- a/bindings.cmake
+++ b/bindings.cmake
@@ -8,7 +8,10 @@ include(${LVGL_DIR}/CMakeLists.txt)
 # lvgl bindings target (the mpy module)
 add_library(usermod_lv_bindings INTERFACE)
 target_sources(usermod_lv_bindings INTERFACE ${LV_SRC})
-target_include_directories(usermod_lv_bindings INTERFACE ${LV_INCLUDE})
+target_include_directories(usermod_lv_bindings INTERFACE
+    ${CMAKE_CURRENT_LIST_DIR}/include
+    ${LV_INCLUDE}
+)
 
 target_link_libraries(usermod_lv_bindings INTERFACE lvgl_interface)
 
diff --git a/manifest.py b/manifest.py
new file mode 100644
index 000000000..5c62d0428
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,22 @@
+import sys
+
+include("$(MPY_DIR)/extmod/asyncio")
+
+freeze("lib", (
+    "display_driver.py",
+    "display_driver_utils.py",
+    "fs_driver.py",
+    "lv_colors.py",
+    "lv_utils.py",
+    "tpcal.py",
+    "utils.py",
+), opt=0)
+
+require("aiorepl")
+
+if sys.platform in ("linux"):
+    freeze("driver/linux", (
+        "evdev.py",
+        "lv_timer.py",
+    ), opt=1)
+
diff --git a/micropython.cmake b/micropython.cmake
new file mode 100644
index 000000000..3e4b322b8
--- /dev/null
+++ b/micropython.cmake
@@ -0,0 +1,8 @@
+# For detauils see: https://docs.micropython.org/en/latest/develop/cmodules.html
+
+set(LVGL_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR})
+
+include(${CMAKE_CURRENT_LIST_DIR}/env_support/cmake/micropython.cmake)
+
+# Link our INTERFACE library to the usermod target.
+target_link_libraries(usermod INTERFACE lvgl_interface)
diff --git a/micropython.mk b/micropython.mk
new file mode 100644
index 000000000..420411e79
--- /dev/null
+++ b/micropython.mk
@@ -0,0 +1,103 @@
+################################################################################
+# LVGL build rules for ports based on Makefile
+
+# For details see: https://docs.micropython.org/en/latest/develop/cmodules.html
+
+LVGL_BINDING_DIR := $(USERMOD_DIR)
+
+LVGL_DIR = $(LVGL_BINDING_DIR)/lvgl
+LVGL_GENERIC_DRV_DIR = $(LVGL_BINDING_DIR)/driver/generic
+
+ifeq ($(wildcard $(LVGL_DIR)/.),,)
+$(info lvgl submodule not init)
+else
+# This listing of all lvgl src files is used by make to track when the bindings need to be regenerated
+ALL_LVGL_SRC = $(shell find $(LVGL_DIR) -type f -name '*.h') $(LVGL_BINDING_DIR)/lv_conf.h
+endif
+
+LVGL_PP = $(BUILD)/lvgl/lvgl.pp.c
+LVGL_MPY = $(BUILD)/lvgl/lv_mpy.c
+LVGL_MPY_METADATA = $(BUILD)/lvgl/lv_mpy.json
+QSTR_GLOBAL_DEPENDENCIES += $(LVGL_MPY)
+INC += -I$(LVGL_BINDING_DIR) -I$(LVGL_BINDING_DIR)/include -I$(LVGL_DIR)/src
+CFLAGS_USERMOD += $(INC) $(LV_CFLAGS)
+
+ifneq ($(MICROPY_FLOAT_IMPL),double)
+# Tiny TTF library needs a number of math.h double functions
+CFLAGS_USERMOD += -DLV_USE_TINY_TTF=0
+endif
+
+# chain lvgl submodule check off the micropython submodules rule.
+LVGL_SUBMODULES = lvgl pycparser
+submodules: lvgl_submodule
+lvgl_submodule:
+	$(ECHO) "Updating submodules: $(LVGL_SUBMODULES)"
+	$(Q)cd $(LVGL_BINDING_DIR) && git submodule sync $(LVGL_SUBMODULES)
+	$(Q)cd $(LVGL_BINDING_DIR) && git submodule update --init $(LVGL_SUBMODULES)
+.PHONY: lvgl_submodule
+
+# Generate the main micropython bindings library
+$(LVGL_MPY): $(ALL_LVGL_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py
+	$(ECHO) "LVGL-GEN $@"
+	$(Q)mkdir -p $(dir $@)
+	$(Q)$(CPP) $(CFLAGS_USERMOD) -DPYCPARSER -x c -I $(LVGL_BINDING_DIR)/pycparser/utils/fake_libc_include $(INC) $(LVGL_DIR)/lvgl.h > $(LVGL_PP)
+	$(Q)$(PYTHON) $(LVGL_BINDING_DIR)/gen/gen_mpy.py -M lvgl -MP lv -MD $(LVGL_MPY_METADATA) -E $(LVGL_PP) $(LVGL_DIR)/lvgl.h > $@
+
+# Add all lvgl source files to the module.
+SRC_USERMOD += $(shell find $(LVGL_DIR)/src $(LVGL_DIR)/examples $(LVGL_GENERIC_DRV_DIR) -type f -name "*.c")
+SRC_USERMOD += $(LVGL_MPY)
+
+CFLAGS_USERMOD += -Wno-unused-function
+
+FROZEN_MANIFEST += $(LVGL_BINDING_DIR)/manifest.py
+
+################################################################################
+# Per-port Support 
+
+MICROPY_PORT = $(notdir $(CURDIR))
+
+ifeq ($(MICROPY_PORT),unix)
+# This section only included when building the micropython unix port
+UNAME_S := $(shell uname -s)
+
+ifneq ($(UNAME_S),Darwin)
+# On macos enable framebuffer support
+CFLAGS_USERMOD += -DMICROPY_FB=1
+endif
+
+ifneq (,$(shell which pkg-config))
+# Linux Support: if pkg-config is installed use it to 
+# check for and use optional libraries
+
+SDL_CFLAGS_USERMOD :=  $(shell pkg-config --silence-errors --cflags sdl2)
+SDL_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs   sdl2)
+ifneq ($(SDL_LDFLAGS_USERMOD),)
+CFLAGS_USERMOD += $(SDL_CFLAGS_USERMOD) -DMICROPY_SDL=1
+LDFLAGS_USERMOD += $(SDL_LDFLAGS_USERMOD)
+endif
+
+RLOTTIE_CFLAGS_USERMOD :=  $(shell pkg-config --silence-errors --cflags rlottie)
+RLOTTIE_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs   rlottie)
+ifneq ($(RLOTTIE_LDFLAGS_USERMOD),)
+CFLAGS_USERMOD += $(RLOTTIE_CFLAGS_USERMOD) -DMICROPY_RLOTTIE=1
+LDFLAGS_USERMOD += $(RLOTTIE_LDFLAGS_USERMOD)
+endif
+
+FREETYPE_CFLAGS_USERMOD :=  $(shell pkg-config --silence-errors --cflags freetype2)
+FREETYPE_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs   freetype2)
+ifneq ($(FREETYPE_LDFLAGS_USERMOD),)
+CFLAGS_USERMOD += $(FREETYPE_CFLAGS_USERMOD) -DMICROPY_FREETYPE=1
+LDFLAGS_USERMOD += $(FREETYPE_LDFLAGS_USERMOD)
+endif
+
+FFMPEG_LIBS := libavformat libavcodec libswscale libavutil
+FFMPEG_CFLAGS_USERMOD :=  $(shell pkg-config --silence-errors --cflags $(FFMPEG_LIBS))
+FFMPEG_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs   $(FFMPEG_LIBS))
+ifneq ($(FFMPEG_LDFLAGS_USERMOD),)
+CFLAGS_USERMOD += $(FFMPEG_CFLAGS_USERMOD) -DMICROPY_FFMPEG=1
+LDFLAGS_USERMOD += $(FFMPEG_LDFLAGS_USERMOD)
+endif
+
+endif  # Linux
+
+endif  # unix port

From a4ea25287ea69cfc826d2c1148540431d8f1c9dc Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Fri, 18 Oct 2024 08:30:57 +1100
Subject: [PATCH 03/18] lib: Allow using lv_utils in unix / macos port.

Fix to allow using lv_utils with asyncio when Timer not available.
---
 lib/lv_utils.py | 62 ++++++++++++++++++++++++++++++++-----------------
 1 file changed, 41 insertions(+), 21 deletions(-)

diff --git a/lib/lv_utils.py b/lib/lv_utils.py
index e920873ee..83d3fe95c 100644
--- a/lib/lv_utils.py
+++ b/lib/lv_utils.py
@@ -12,19 +12,19 @@
 #        event_loop = lv_utils.event_loop()
 #
 #
-# uasyncio example with SDL:
+# asyncio example with SDL:
 #
 #        SDL.init(auto_refresh=False)
 #        # Register SDL display driver.
 #        # Register SDL mouse driver
 #        event_loop = lv_utils.event_loop(asynchronous=True)
-#        uasyncio.Loop.run_forever()
+#        asyncio.Loop.run_forever()
 #
-# uasyncio example with ili9341:
+# asyncio example with ili9341:
 #
 #        event_loop = lv_utils.event_loop(asynchronous=True) # Optional!
 #        self.disp = ili9341(asynchronous=True)
-#        uasyncio.Loop.run_forever()
+#        asyncio.Loop.run_forever()
 #
 # MIT license; Copyright (c) 2021 Amir Gonnen
 #
@@ -32,7 +32,8 @@
 
 import lvgl as lv
 import micropython
-import usys
+import sys
+import time
 
 # Try standard machine.Timer, or custom timer from lv_timer, if available
 
@@ -42,26 +43,26 @@
     try:
         from lv_timer import Timer
     except:
-        raise RuntimeError("Missing machine.Timer implementation!")
+        Timer = False
 
 # Try to determine default timer id
 
 default_timer_id = 0
-if usys.platform == 'pyboard':
+if sys.platform == 'pyboard':
     # stm32 only supports SW timer -1
     default_timer_id = -1
     
-if usys.platform == 'rp2':
+if sys.platform == 'rp2':
     # rp2 only supports SW timer -1
     default_timer_id = -1
 
-# Try importing uasyncio, if available
+# Try importing asyncio, if available
 
 try:
-    import uasyncio
-    uasyncio_available = True
+    import asyncio
+    asyncio_available = True
 except:
-    uasyncio_available = False
+    asyncio_available = False
 
 ##############################################################################
 
@@ -84,24 +85,34 @@ def __init__(self, freq=25, timer_id=default_timer_id, max_scheduled=2, refresh_
 
         self.asynchronous = asynchronous
         if self.asynchronous:
-            if not uasyncio_available:
-                raise RuntimeError("Cannot run asynchronous event loop. uasyncio is not available!")
-            self.refresh_event = uasyncio.Event()
-            self.refresh_task = uasyncio.create_task(self.async_refresh())
-            self.timer_task = uasyncio.create_task(self.async_timer())
+            if not asyncio_available:
+                raise RuntimeError("Cannot run asynchronous event loop. asyncio is not available!")
+            self.refresh_event = asyncio.Event()
+            self.refresh_task = asyncio.create_task(self.async_refresh())
+            self.timer_task = asyncio.create_task(self.async_timer())
         else:
-            self.timer = Timer(timer_id)
+            if Timer:
+                self.timer = Timer(timer_id)
+                self.timer.init(
+                    mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb
+                )
             self.task_handler_ref = self.task_handler  # Allocation occurs here
             self.timer.init(mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb)
             self.max_scheduled = max_scheduled
             self.scheduled = 0
 
+    def init_async(self):
+        self.refresh_event = asyncio.Event()
+        self.refresh_task = asyncio.create_task(self.async_refresh())
+        self.timer_task = asyncio.create_task(self.async_timer())
+
     def deinit(self):
         if self.asynchronous:
             self.refresh_task.cancel()
             self.timer_task.cancel()
         else:
-            self.timer.deinit()
+            if Timer:
+                self.timer.deinit()
         event_loop._current_instance = None
 
     def disable(self):
@@ -128,6 +139,15 @@ def task_handler(self, _):
             if self.exception_sink:
                 self.exception_sink(e)
 
+    def tick(self):
+        self.timer_cb(None)
+
+    def run(self):
+        if not Timer:
+            while True:
+                self.tick()
+                time.sleep(0)
+
     def timer_cb(self, t):
         # Can be called in Interrupt context
         # Use task_handler_ref since passing self.task_handler would cause allocation.
@@ -153,11 +173,11 @@ async def async_refresh(self):
 
     async def async_timer(self):
         while True:
-            await uasyncio.sleep_ms(self.delay)
+            await asyncio.sleep_ms(self.delay)
             lv.tick_inc(self.delay)
             self.refresh_event.set()
             
 
     def default_exception_sink(self, e):
-        usys.print_exception(e)
+        sys.print_exception(e)
         event_loop.current_instance().deinit()

From 494a362b07796290cb0b3eae36eff27f1cbf07e3 Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Mon, 24 Jun 2024 02:21:18 +0100
Subject: [PATCH 04/18] fix(init/deinit): Properly init/deinit lvgl module.

Properly handle root pointers on lvgl init/deinit which fixes
init error after a soft reset (see #343).
---
 gen/gen_mpy.py  | 44 +++++++++++++++++++++++++++++++++++++++++---
 lib/lv_utils.py |  1 -
 lv_conf.h       |  2 ++
 lvgl            |  2 +-
 4 files changed, 44 insertions(+), 5 deletions(-)

diff --git a/gen/gen_mpy.py b/gen/gen_mpy.py
index af729e479..ac63021a8 100644
--- a/gen/gen_mpy.py
+++ b/gen/gen_mpy.py
@@ -1108,18 +1108,54 @@ def register_int_ptr_type(convertor, *types):
 // Register LVGL root pointers
 MP_REGISTER_ROOT_POINTER(void *mp_lv_roots);
 MP_REGISTER_ROOT_POINTER(void *mp_lv_user_data);
+MP_REGISTER_ROOT_POINTER(int mp_lv_roots_initialized);
+MP_REGISTER_ROOT_POINTER(int lvgl_mod_initialized);
 
 void *mp_lv_roots;
+void *mp_lv_user_data;
+int mp_lv_roots_initialized = 0;
+int lvgl_mod_initialized = 0;
 
 void mp_lv_init_gc()
 {
-    static bool mp_lv_roots_initialized = false;
-    if (!mp_lv_roots_initialized) {
+    if (!MP_STATE_VM(mp_lv_roots_initialized)) {
+        // mp_printf(&mp_plat_print, "[ INIT GC ]");
         mp_lv_roots = MP_STATE_VM(mp_lv_roots) = m_new0(lv_global_t, 1);
-        mp_lv_roots_initialized = true;
+        mp_lv_roots_initialized = MP_STATE_VM(mp_lv_roots_initialized) = 1;
     }
 }
 
+void mp_lv_deinit_gc()
+{
+
+    // mp_printf(&mp_plat_print, "[ DEINIT GC ]");
+    mp_lv_roots = MP_STATE_VM(mp_lv_roots) = NULL;
+    mp_lv_user_data = MP_STATE_VM(mp_lv_user_data) = NULL;
+    mp_lv_roots_initialized = MP_STATE_VM(mp_lv_roots_initialized) = 0;
+    lvgl_mod_initialized = MP_STATE_VM(lvgl_mod_initialized) = 0;
+
+}
+
+static mp_obj_t lvgl_mod___init__(void) {
+    if (!MP_STATE_VM(lvgl_mod_initialized)) {
+        // __init__ for builtins is called each time the module is imported,
+        //   so ensure that initialisation only happens once.
+        MP_STATE_VM(lvgl_mod_initialized) = true;
+        lv_init();
+    }
+    return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(lvgl_mod___init___obj, lvgl_mod___init__);
+
+
+static mp_obj_t lvgl_mod___del__(void) {
+    if (MP_STATE_VM(lvgl_mod_initialized)) {
+        lv_deinit();
+    }
+    return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(lvgl_mod___del___obj, lvgl_mod___del__);
+
 #else // LV_OBJ_T
 
 typedef struct mp_lv_obj_type_t {
@@ -2982,6 +3018,8 @@ def generate_struct_functions(struct_list):
 
 static const mp_rom_map_elem_t {module_name}_globals_table[] = {{
     {{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_{module_name}) }},
+    {{ MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&lvgl_mod___init___obj) }},
+    {{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&lvgl_mod___del___obj) }},
     {objects}
     {functions}
     {enums}
diff --git a/lib/lv_utils.py b/lib/lv_utils.py
index 83d3fe95c..259074e3a 100644
--- a/lib/lv_utils.py
+++ b/lib/lv_utils.py
@@ -180,4 +180,3 @@ async def async_timer(self):
 
     def default_exception_sink(self, e):
         sys.print_exception(e)
-        event_loop.current_instance().deinit()
diff --git a/lv_conf.h b/lv_conf.h
index c2d4e6c59..e624057bc 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -299,7 +299,9 @@
 /*Garbage Collector settings
  *Used if LVGL is bound to higher level language and the memory is managed by that language*/
 extern void mp_lv_init_gc();
+extern void mp_lv_deinit_gc();
 #define LV_GC_INIT() mp_lv_init_gc()
+#define LV_GC_DEINIT() mp_lv_deinit_gc()
 
 #define LV_ENABLE_GLOBAL_CUSTOM 1
 #if LV_ENABLE_GLOBAL_CUSTOM
diff --git a/lvgl b/lvgl
index 905de3d7c..00d07390e 160000
--- a/lvgl
+++ b/lvgl
@@ -1 +1 @@
-Subproject commit 905de3d7c81367608577e1e38d032831a7871c16
+Subproject commit 00d07390edbab7c13bdad83ed0b389616cb4d96b

From f762a9f38d8954e74bddab640220c8a29efbd553 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Wed, 23 Oct 2024 18:35:27 +1100
Subject: [PATCH 05/18] lv_utils: Default to soft-timer on all ports.

---
 lib/lv_utils.py | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/lib/lv_utils.py b/lib/lv_utils.py
index 259074e3a..bb65d0f36 100644
--- a/lib/lv_utils.py
+++ b/lib/lv_utils.py
@@ -45,19 +45,10 @@
     except:
         Timer = False
 
-# Try to determine default timer id
-
-default_timer_id = 0
-if sys.platform == 'pyboard':
-    # stm32 only supports SW timer -1
-    default_timer_id = -1
-    
-if sys.platform == 'rp2':
-    # rp2 only supports SW timer -1
-    default_timer_id = -1
+# By default use soft timer
+default_timer_id = -1
 
 # Try importing asyncio, if available
-
 try:
     import asyncio
     asyncio_available = True

From 3edd8f572a351aea2b84b78b393ae5a738935452 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Wed, 23 Oct 2024 18:36:12 +1100
Subject: [PATCH 06/18] gen_mpy: Add checks in mp_lv_deinit_gc().

---
 gen/gen_mpy.py | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/gen/gen_mpy.py b/gen/gen_mpy.py
index ac63021a8..536b1f5d4 100644
--- a/gen/gen_mpy.py
+++ b/gen/gen_mpy.py
@@ -1127,16 +1127,21 @@ def register_int_ptr_type(convertor, *types):
 
 void mp_lv_deinit_gc()
 {
-
-    // mp_printf(&mp_plat_print, "[ DEINIT GC ]");
-    mp_lv_roots = MP_STATE_VM(mp_lv_roots) = NULL;
-    mp_lv_user_data = MP_STATE_VM(mp_lv_user_data) = NULL;
-    mp_lv_roots_initialized = MP_STATE_VM(mp_lv_roots_initialized) = 0;
-    lvgl_mod_initialized = MP_STATE_VM(lvgl_mod_initialized) = 0;
-
+    if (MP_STATE_VM(mp_lv_roots_initialized)) {
+        // mp_printf(&mp_plat_print, "[ DEINIT GC ]");
+        mp_lv_roots = MP_STATE_VM(mp_lv_roots) = NULL;
+        mp_lv_user_data = MP_STATE_VM(mp_lv_user_data) = NULL;
+        mp_lv_roots_initialized = MP_STATE_VM(mp_lv_roots_initialized) = 0;
+        lvgl_mod_initialized = MP_STATE_VM(lvgl_mod_initialized) = 0;
+    }
 }
 
-static mp_obj_t lvgl_mod___init__(void) {
+#if !MICROPY_MODULE_BUILTIN_INIT
+#error MICROPY_MODULE_BUILTIN_INIT required to be set in build.
+#endif
+
+static mp_obj_t lvgl_mod___init__(void)
+{
     if (!MP_STATE_VM(lvgl_mod_initialized)) {
         // __init__ for builtins is called each time the module is imported,
         //   so ensure that initialisation only happens once.
@@ -1147,8 +1152,8 @@ def register_int_ptr_type(convertor, *types):
 }
 static MP_DEFINE_CONST_FUN_OBJ_0(lvgl_mod___init___obj, lvgl_mod___init__);
 
-
-static mp_obj_t lvgl_mod___del__(void) {
+static mp_obj_t lvgl_mod___del__(void)
+{
     if (MP_STATE_VM(lvgl_mod_initialized)) {
         lv_deinit();
     }

From 926d5ea542133c2a7b1827cc11e402e83afb48da Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Thu, 25 Apr 2024 22:43:38 +0100
Subject: [PATCH 07/18] esp32: Enable lv_binding_micropython as user C mod.

---
 micropython.cmake       |  47 ++++++++++--
 mkrules_usermod.cmake   | 157 ++++++++++++++++++++++++++++++++++++++++
 ports/esp32/manifest.py |   1 +
 3 files changed, 200 insertions(+), 5 deletions(-)
 create mode 100644 mkrules_usermod.cmake
 create mode 100644 ports/esp32/manifest.py

diff --git a/micropython.cmake b/micropython.cmake
index 3e4b322b8..c79ecff36 100644
--- a/micropython.cmake
+++ b/micropython.cmake
@@ -1,8 +1,45 @@
-# For detauils see: https://docs.micropython.org/en/latest/develop/cmodules.html
+# This file is to be given as "make USER_C_MODULES=..." when building Micropython port
 
-set(LVGL_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR})
+# Include LVGL component, ignore KCONFIG
 
-include(${CMAKE_CURRENT_LIST_DIR}/env_support/cmake/micropython.cmake)
+idf_build_set_property(LV_MICROPYTHON 1)
+idf_build_component(${CMAKE_CURRENT_LIST_DIR}/lvgl)
+idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND)
+separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS})
+idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND)
+idf_build_set_property(COMPILE_OPTIONS "-Wno-unused-function" APPEND)
+idf_build_set_property(SRCS "${LV_SRC}" APPEND)
+idf_build_set_property(INCLUDE_DIRS "${LV_INCLUDE}" APPEND)
 
-# Link our INTERFACE library to the usermod target.
-target_link_libraries(usermod INTERFACE lvgl_interface)
+include(${CMAKE_CURRENT_LIST_DIR}/mkrules_usermod.cmake)
+
+# Add lv_bindings rules
+
+all_lv_bindings()
+
+
+# # # make usermod (target declared by Micropython for all user compiled modules) link to bindings
+# # # this way the bindings (and transitively lvgl_interface) get proper compilation flags
+# target_link_libraries(usermod INTERFACE usermod_lvgl)
+
+file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/src/*.c)
+
+add_library(lvgl_interface INTERFACE)
+
+target_sources(lvgl_interface INTERFACE ${SOURCES})
+target_compile_options(lvgl_interface INTERFACE ${LV_CFLAGS})
+
+# # lvgl bindings target (the mpy module)
+
+add_library(usermod_lvgl INTERFACE)
+target_sources(usermod_lvgl INTERFACE ${LV_SRC})
+target_include_directories(usermod_lvgl INTERFACE ${LV_INCLUDE})
+
+
+file(WRITE ${LV_MP} "")
+
+target_link_libraries(usermod_lvgl INTERFACE lvgl_interface)
+
+# # # make usermod (target declared by Micropython for all user compiled modules) link to bindings
+# # # this way the bindings (and transitively lvgl_interface) get proper compilation flags
+target_link_libraries(usermod INTERFACE usermod_lvgl)
diff --git a/mkrules_usermod.cmake b/mkrules_usermod.cmake
new file mode 100644
index 000000000..9846dbe90
--- /dev/null
+++ b/mkrules_usermod.cmake
@@ -0,0 +1,157 @@
+
+
+find_package(Python3 REQUIRED COMPONENTS Interpreter)
+find_program(AWK awk mawk gawk)
+
+set(LV_BINDINGS_DIR ${CMAKE_CURRENT_LIST_DIR})
+
+# Common function for creating LV bindings
+
+function(lv_bindings)
+    set(_options)
+    set(_one_value_args OUTPUT)
+    set(_multi_value_args INPUT DEPENDS COMPILE_OPTIONS PP_OPTIONS GEN_OPTIONS FILTER)
+    cmake_parse_arguments(
+        PARSE_ARGV 0 LV
+        "${_options}"
+        "${_one_value_args}"
+        "${_multi_value_args}"
+    )
+
+    set(LV_PP ${LV_OUTPUT}.pp)
+    set(LV_MPY_METADATA ${LV_OUTPUT}.json)
+
+    add_custom_command(
+        OUTPUT 
+            ${LV_PP}
+        COMMAND
+        ${CMAKE_C_COMPILER} -E -DPYCPARSER ${LV_COMPILE_OPTIONS} ${LV_PP_OPTIONS} "${LV_CFLAGS}" -I ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include ${MICROPY_CPP_FLAGS} ${LV_INPUT} > ${LV_PP}
+        DEPENDS
+            ${LV_INPUT}
+            ${LV_DEPENDS}
+            ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include
+        IMPLICIT_DEPENDS
+            C ${LV_INPUT}
+        VERBATIM
+        COMMAND_EXPAND_LISTS
+    )
+
+    # if(ESP_PLATFORM)
+    #     target_compile_options(${COMPONENT_LIB} PRIVATE ${LV_COMPILE_OPTIONS})
+    # else()
+    # target_compile_options(usermod_lv_bindings INTERFACE ${LV_COMPILE_OPTIONS})
+    # endif()
+
+    if (DEFINED LV_FILTER)
+
+        set(LV_PP_FILTERED ${LV_PP}.filtered)
+        set(LV_AWK_CONDITION)
+        foreach(_f ${LV_FILTER})
+            string(APPEND LV_AWK_CONDITION "\$3!~\"${_f}\" && ")
+        endforeach()
+        string(APPEND LV_AWK_COMMAND "\$1==\"#\"{p=(${LV_AWK_CONDITION} 1)} p{print}")
+
+        # message("AWK COMMAND: ${LV_AWK_COMMAND}")
+
+        add_custom_command(
+            OUTPUT
+                ${LV_PP_FILTERED}
+            COMMAND
+                ${AWK} ${LV_AWK_COMMAND} ${LV_PP} > ${LV_PP_FILTERED}
+            DEPENDS
+                ${LV_PP}
+            VERBATIM
+            COMMAND_EXPAND_LISTS
+        )
+    else()
+        set(LV_PP_FILTERED ${LV_PP})
+    endif()
+
+    add_custom_command(
+        OUTPUT
+            ${LV_OUTPUT}
+        COMMAND
+            ${Python3_EXECUTABLE} ${LV_BINDINGS_DIR}/gen/gen_mpy.py ${LV_GEN_OPTIONS} -MD ${LV_MPY_METADATA} -E ${LV_PP_FILTERED} ${LV_INPUT} > ${LV_OUTPUT} || (rm -f ${LV_OUTPUT} && /bin/false)
+        DEPENDS
+            ${LV_BINDINGS_DIR}/gen/gen_mpy.py
+            ${LV_PP_FILTERED}
+        COMMAND_EXPAND_LISTS
+    )
+
+endfunction()
+
+# Definitions for specific bindings
+
+set(LVGL_DIR ${LV_BINDINGS_DIR}/lvgl)
+
+set(LV_MP ${CMAKE_BINARY_DIR}/lv_mp.c)
+# if(ESP_PLATFORM)
+#     set(LV_ESPIDF ${CMAKE_BINARY_DIR}/lv_espidf.c)
+# endif()
+
+# Function for creating all specific bindings
+
+function(all_lv_bindings)
+
+    # LVGL bindings
+
+    file(GLOB_RECURSE LVGL_HEADERS ${LVGL_DIR}/src/*.h ${LV_BINDINGS_DIR}/lv_conf.h)
+    lv_bindings(
+        OUTPUT
+            ${LV_MP}
+        INPUT
+            ${LVGL_DIR}/lvgl.h
+        DEPENDS
+            ${LVGL_HEADERS}
+        GEN_OPTIONS
+            -M lvgl -MP lv
+    )
+
+        
+    # ESPIDF bindings
+
+    # if(ESP_PLATFORM)
+    #     file(GLOB_RECURSE LV_ESPIDF_HEADERS ${IDF_PATH}/components/*.h ${LV_BINDINGS_DIR}/driver/esp32/*.h)
+    #     lv_bindings(
+    #         OUTPUT
+    #             ${LV_ESPIDF}
+    #         INPUT
+    #             ${LV_BINDINGS_DIR}/driver/esp32/espidf.h
+    #         DEPENDS
+    #             ${LV_ESPIDF_HEADERS}
+    #         GEN_OPTIONS
+    #              -M espidf
+    #         FILTER
+    #             i2s_ll.h
+    #             i2s_hal.h
+    #             esp_intr_alloc.h
+    #             soc/spi_periph.h
+    #             rom/ets_sys.h
+    #             soc/sens_struct.h
+    #             soc/rtc.h
+    #             driver/periph_ctrl.h
+    #     )
+    # endif(ESP_PLATFORM)
+
+endfunction()
+
+# Add includes to CMake component
+
+set(LV_INCLUDE
+    ${LV_BINDINGS_DIR}
+)
+
+# Add sources to CMake component
+
+set(LV_SRC
+    ${LV_MP}
+)
+
+# if(ESP_PLATFORM)
+#     LIST(APPEND LV_SRC
+#         ${LV_BINDINGS_DIR}/driver/esp32/espidf.c
+#         ${LV_BINDINGS_DIR}/driver/esp32/modrtch.c
+#         ${LV_BINDINGS_DIR}/driver/esp32/sh2lib.c
+#         ${LV_ESPIDF}
+#     )
+# endif(ESP_PLATFORM)
diff --git a/ports/esp32/manifest.py b/ports/esp32/manifest.py
new file mode 100644
index 000000000..58dc529a6
--- /dev/null
+++ b/ports/esp32/manifest.py
@@ -0,0 +1 @@
+module("lv_utils.py", base_path="../../lib")

From 4bc4878b0551cc7a781ced004ee2ee61e7a61bc6 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Thu, 17 Oct 2024 18:26:24 +1100
Subject: [PATCH 08/18] uasyncio_example1: Update for lvgl v9 and add aiorepl.

---
 examples/uasyncio_example1.py | 42 ++++++++++++++++++++++-------------
 1 file changed, 27 insertions(+), 15 deletions(-)

diff --git a/examples/uasyncio_example1.py b/examples/uasyncio_example1.py
index 06007a251..24fa0d522 100644
--- a/examples/uasyncio_example1.py
+++ b/examples/uasyncio_example1.py
@@ -15,19 +15,26 @@
 
 # Workaround for including frozen modules when running micropython with a script argument
 # https://github.com/micropython/micropython/issues/6419
-import usys as sys
+import sys
 sys.path.append('')
 
 # Imports
 
-from urandom import getrandbits, seed
-from utime import ticks_us
-from uasyncio import sleep, create_task, Loop, CancelledError
+from random import getrandbits, seed
+from time import ticks_us
+from asyncio import sleep, create_task, Loop, CancelledError
 import lv_utils
 import lvgl as lv
 
+try:
+    import aiorepl
+except ImportError:
+    aiorepl = None
+
 seed(ticks_us())
-lv.init()
+
+event_loop = lv_utils.event_loop(asynchronous=True)
+
 
 ##################################################################################################
 # Display initialization
@@ -44,8 +51,6 @@
     disp_drv = lv.sdl_window_create(HOR_RES, VER_RES)
     mouse = lv.sdl_mouse_create()
 
-    event_loop = lv_utils.event_loop(asynchronous=True)
-
 except AttributeError:
     pass
 
@@ -66,24 +71,28 @@
 # Stylized Message Box class
 ##################################################################################################
 
+popup_msg = "Pop"
+
 class MsgBox(lv.win):
 
     def drag_event_handler(self, e):
         self.move_foreground()
-        indev = lv.indev_get_act()
+        # obj = lv.event_get_target(e)
+        indev = lv.indev_active()
         indev.get_vect(self.vect)
-        x = self.get_x() + self.vect.x
-        y = self.get_y() + self.vect.y
+        x = self.get_x_aligned() + self.vect.x
+        y = self.get_y_aligned() + self.vect.y
         self.set_pos(x, y)
 
     def __init__(self, parent):
+        global popup_msg
         super().__init__(parent)
         self.vect = lv.point_t()
 
         self.set_size(100,80)
-        self.add_title("Pop")
+        self.add_title(popup_msg)
         msg_box_close_btn = self.add_button(lv.SYMBOL.CLOSE, 20)
-        msg_box_close_btn.add_event(lambda e: self.close_msg_box(), lv.EVENT.RELEASED, None)
+        msg_box_close_btn.add_event_cb(lambda e: self.close_msg_box(), lv.EVENT.RELEASED, None)
 
         header = self.get_header()
         header.set_style_bg_color(lv.color_hex3(0xFEE), lv.PART.MAIN)
@@ -104,7 +113,7 @@ def __init__(self, parent):
         self.label = lv.label(content)
 
         for element in [content, header]:
-            element.add_event(self.drag_event_handler, lv.EVENT.PRESSING, None)
+            element.add_event_cb(self.drag_event_handler, lv.EVENT.PRESSING, None)
 
         self.opened = True
 
@@ -121,7 +130,7 @@ def close_msg_box(self):
             self.anim.set_custom_exec_cb(lambda obj, val:
                     self.set_style_opa(val, lv.PART.MAIN))
             self.anim.set_path_cb(lv.anim_t.path_ease_in)
-            self.anim.set_ready_cb(lambda a: self.del_async())
+            self.anim.set_completed_cb(lambda a: self.delete_async())
             lv.anim_t.start(self.anim)
             self.opened = False
 
@@ -161,7 +170,7 @@ async def btn_event_task(obj=None, event=-1):
 scr = lv.screen_active()
 btn = lv.button(scr)
 btn.align(lv.ALIGN.TOP_MID, 0, 10)
-btn.add_event(lambda e: create_task(btn_event_task()), lv.EVENT.CLICKED, None)
+btn.add_event_cb(lambda e: create_task(btn_event_task()), lv.EVENT.CLICKED, None)
 label = lv.label(btn)
 label.set_text('Click Me Again!')
 
@@ -169,5 +178,8 @@ async def btn_event_task(obj=None, event=-1):
 # Start event loop
 ##################################################################################################
 
+if aiorepl:
+    create_task(aiorepl.task())
+
 Loop.run_forever()
 

From beb9f8eac1c8b155888e599228293c2deb6cc0d2 Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Sun, 12 May 2024 16:15:04 +0100
Subject: [PATCH 09/18] feat(tests): Add tests for MicroPython test suite.

---
 tests/README.md                   |  46 ++++++
 tests/api/basic.py                |  60 ++++++++
 tests/api/basic.py.exp            |  34 ++++
 tests/api/basic_indev.py          |  77 ++++++++++
 tests/api/basic_indev.py.exp      |  47 ++++++
 tests/api/basic_slider.py         |  77 ++++++++++
 tests/api/basic_slider.py.exp     |  37 +++++
 tests/display/basic.py            |  60 ++++++++
 tests/display/basic.py.exp        |   5 +
 tests/display/basic_indev.py      |  77 ++++++++++
 tests/display/basic_indev.py.exp  |  18 +++
 tests/display/basic_slider.py     |  77 ++++++++++
 tests/display/basic_slider.py.exp |   8 +
 tests/display/display_mode.py     |   1 +
 tests/imageconvert.py             |  22 +++
 tests/indev/basic.py              |  69 +++++++++
 tests/indev/basic.py.exp          |   5 +
 tests/indev/basic_slider.py       |  76 +++++++++
 tests/indev/basic_slider.py.exp   |   5 +
 tests/indev/display_mode.py       |   2 +
 tests/testdisplay.py              | 247 ++++++++++++++++++++++++++++++
 tests/testrunner.py               |  65 ++++++++
 22 files changed, 1115 insertions(+)
 create mode 100644 tests/README.md
 create mode 100644 tests/api/basic.py
 create mode 100644 tests/api/basic.py.exp
 create mode 100644 tests/api/basic_indev.py
 create mode 100644 tests/api/basic_indev.py.exp
 create mode 100644 tests/api/basic_slider.py
 create mode 100644 tests/api/basic_slider.py.exp
 create mode 100644 tests/display/basic.py
 create mode 100644 tests/display/basic.py.exp
 create mode 100644 tests/display/basic_indev.py
 create mode 100644 tests/display/basic_indev.py.exp
 create mode 100644 tests/display/basic_slider.py
 create mode 100644 tests/display/basic_slider.py.exp
 create mode 100644 tests/display/display_mode.py
 create mode 100755 tests/imageconvert.py
 create mode 100644 tests/indev/basic.py
 create mode 100644 tests/indev/basic.py.exp
 create mode 100644 tests/indev/basic_slider.py
 create mode 100644 tests/indev/basic_slider.py.exp
 create mode 100644 tests/indev/display_mode.py
 create mode 100644 tests/testdisplay.py
 create mode 100644 tests/testrunner.py

diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 000000000..46383acb5
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,46 @@
+
+### Tests:
+
+- `api/`: These tests are to test MicroPython-LVGL bindings. They can be
+automated/included in CI. 
+To run from `micropython/tests`:
+```
+ ./run-tests.py ../../user_modules/lv_binding_micropython/tests/api/basic*.py -r .
+```
+
+- `display/`: These are to test the `api` + display driver. Intended for HIL
+(Hardware in the loop) testing. Display only, no touch interface, touch is
+automated and simulated in software.
+To run from `micropython/tests`:
+```
+./run-tests.py ../../user_modules/lv_binding_micropython/tests/display/basic*.py -r .
+```
+e.g. in unix port a display will appear to provide visual feedback.
+
+
+- `indev/`: These are to test the `display` + indev (touch) driver. Intended for
+interactive HIL testing, e.g. they expect user input to complete the test. 
+
+To run from `micropython/tests`:
+```
+./run-tests.py ../../user_modules/lv_binding_micropython/tests/indev/basic*.py -r .
+```
+e.g. in unix port a display will appear to allow user input.
+
+All tests are intended/expected to be run both in desktop (unix port) and in devices with the same result.
+
+For devices `testrunner.py`, `testdisplay.py` and `display_mode.py` need to be
+uploaded. Also for display/indev testing a `hwdisplay.py` with a display driver
+called `display` is expected. This `display` driver is expected to have at least a 
+```py
+
+    def blit(self, x1, y1, w, h, buff):
+```
+method or handle the lv display setup by itself (e.g setting buffers, `flush_cb`, etc)
+
+For interactive indev tests, it is required to have a 
+```py
+
+    def read_cb(self, indev, data):
+```
+method too, or handle indev creation by itself.
diff --git a/tests/api/basic.py b/tests/api/basic.py
new file mode 100644
index 000000000..703249234
--- /dev/null
+++ b/tests/api/basic.py
@@ -0,0 +1,60 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        _btn = lv.button(scr)
+        _btn.set_size(lv.pct(25), lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), 0)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name):
+        print(f"{name} PRESSED")
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name: button_cb(event, button_name),
+            lv.EVENT.CLICKED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    print("EVENT TEST:")
+    for _btn, name in _all_btns:
+        _btn.send_event(lv.EVENT.CLICKED, None)
+        await asyncio.sleep_ms(200)
+
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode)
+testrunner.devicereset()
diff --git a/tests/api/basic.py.exp b/tests/api/basic.py.exp
new file mode 100644
index 000000000..c0de908b1
--- /dev/null
+++ b/tests/api/basic.py.exp
@@ -0,0 +1,34 @@
+
+FRAME: 0 (0, 0, 240, 32, 23040)
+d5c5d09cff879bb12cb926dc44bf10161cded58d2057806e7cbde536540b1421
+
+FRAME: 1 (0, 32, 240, 32, 23040)
+f281e1fce42dc013342ad8a4573d74874238d995e6dff46dc29a1d68b780f920
+
+FRAME: 2 (0, 64, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 3 (0, 96, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 4 (0, 128, 240, 32, 23040)
+424125778438a53da017c2dca09964ec2cec5ad4e2689038dd0e830125112fd8
+
+FRAME: 5 (0, 160, 240, 32, 23040)
+9abb7f9219bb7ccc8784119c784b1bf41c451f9957989fd2a9fc12a15606b1d0
+
+FRAME: 6 (0, 192, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 7 (0, 224, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 8 (0, 256, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 9 (0, 288, 240, 32, 23040)
+f546d8ae7340f5fb71e30358ef0d6f33a4f2d72946d9b312444b07fa9d659396
+EVENT TEST:
+RED PRESSED
+GREEN PRESSED
+BLUE PRESSED
diff --git a/tests/api/basic_indev.py b/tests/api/basic_indev.py
new file mode 100644
index 000000000..38ac0ed88
--- /dev/null
+++ b/tests/api/basic_indev.py
@@ -0,0 +1,77 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        _btn = lv.button(scr)
+        _btn.set_size(lv.pct(25), lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), 0)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name):
+        print(f"{name} PRESSED")
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name: button_cb(event, button_name),
+            lv.EVENT.CLICKED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    print("EVENT TEST:")
+    for _btn, name in _all_btns:
+        _btn.send_event(lv.EVENT.CLICKED, None)
+        await asyncio.sleep_ms(200)
+
+    # simulate touch events
+    if display:
+        print("INDEV TEST:")
+        await display.touch(100, 100)
+
+        await asyncio.sleep_ms(500)
+
+        print("INDEV + BUTTONS TEST:")
+        # display.debug_indev(press=False, release=False)
+        display.debug_display(False)
+        for _btn, name in _all_btns:
+            pos = _btn.get_x(), _btn.get_y()
+            await display.touch(*pos)
+            await asyncio.sleep_ms(1000)
+
+        await asyncio.sleep_ms(500)
+
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode)
+testrunner.devicereset()
diff --git a/tests/api/basic_indev.py.exp b/tests/api/basic_indev.py.exp
new file mode 100644
index 000000000..d8cbf7b85
--- /dev/null
+++ b/tests/api/basic_indev.py.exp
@@ -0,0 +1,47 @@
+
+FRAME: 0 (0, 0, 240, 32, 23040)
+d5c5d09cff879bb12cb926dc44bf10161cded58d2057806e7cbde536540b1421
+
+FRAME: 1 (0, 32, 240, 32, 23040)
+f281e1fce42dc013342ad8a4573d74874238d995e6dff46dc29a1d68b780f920
+
+FRAME: 2 (0, 64, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 3 (0, 96, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 4 (0, 128, 240, 32, 23040)
+424125778438a53da017c2dca09964ec2cec5ad4e2689038dd0e830125112fd8
+
+FRAME: 5 (0, 160, 240, 32, 23040)
+9abb7f9219bb7ccc8784119c784b1bf41c451f9957989fd2a9fc12a15606b1d0
+
+FRAME: 6 (0, 192, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 7 (0, 224, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 8 (0, 256, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 9 (0, 288, 240, 32, 23040)
+f546d8ae7340f5fb71e30358ef0d6f33a4f2d72946d9b312444b07fa9d659396
+EVENT TEST:
+RED PRESSED
+GREEN PRESSED
+BLUE PRESSED
+INDEV TEST:
+[PRESSED]: (100,100)
+[RELEASED]: (100,100)
+INDEV + BUTTONS TEST:
+[PRESSED]: (90,0)
+RED PRESSED
+[RELEASED]: (90,0)
+[PRESSED]: (90,288)
+GREEN PRESSED
+[RELEASED]: (90,288)
+[PRESSED]: (90,144)
+BLUE PRESSED
+[RELEASED]: (90,144)
diff --git a/tests/api/basic_slider.py b/tests/api/basic_slider.py
new file mode 100644
index 000000000..f226c5368
--- /dev/null
+++ b/tests/api/basic_slider.py
@@ -0,0 +1,77 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner  # noqa
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        scr.set_style_pad_all(10, 0)
+        _btn = lv.slider(scr)
+        _btn.set_width(lv.pct(75))
+        _btn.set_height(lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.set_style_text_color(lv.color_white(), 0)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.INDICATOR)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.MAIN)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.KNOB)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name, slider):
+        if slider.get_value() == 100:
+            print(f"{name} VALUE: {slider.get_value()}")
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name, slider=btn: button_cb(
+                event, button_name, slider
+            ),
+            lv.EVENT.VALUE_CHANGED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    # simulate touch events
+    if display:
+        print("INDEV + SLIDER TEST:")
+        display.debug_indev(press=False)
+        display.debug_display(False)
+        for _btn, name in _all_btns:
+            pos = _btn.get_x(), _btn.get_y()
+            pos2 = _btn.get_x2(), _btn.get_y2()
+            x1, y1 = pos
+            x2, y2 = pos2
+            y_mid = y2 - ((y2 - y1) // 2)
+            await display.swipe(x1 + 5, y_mid, x2 + (y2 - y1), y_mid, ms=500)
+            await asyncio.sleep_ms(100)
+
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode)
+testrunner.devicereset()
diff --git a/tests/api/basic_slider.py.exp b/tests/api/basic_slider.py.exp
new file mode 100644
index 000000000..c6359c382
--- /dev/null
+++ b/tests/api/basic_slider.py.exp
@@ -0,0 +1,37 @@
+
+FRAME: 0 (0, 0, 240, 32, 23040)
+6e5737038637abc5ea724930a5113dd9193a3e708b13c3be75d2e5164ccfc57a
+
+FRAME: 1 (0, 32, 240, 32, 23040)
+5c139099d39acc6aa2081459fb397f698035937288fd088b60325f11d8d839a9
+
+FRAME: 2 (0, 64, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 3 (0, 96, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 4 (0, 128, 240, 32, 23040)
+85b39d4c5e001bd4aa82e2c0efd390d2f1c20517426ebc07a63a64c8315dcdc4
+
+FRAME: 5 (0, 160, 240, 32, 23040)
+798e52f4dd160d6e592d0d4d075ef4779eeffed0a4f2339aa9b524e2fe008eae
+
+FRAME: 6 (0, 192, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 7 (0, 224, 240, 32, 23040)
+46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694
+
+FRAME: 8 (0, 256, 240, 32, 23040)
+a6b9cdacc2013dbb3ce95198217d24ce42333d0178da7fddd15ff353ab012891
+
+FRAME: 9 (0, 288, 240, 32, 23040)
+08943f10a0eeb2c8a3b8ec437fd2d0725b5a764d29375282553eaafd51ff704a
+INDEV + SLIDER TEST:
+RED VALUE: 100
+[RELEASED]: (218,15)
+GREEN VALUE: 100
+[RELEASED]: (218,285)
+BLUE VALUE: 100
+[RELEASED]: (218,150)
diff --git a/tests/display/basic.py b/tests/display/basic.py
new file mode 100644
index 000000000..703249234
--- /dev/null
+++ b/tests/display/basic.py
@@ -0,0 +1,60 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        _btn = lv.button(scr)
+        _btn.set_size(lv.pct(25), lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), 0)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name):
+        print(f"{name} PRESSED")
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name: button_cb(event, button_name),
+            lv.EVENT.CLICKED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    print("EVENT TEST:")
+    for _btn, name in _all_btns:
+        _btn.send_event(lv.EVENT.CLICKED, None)
+        await asyncio.sleep_ms(200)
+
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode)
+testrunner.devicereset()
diff --git a/tests/display/basic.py.exp b/tests/display/basic.py.exp
new file mode 100644
index 000000000..bdb7f65e8
--- /dev/null
+++ b/tests/display/basic.py.exp
@@ -0,0 +1,5 @@
+DISPLAY_MODE: INTERACTIVE
+EVENT TEST:
+RED PRESSED
+GREEN PRESSED
+BLUE PRESSED
diff --git a/tests/display/basic_indev.py b/tests/display/basic_indev.py
new file mode 100644
index 000000000..6dde2bc63
--- /dev/null
+++ b/tests/display/basic_indev.py
@@ -0,0 +1,77 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        _btn = lv.button(scr)
+        _btn.set_size(lv.pct(25), lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), 0)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name):
+        print(f"{name} PRESSED")
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name: button_cb(event, button_name),
+            lv.EVENT.CLICKED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    print("EVENT TEST:")
+    for _btn, name in _all_btns:
+        _btn.send_event(lv.EVENT.CLICKED, None)
+        await asyncio.sleep_ms(200)
+
+    # simulate touch events
+    if display:
+        print("INDEV TEST:")
+        await display.touch(100, 100)
+
+        await asyncio.sleep_ms(500)
+
+        print("INDEV + BUTTONS TEST:")
+        # display.debug_indev(press=True, release=True)
+        display.debug_display(False)
+        for _btn, name in _all_btns:
+            pos = _btn.get_x(), _btn.get_y()
+            await display.touch(*pos)
+            await asyncio.sleep_ms(1000)
+
+        await asyncio.sleep_ms(500)
+
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode)
+testrunner.devicereset()
diff --git a/tests/display/basic_indev.py.exp b/tests/display/basic_indev.py.exp
new file mode 100644
index 000000000..25f341f5e
--- /dev/null
+++ b/tests/display/basic_indev.py.exp
@@ -0,0 +1,18 @@
+DISPLAY_MODE: INTERACTIVE
+EVENT TEST:
+RED PRESSED
+GREEN PRESSED
+BLUE PRESSED
+INDEV TEST:
+[PRESSED]: (100,100)
+[RELEASED]: (100,100)
+INDEV + BUTTONS TEST:
+[PRESSED]: (90,0)
+RED PRESSED
+[RELEASED]: (90,0)
+[PRESSED]: (90,288)
+GREEN PRESSED
+[RELEASED]: (90,288)
+[PRESSED]: (90,144)
+BLUE PRESSED
+[RELEASED]: (90,144)
diff --git a/tests/display/basic_slider.py b/tests/display/basic_slider.py
new file mode 100644
index 000000000..f226c5368
--- /dev/null
+++ b/tests/display/basic_slider.py
@@ -0,0 +1,77 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner  # noqa
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        scr.set_style_pad_all(10, 0)
+        _btn = lv.slider(scr)
+        _btn.set_width(lv.pct(75))
+        _btn.set_height(lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.set_style_text_color(lv.color_white(), 0)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.INDICATOR)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.MAIN)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.KNOB)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name, slider):
+        if slider.get_value() == 100:
+            print(f"{name} VALUE: {slider.get_value()}")
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name, slider=btn: button_cb(
+                event, button_name, slider
+            ),
+            lv.EVENT.VALUE_CHANGED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    # simulate touch events
+    if display:
+        print("INDEV + SLIDER TEST:")
+        display.debug_indev(press=False)
+        display.debug_display(False)
+        for _btn, name in _all_btns:
+            pos = _btn.get_x(), _btn.get_y()
+            pos2 = _btn.get_x2(), _btn.get_y2()
+            x1, y1 = pos
+            x2, y2 = pos2
+            y_mid = y2 - ((y2 - y1) // 2)
+            await display.swipe(x1 + 5, y_mid, x2 + (y2 - y1), y_mid, ms=500)
+            await asyncio.sleep_ms(100)
+
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode)
+testrunner.devicereset()
diff --git a/tests/display/basic_slider.py.exp b/tests/display/basic_slider.py.exp
new file mode 100644
index 000000000..6dc1b3cc5
--- /dev/null
+++ b/tests/display/basic_slider.py.exp
@@ -0,0 +1,8 @@
+DISPLAY_MODE: INTERACTIVE
+INDEV + SLIDER TEST:
+RED VALUE: 100
+[RELEASED]: (218,15)
+GREEN VALUE: 100
+[RELEASED]: (218,285)
+BLUE VALUE: 100
+[RELEASED]: (218,150)
diff --git a/tests/display/display_mode.py b/tests/display/display_mode.py
new file mode 100644
index 000000000..c6af2743b
--- /dev/null
+++ b/tests/display/display_mode.py
@@ -0,0 +1 @@
+MODE = "interactive"
diff --git a/tests/imageconvert.py b/tests/imageconvert.py
new file mode 100755
index 000000000..4157cfe3d
--- /dev/null
+++ b/tests/imageconvert.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+
+from PIL import Image
+import sys
+import argparse
+
+parser = argparse.ArgumentParser(description="RGB888 to PNG converter")
+parser.add_argument("file", help="file/s name", nargs="*")
+
+args = parser.parse_args()
+
+for file in args.file:
+    with open(file, "rb") as ff:
+        w, h, cs = ff.readline().split(b":")
+        width, height = int(w.decode()), int(h.decode())
+        frame = ff.read()
+        assert len(frame) == width * height * int(cs)
+
+    image = Image.new("RGB", (width, height))
+    image.frombytes(frame)
+    image.save(file.replace(".bin", ".png"), "PNG")
+    image.close()
diff --git a/tests/indev/basic.py b/tests/indev/basic.py
new file mode 100644
index 000000000..1a96ad735
--- /dev/null
+++ b/tests/indev/basic.py
@@ -0,0 +1,69 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+class TestButton(lv.button):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.event_press = asyncio.Event()
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        _btn = TestButton(scr)
+        _btn.set_size(lv.pct(25), lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), 0)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name, button):
+        print(f"{name} PRESSED")
+        button.event_press.set()
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name, button=btn: button_cb(
+                event, button_name, button
+            ),
+            lv.EVENT.CLICKED,
+            None,
+        )
+
+    await asyncio.sleep_ms(500)  # await so the frame can be rendered
+    print("PRESS EVENT TEST:")
+    for _btn, name in _all_btns:
+        await _btn.event_press.wait()
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+    from display_mode import POINTER as _pointer
+except Exception:
+    _mode = "test"
+    _pointer = "sim"
+
+testrunner.run(demo, __file__, mode=_mode, pointer=_pointer)
+testrunner.devicereset()
diff --git a/tests/indev/basic.py.exp b/tests/indev/basic.py.exp
new file mode 100644
index 000000000..83badc00a
--- /dev/null
+++ b/tests/indev/basic.py.exp
@@ -0,0 +1,5 @@
+DISPLAY_MODE: INTERACTIVE
+PRESS EVENT TEST:
+RED PRESSED
+BLUE PRESSED
+GREEN PRESSED
diff --git a/tests/indev/basic_slider.py b/tests/indev/basic_slider.py
new file mode 100644
index 000000000..481ddb453
--- /dev/null
+++ b/tests/indev/basic_slider.py
@@ -0,0 +1,76 @@
+import lvgl as lv
+import sys
+import asyncio
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+import testrunner  # noqa
+
+# This is a basic test to test buttons, labels,
+# RGB colors, layout aligment and events.
+
+
+class TestSlider(lv.slider):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.event_completed = asyncio.Event()
+
+
+async def demo(scr, display=None):
+    def get_button(scr, text, align, color):
+        scr.set_style_pad_all(10, 0)
+        _btn = TestSlider(scr)
+        _btn.set_width(lv.pct(75))
+        _btn.set_height(lv.pct(10))
+        _lab = lv.label(_btn)
+        _lab.set_text(text)
+        _lab.set_style_text_color(lv.color_white(), 0)
+        _lab.center()
+        _btn.set_style_align(align, 0)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.INDICATOR)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.MAIN)
+        _btn.set_style_bg_color(lv.color_make(*color), lv.PART.KNOB)
+        return _btn, text
+
+    buttons = [
+        ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)),
+        ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)),
+        ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)),
+    ]
+
+    def button_cb(event, name, slider):
+        if slider.get_value() == 100:
+            if not slider.event_completed.is_set():
+                print(f"{name} VALUE: {slider.get_value()}")
+                slider.event_completed.set()
+
+    _all_btns = [get_button(scr, *btn) for btn in buttons]
+
+    for btn, name in _all_btns:
+        btn.add_event_cb(
+            lambda event, button_name=name, slider=btn: button_cb(
+                event, button_name, slider
+            ),
+            lv.EVENT.VALUE_CHANGED,
+            None,
+        )
+
+    print("INDEV + SLIDER TEST:")
+    display.debug_indev(press=False)
+    display.debug_display(False)
+    for _btn, name in _all_btns:
+        await _btn.event_completed.wait()
+    return _all_btns
+
+
+__file__ = globals().get("__file__", "test")
+
+try:
+    from display_mode import MODE as _mode
+    from display_mode import POINTER as _pointer
+except Exception:
+    _mode = None
+
+testrunner.run(demo, __file__, mode=_mode, pointer=_pointer)
+testrunner.devicereset()
diff --git a/tests/indev/basic_slider.py.exp b/tests/indev/basic_slider.py.exp
new file mode 100644
index 000000000..fe869d3ed
--- /dev/null
+++ b/tests/indev/basic_slider.py.exp
@@ -0,0 +1,5 @@
+DISPLAY_MODE: INTERACTIVE
+INDEV + SLIDER TEST:
+RED VALUE: 100
+BLUE VALUE: 100
+GREEN VALUE: 100
diff --git a/tests/indev/display_mode.py b/tests/indev/display_mode.py
new file mode 100644
index 000000000..54dfb8c14
--- /dev/null
+++ b/tests/indev/display_mode.py
@@ -0,0 +1,2 @@
+MODE = "interactive"
+POINTER = False
diff --git a/tests/testdisplay.py b/tests/testdisplay.py
new file mode 100644
index 000000000..1f7e71433
--- /dev/null
+++ b/tests/testdisplay.py
@@ -0,0 +1,247 @@
+# adapted from https://github.com/bdbarnett/mpdisplay
+# utils/lv_mpdisplay.py
+
+import lvgl as lv
+import hashlib
+from binascii import hexlify
+import lv_utils
+import sys
+import asyncio
+
+
+class TestDisplayDriver:
+    def __init__(
+        self,
+        display_drv,
+        frame_buffer1,
+        frame_buffer2,
+        color_format=lv.COLOR_FORMAT.RGB565,
+        mode="test",
+        pointer="sim",
+        fps=25,
+    ):
+        self.display_drv = display_drv
+        self._color_size = lv.color_format_get_size(color_format)
+        self._frame_buffer1 = frame_buffer1
+        self._frame_buffer2 = frame_buffer2
+        self._x = 0
+        self._y = 0
+        self._dstate = None
+        self._press_event = False
+        self._release_event = False
+        self._debug_press = True
+        self._debug_release = True
+        self.mode = mode
+
+        render_mode = lv.DISPLAY_RENDER_MODE.PARTIAL
+
+        if not lv_utils.event_loop.is_running():
+            self.event_loop = lv_utils.event_loop(freq=fps, asynchronous=True)
+
+        else:
+            self.event_loop = lv_utils.event_loop.current_instance()
+        self.event_loop.scheduled = 0
+
+        if not lv.is_initialized():
+            lv.init()
+
+        if mode == "test" or (
+            mode == "interactive" and not isinstance(display_drv, DummyDisplay)
+        ):
+            if hasattr(display_drv, "blit"):
+                self.lv_display = lv.display_create(
+                    self.display_drv.width, self.display_drv.height
+                )
+                self.lv_display.set_color_format(color_format)
+                self.lv_display.set_flush_cb(self._flush_cb)
+                self.lv_display.set_buffers(
+                    self._frame_buffer1,
+                    self._frame_buffer2,
+                    len(self._frame_buffer1),
+                    render_mode,
+                )
+            self.indev_test = lv.indev_create()
+            self.indev_test.set_display(lv.display_get_default())
+            self.indev_test.set_group(lv.group_get_default())
+            # TODO: test other types of indev
+            self.indev_test.set_type(lv.INDEV_TYPE.POINTER)
+            if hasattr(display_drv, "read_cb") and pointer != "sim":
+                self.indev_test.set_read_cb(display_drv.read_cb)
+
+            else:
+                self.indev_test.set_read_cb(self._read_cb)
+
+        else:  # interactive + DummyDisplay -> SDL
+            self.group = lv.group_create()
+            self.group.set_default()
+            self.lv_display_int = lv.sdl_window_create(
+                display_drv.width, display_drv.height
+            )
+            lv.sdl_window_set_title(self.lv_display_int, "MicroPython-LVGL")
+            self.mouse = lv.sdl_mouse_create()
+            self.keyboard = lv.sdl_keyboard_create()
+            self.keyboard.set_group(self.group)
+            if pointer == "sim":
+                self.indev = lv.indev_create()
+                self.indev.set_display(self.lv_display_int)
+                self.indev.set_group(self.group)
+                self.indev.set_type(lv.INDEV_TYPE.POINTER)
+                # NOTE: only one indev pointer allowed, use the keyboard
+                # for interactive control
+                self.indev.set_read_cb(self._read_cb)
+
+    def set_test_name(self, name):
+        self.display_drv.test_name = name
+
+    def debug_indev(self, press=None, release=None):
+        self._debug_press = press if press is not None else self._debug_press
+        self._debug_release = release if release is not None else self._debug_release
+
+    def debug_display(self, debug=True):
+        if hasattr(self.display_drv, "debug"):
+            self.display_drv.debug = debug
+
+    async def touch(self, x, y, ms=100):
+        self._x = x
+        self._y = y
+        self._dstate = lv.INDEV_STATE.PRESSED
+        self._press_event = True
+        await asyncio.sleep_ms(ms)
+
+        self._dstate = lv.INDEV_STATE.RELEASED
+        self._release_event = True
+        await asyncio.sleep_ms(25)
+
+    async def swipe(self, x1, y1, x2, y2, steps=5, ms=100):
+        self._dstate = lv.INDEV_STATE.PRESSED
+        if y1 == y2:  # HORIZONTAL
+            self._y = y1
+            self._x = x1
+            if x2 < x1:
+                steps = -steps  # RIGHT-LEFT
+            for xi in range(x1, x2, steps):
+                self._x = xi
+                self._press_event = True
+                await asyncio.sleep_ms(ms // steps)
+        elif x1 == x2:
+            self._y = y1
+            self._x = x1
+            if y2 < y1:
+                steps = -steps  # BOTTOM-UP
+            for yi in range(y1, y2, steps):
+                self._y = yi
+                self._press_event = True
+                await asyncio.sleep_ms(ms // steps)
+
+        self._dstate = lv.INDEV_STATE.RELEASED
+        self._release_event = True
+        await asyncio.sleep_ms(25)
+
+    def _flush_cb(self, disp_drv, area, color_p):
+        width = area.x2 - area.x1 + 1
+        height = area.y2 - area.y1 + 1
+
+        self.display_drv.blit(
+            area.x1,
+            area.y1,
+            width,
+            height,
+            color_p.__dereference__(width * height * self._color_size),
+        )
+        self.lv_display.flush_ready()
+
+    def _read_cb(self, indev, data):
+        if self._press_event:
+            self._press_event = False
+            if self._debug_press:
+                print(f"[PRESSED]: ({self._x},{self._y})")
+            data.point = lv.point_t({"x": self._x, "y": self._y})
+            data.state = self._dstate
+        elif self._release_event:
+            self._release_event = False
+            if self._debug_release:
+                print(f"[RELEASED]: ({self._x},{self._y})")
+            data.state = self._dstate
+
+
+class DummyDisplay:
+    def __init__(self, width=240, height=320, color_format=lv.COLOR_FORMAT.RGB565):
+        self.width = width
+        self.height = height
+        self.color_depth = lv.color_format_get_bpp(color_format)
+        self.color_size = lv.color_format_get_size(color_format)
+        self.n = 0
+        self.test_name = "testframe"
+        self._header_set = False
+        self._save_frame = sys.platform in ["darwin", "linux"]
+        self._debug = True
+
+    @property
+    def debug(self):
+        return self._debug
+
+    @debug.setter
+    def debug(self, x):
+        self._debug = x
+        self._save_frame = x
+
+    def save_frame(self, data):
+        if not self._header_set:
+            self._header_set = True
+            with open(f"{self.test_name}.bin", "wb") as fr:
+                fr.write(f"{self.width}:{self.height}:{self.color_size}\n".encode())
+
+        with open(f"{self.test_name}.bin", "ab") as fr:
+            fr.write(data)
+
+    def _shasum_frame(self, data):
+        _hash = hashlib.sha256()
+        _hash.update(data)
+        _result = _hash.digest()
+        result = hexlify(_result).decode()
+        return result
+
+    def blit(self, x1, y1, w, h, buff):
+        if self.debug:
+            print(f"\nFRAME: {self.n} {(x1, y1, w, h, len(buff[:]))}")
+            print(self._shasum_frame(bytes(buff[:])))
+        if self._save_frame:
+            self.save_frame(buff[:])
+        self.n += 1
+
+
+tdisp = DummyDisplay(color_format=lv.COLOR_FORMAT.RGB888)
+
+
+alloc_buffer = lambda buffersize: memoryview(bytearray(buffer_size))
+
+factor = 10  ### Must be 1 if using an RGBBus
+double_buf = True  ### Must be False if using an RGBBus
+
+buffer_size = tdisp.width * tdisp.height * (tdisp.color_depth // 8) // factor
+
+fbuf1 = alloc_buffer(buffer_size)
+fbuf2 = alloc_buffer(buffer_size) if double_buf else None
+
+
+def get_display(
+    width=240,
+    height=320,
+    disp=tdisp,
+    color_format=lv.COLOR_FORMAT.RGB888,
+    mode="test",
+    pointer="sim",
+):
+    if mode == "test":
+        disp = tdisp
+    elif mode == "interactive":
+        print("DISPLAY_MODE: INTERACTIVE")
+        try:
+            from hwdisplay import display as disp
+        except Exception as e:
+            if sys.platform not in ["darwin", "linux"]:
+                sys.print_exception(e)
+    if hasattr(disp, "width") and hasattr(disp, "height"):
+        disp.width = width
+        disp.height = height
+    return TestDisplayDriver(disp, fbuf1, fbuf2, color_format, mode, pointer)
diff --git a/tests/testrunner.py b/tests/testrunner.py
new file mode 100644
index 000000000..71176c09f
--- /dev/null
+++ b/tests/testrunner.py
@@ -0,0 +1,65 @@
+import sys
+import os
+
+sys.path.append("..")
+sys.path.append(os.getcwd())
+
+from testdisplay import get_display  # noqa
+
+_int = sys.argv.pop() if sys.platform in ["darwin", "linux"] else ""
+_mode = "test"
+if _int in ("-id", "-d"):
+    _mode = "interactive"
+
+
+async def run_test(func, display=None):
+    import lvgl as lv  # noqa
+
+    lv.init()
+
+    scr = lv.obj()
+    scr.set_style_bg_color(lv.color_black(), 0)
+    lv.screen_load(scr)
+
+    resp = await func(scr, display)
+    return scr, resp
+
+
+def run(func, filename, w=240, h=320, mode=None, **kwargs):
+    import asyncio
+
+    # import micropython  # noqa
+
+    # micropython.mem_info()
+
+    async def _run(func, w, h, mode=None, **kwargs):
+        display = get_display(
+            w,
+            h,
+            mode=mode if mode is not None else _mode,
+            pointer=kwargs.get("pointer", "sim"),
+        )
+
+        if display.mode == "test":
+            display.set_test_name(f"{filename.replace('.py', '')}.{func.__name__}")
+            await run_test(func, display)
+            await asyncio.sleep_ms(100)
+        elif display.mode == "interactive":
+            await run_test(func, display)
+            if _int == "-id":
+                while True:
+                    try:
+                        await asyncio.sleep_ms(1000)
+                    except KeyboardInterrupt:
+                        sys.exit()
+                    except Exception as e:
+                        sys.print_exception(e)
+
+    asyncio.run(_run(func, w, h, mode, **kwargs))
+
+
+def devicereset():
+    import lvgl as lv
+
+    if lv.is_initialized():
+        lv.deinit()

From 251e9de1bf563e0c1ff4ac2959b6285d7bfb4ad6 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Mon, 11 Nov 2024 16:51:23 +1100
Subject: [PATCH 10/18] cmake: Register automatic git submodules handling.

---
 micropython.cmake | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/micropython.cmake b/micropython.cmake
index c79ecff36..df3363e0e 100644
--- a/micropython.cmake
+++ b/micropython.cmake
@@ -1,5 +1,11 @@
 # This file is to be given as "make USER_C_MODULES=..." when building Micropython port
 
+if(ECHO_SUBMODULES)
+    string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " ${CMAKE_CURRENT_LIST_DIR}/lvgl ${CMAKE_CURRENT_LIST_DIR}/pycparser)
+elseif(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/lvgl/README.md)
+    message(FATAL_ERROR " lvgl not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'")
+endif()
+
 # Include LVGL component, ignore KCONFIG
 
 idf_build_set_property(LV_MICROPYTHON 1)

From dd0959f01cf11b6348bf17979a2d3e7e8da17d22 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Thu, 14 Nov 2024 21:55:54 +1100
Subject: [PATCH 11/18] Update LVGL

---
 lvgl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lvgl b/lvgl
index 00d07390e..339e7d1d9 160000
--- a/lvgl
+++ b/lvgl
@@ -1 +1 @@
-Subproject commit 00d07390edbab7c13bdad83ed0b389616cb4d96b
+Subproject commit 339e7d1d964c40ef9d2de066172a14bdf0076412

From 86cda8d1f1d26cf90bacf759697348db45e953e9 Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Wed, 13 Nov 2024 14:55:33 +1100
Subject: [PATCH 12/18] lv_conf: Merge in changes / defaults from lvgl update.

---
 lv_conf.h | 63 +++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 20 deletions(-)

diff --git a/lv_conf.h b/lv_conf.h
index e624057bc..52f55ca66 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -88,6 +88,7 @@
  * - LV_OS_CMSIS_RTOS2
  * - LV_OS_RTTHREAD
  * - LV_OS_WINDOWS
+ * - LV_OS_MQX
  * - LV_OS_CUSTOM */
 #define LV_USE_OS   LV_OS_NONE
 
@@ -113,6 +114,11 @@
 /*The target buffer size for simple layer chunks.*/
 #define LV_DRAW_LAYER_SIMPLE_BUF_SIZE    (24 * 1024)   /*[bytes]*/
 
+/* The stack size of the drawing thread.
+ * NOTE: If FreeType or ThorVG is enabled, it is recommended to set it to 32KB or more.
+ */
+#define LV_DRAW_THREAD_STACK_SIZE    (8 * 1024)   /*[bytes]*/
+
 #define LV_USE_DRAW_SW 1
 #if LV_USE_DRAW_SW == 1
     /* Set the number of draw unit.
@@ -151,7 +157,9 @@
 #endif
 
 /* Use NXP's VG-Lite GPU on iMX RTxxx platforms. */
+#ifndef LV_USE_DRAW_VGLITE
 #define LV_USE_DRAW_VGLITE 0
+#endif
 
 #if LV_USE_DRAW_VGLITE
     /* Enable blit quality degradation workaround recommended for screen's dimension > 352 pixels. */
@@ -167,7 +175,9 @@
 #endif
 
 /* Use NXP's PXP on iMX RTxxx platforms. */
+#ifndef LV_USE_DRAW_PXP
 #define LV_USE_DRAW_PXP 0
+#endif
 
 #if LV_USE_DRAW_PXP
     /* Enable PXP asserts. */
@@ -201,7 +211,7 @@
 /* VG-Lite linear gradient image maximum cache number.
  * NOTE: The memory usage of a single gradient image is 4K bytes.
  */
-#define LV_VG_LITE_LINEAER_GRAD_CACHE_CNT 32
+#define LV_VG_LITE_LINEAR_GRAD_CACHE_CNT 32
 
 /* VG-Lite radial gradient image maximum cache size.
  * NOTE: The memory usage of a single gradient image is radial grad radius * 4 bytes.
@@ -240,6 +250,11 @@
     *0: User need to register a callback with `lv_log_register_print_cb()`*/
     #define LV_LOG_PRINTF 0
 
+    /*Set callback to print the logs.
+     *E.g `my_print`. The prototype should be `void my_print(lv_log_level_t level, const char * buf)`
+     *Can be overwritten by `lv_log_register_print_cb`*/
+    //#define LV_LOG_PRINT_CB
+
     /*1: Enable print timestamp;
      *0: Disable print timestamp*/
     #define LV_LOG_USE_TIMESTAMP 1
@@ -248,6 +263,7 @@
      *0: Do not print file and line number of the log*/
     #define LV_LOG_USE_FILE_LINE 1
 
+
     /*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
     #define LV_LOG_TRACE_MEM        1
     #define LV_LOG_TRACE_TIMER      1
@@ -424,7 +440,7 @@ extern void mp_lv_deinit_gc();
 #define LV_FONT_MONTSERRAT_14 1
 #define LV_FONT_MONTSERRAT_16 1
 #define LV_FONT_MONTSERRAT_18 0
-#define LV_FONT_MONTSERRAT_20 0
+#define LV_FONT_MONTSERRAT_20 1
 #define LV_FONT_MONTSERRAT_22 0
 #define LV_FONT_MONTSERRAT_24 1
 #define LV_FONT_MONTSERRAT_26 0
@@ -432,7 +448,7 @@ extern void mp_lv_deinit_gc();
 #define LV_FONT_MONTSERRAT_30 0
 #define LV_FONT_MONTSERRAT_32 0
 #define LV_FONT_MONTSERRAT_34 0
-#define LV_FONT_MONTSERRAT_36 0
+#define LV_FONT_MONTSERRAT_36 1
 #define LV_FONT_MONTSERRAT_38 0
 #define LV_FONT_MONTSERRAT_40 0
 #define LV_FONT_MONTSERRAT_42 0
@@ -443,6 +459,7 @@ extern void mp_lv_deinit_gc();
 /*Demonstrate special features*/
 #define LV_FONT_MONTSERRAT_28_COMPRESSED 0  /*bpp = 3*/
 #define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0  /*Hebrew, Arabic, Persian letters and all their forms*/
+#define LV_FONT_SIMSUN_14_CJK            0  /*1000 most common CJK radicals*/
 #define LV_FONT_SIMSUN_16_CJK            0  /*1000 most common CJK radicals*/
 
 /*Pixel perfect monospace fonts*/
@@ -541,6 +558,7 @@ extern void mp_lv_deinit_gc();
     #define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March",  "April", "May",  "June", "July", "August", "September", "October", "November", "December"}
     #define LV_USE_CALENDAR_HEADER_ARROW 1
     #define LV_USE_CALENDAR_HEADER_DROPDOWN 1
+    #define LV_USE_CALENDAR_CHINESE 0
 #endif  /*LV_USE_CALENDAR*/
 
 #define LV_USE_CANVAS     1
@@ -646,7 +664,7 @@ extern void mp_lv_deinit_gc();
 /*File system interfaces for common APIs */
 
 /*API for fopen, fread, etc*/
-#define LV_USE_FS_STDIO 0
+#define LV_USE_FS_STDIO 1
 #if LV_USE_FS_STDIO
     #define LV_FS_STDIO_LETTER 'A'      /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
     #define LV_FS_STDIO_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
@@ -654,9 +672,9 @@ extern void mp_lv_deinit_gc();
 #endif
 
 /*API for open, read, etc*/
-#define LV_USE_FS_POSIX 0
+#define LV_USE_FS_POSIX 1
 #if LV_USE_FS_POSIX
-    #define LV_FS_POSIX_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
+    #define LV_FS_POSIX_LETTER 'P'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
     #define LV_FS_POSIX_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
     #define LV_FS_POSIX_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
 #endif
@@ -677,7 +695,7 @@ extern void mp_lv_deinit_gc();
 #endif
 
 /*API for memory-mapped file access. */
-#define LV_USE_FS_MEMFS 1
+#define LV_USE_FS_MEMFS 0
 #if LV_USE_FS_MEMFS
     #define LV_FS_MEMFS_LETTER 'M'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
 #endif
@@ -694,6 +712,14 @@ extern void mp_lv_deinit_gc();
     #define LV_FS_ARDUINO_ESP_LITTLEFS_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
 #endif
 
+/*API for Arduino Sd. */
+#define LV_USE_FS_ARDUINO_SD 0
+#if LV_USE_FS_ARDUINO_SD
+    #define LV_FS_ARDUINO_SD_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
+    #define LV_FS_ARDUINO_SD_CS_PIN 0     /*Set the pin connected to the chip select line of the SD card */
+    #define LV_FS_ARDUINO_SD_FREQUENCY 40000000     /*Set the frequency used by the chip of the SD CARD */
+#endif
+
 /*LODEPNG decoder library*/
 #define LV_USE_LODEPNG 1
 
@@ -718,6 +744,7 @@ extern void mp_lv_deinit_gc();
 #define LV_GIF_CACHE_DECODE_DATA 0
 #endif
 
+
 /*Decode bin images to RAM*/
 #define LV_BIN_DECODER_RAM_LOAD 0
 
@@ -725,10 +752,10 @@ extern void mp_lv_deinit_gc();
 #define LV_USE_RLE 0
 
 /*QR code library*/
-#define LV_USE_QRCODE 1
+#define LV_USE_QRCODE 0
 
 /*Barcode code library*/
-#define LV_USE_BARCODE 1
+#define LV_USE_BARCODE 0
 
 /*FreeType library*/
 #ifdef MICROPY_FREETYPE
@@ -771,18 +798,14 @@ extern void mp_lv_deinit_gc();
 #define LV_USE_THORVG_EXTERNAL 0
 
 /*Use lvgl built-in LZ4 lib*/
-#define LV_USE_LZ4_INTERNAL  0
+#define LV_USE_LZ4_INTERNAL  1
 
 /*Use external LZ4 library*/
 #define LV_USE_LZ4_EXTERNAL  0
 
 /*FFmpeg library for image decoding and playing videos
  *Supports all major image formats so do not enable other image decoder with it*/
-#ifdef MICROPY_FFMPEG
-    #define LV_USE_FFMPEG MICROPY_FFMPEG
-#else
     #define LV_USE_FFMPEG 0
-#endif
 #if LV_USE_FFMPEG
     /*Dump input information to stderr*/
     #define LV_FFMPEG_DUMP_FORMAT 0
@@ -793,7 +816,7 @@ extern void mp_lv_deinit_gc();
  *==================*/
 
 /*1: Enable API to take snapshot for object*/
-#define LV_USE_SNAPSHOT 1
+#define LV_USE_SNAPSHOT 0
 
 /*1: Enable system monitor component*/
 #define LV_USE_SYSMON   0
@@ -908,7 +931,7 @@ extern void mp_lv_deinit_gc();
     #define LV_USE_SDL 0
 #endif
 #if LV_USE_SDL
-    #define LV_SDL_INCLUDE_PATH     <SDL2/SDL.h>
+    #define LV_SDL_INCLUDE_PATH     <SDL.h>
     #define LV_SDL_RENDER_MODE      LV_DISPLAY_RENDER_MODE_DIRECT   /*LV_DISPLAY_RENDER_MODE_DIRECT is recommended for best performance*/
     #define LV_SDL_BUF_COUNT        1    /*1 or 2*/
     #define LV_SDL_FULLSCREEN       0    /*1: Make the window full screen by default*/
@@ -983,10 +1006,10 @@ extern void mp_lv_deinit_gc();
 #endif
 
 /*Drivers for LCD devices connected via SPI/parallel port*/
-#define LV_USE_ST7735		0
-#define LV_USE_ST7789		0
-#define LV_USE_ST7796		0
-#define LV_USE_ILI9341		0
+#define LV_USE_ST7735        0
+#define LV_USE_ST7789        0
+#define LV_USE_ST7796        0
+#define LV_USE_ILI9341       0
 
 #define LV_USE_GENERIC_MIPI (LV_USE_ST7735 | LV_USE_ST7789 | LV_USE_ST7796 | LV_USE_ILI9341)
 

From 17343480e29f8531eae9492d5f423ecb26c36fea Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Tue, 12 Nov 2024 14:45:21 +0000
Subject: [PATCH 13/18] fix(gen_mpy.py): update lv_to_mp float conversion.

Upate for lvgl 9.2.x see diff in lvgl @ 84b28ff
---
 gen/gen_mpy.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gen/gen_mpy.py b/gen/gen_mpy.py
index 536b1f5d4..5d930665c 100644
--- a/gen/gen_mpy.py
+++ b/gen/gen_mpy.py
@@ -620,7 +620,7 @@ class MissingConversionException(ValueError):
     'long int'                  : 'mp_obj_new_int',
     'long long'                 : 'mp_obj_new_int_from_ll',
     'long long int'             : 'mp_obj_new_int_from_ll',
-    'float'                     : 'mp_obj_new_float',
+    'float'                     : 'mp_obj_new_float_from_f',
 }
 
 lv_mp_type = {

From 860a3d7e8304e1ee0a2b30158d636e3c1ef62fe6 Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Tue, 12 Nov 2024 14:46:44 +0000
Subject: [PATCH 14/18] fix(esp32): lvgl component error in idf v5.2.x.

---
 micropython.cmake | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/micropython.cmake b/micropython.cmake
index df3363e0e..81040de47 100644
--- a/micropython.cmake
+++ b/micropython.cmake
@@ -17,6 +17,11 @@ idf_build_set_property(COMPILE_OPTIONS "-Wno-unused-function" APPEND)
 idf_build_set_property(SRCS "${LV_SRC}" APPEND)
 idf_build_set_property(INCLUDE_DIRS "${LV_INCLUDE}" APPEND)
 
+# Fix for idf 5.2.x
+idf_build_get_property(component_targets __COMPONENT_TARGETS)
+string(REPLACE "___idf_lvgl" "" component_targets "${component_targets}")
+idf_build_set_property(__COMPONENT_TARGETS "${component_targets}")
+
 include(${CMAKE_CURRENT_LIST_DIR}/mkrules_usermod.cmake)
 
 # Add lv_bindings rules

From e88717b6145553a31944466d2db48f2581b2cb7f Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Thu, 5 Dec 2024 21:58:55 +0000
Subject: [PATCH 15/18] fix(lv_conf): enable LV_USE_PRIVATE_API for v9.2.0

---
 lv_conf.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lv_conf.h b/lv_conf.h
index 52f55ca66..43e2a9451 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -311,6 +311,9 @@
 /*-------------
  * Others
  *-----------*/
+/* PRIVATE API */
+
+#define LV_USE_PRIVATE_API 1
 
 /*Garbage Collector settings
  *Used if LVGL is bound to higher level language and the memory is managed by that language*/

From 61cfb7a09ad26720974ab449b329891e9f5b3805 Mon Sep 17 00:00:00 2001
From: Carlosgg <carlosgilglez@gmail.com>
Date: Fri, 7 Feb 2025 16:36:17 +1100
Subject: [PATCH 16/18] fix(build): enable LV_CONF_PATH option

This allows to set custom `lv_conf.h` file per board
in `mpconfigboard.(h,cmake)`
---
 micropython.cmake     | 11 ++++++++++-
 micropython.mk        |  8 +++++++-
 mkrules_usermod.cmake | 22 +++++++++++++++++++---
 3 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/micropython.cmake b/micropython.cmake
index 81040de47..db7adc553 100644
--- a/micropython.cmake
+++ b/micropython.cmake
@@ -17,6 +17,9 @@ idf_build_set_property(COMPILE_OPTIONS "-Wno-unused-function" APPEND)
 idf_build_set_property(SRCS "${LV_SRC}" APPEND)
 idf_build_set_property(INCLUDE_DIRS "${LV_INCLUDE}" APPEND)
 
+# DEBUG LV_CONF_PATH
+message(STATUS "LV_CONF_PATH=${LV_CONF_PATH}")
+
 # Fix for idf 5.2.x
 idf_build_get_property(component_targets __COMPONENT_TARGETS)
 string(REPLACE "___idf_lvgl" "" component_targets "${component_targets}")
@@ -45,7 +48,9 @@ target_compile_options(lvgl_interface INTERFACE ${LV_CFLAGS})
 add_library(usermod_lvgl INTERFACE)
 target_sources(usermod_lvgl INTERFACE ${LV_SRC})
 target_include_directories(usermod_lvgl INTERFACE ${LV_INCLUDE})
-
+if (DEFINED LV_CONF_DIR)
+    target_include_directories(usermod_lvgl INTERFACE ${LV_CONF_DIR})
+endif()
 
 file(WRITE ${LV_MP} "")
 
@@ -53,4 +58,8 @@ target_link_libraries(usermod_lvgl INTERFACE lvgl_interface)
 
 # # # make usermod (target declared by Micropython for all user compiled modules) link to bindings
 # # # this way the bindings (and transitively lvgl_interface) get proper compilation flags
+if (DEFINED LV_CONF_DIR)
+    target_include_directories(usermod INTERFACE ${LV_CONF_DIR})
+endif()
+target_compile_options(usermod INTERFACE -DLV_CONF_PATH="${LV_CONF_PATH}")
 target_link_libraries(usermod INTERFACE usermod_lvgl)
diff --git a/micropython.mk b/micropython.mk
index 420411e79..8620dcb03 100644
--- a/micropython.mk
+++ b/micropython.mk
@@ -8,11 +8,17 @@ LVGL_BINDING_DIR := $(USERMOD_DIR)
 LVGL_DIR = $(LVGL_BINDING_DIR)/lvgl
 LVGL_GENERIC_DRV_DIR = $(LVGL_BINDING_DIR)/driver/generic
 
+ifeq ($(LV_CONF_PATH),)
+LV_CONF_PATH = $(LVGL_BINDING_DIR)/lv_conf.h
+endif
+$(info    LV_CONF_PATH is $(LV_CONF_PATH))
+CFLAGS_USERMOD += -DLV_CONF_PATH="<$(abspath $(LV_CONF_PATH))>"
+
 ifeq ($(wildcard $(LVGL_DIR)/.),,)
 $(info lvgl submodule not init)
 else
 # This listing of all lvgl src files is used by make to track when the bindings need to be regenerated
-ALL_LVGL_SRC = $(shell find $(LVGL_DIR) -type f -name '*.h') $(LVGL_BINDING_DIR)/lv_conf.h
+ALL_LVGL_SRC = $(shell find $(LVGL_DIR) -type f -name '*.h') $(LV_CONF_PATH)
 endif
 
 LVGL_PP = $(BUILD)/lvgl/lvgl.pp.c
diff --git a/mkrules_usermod.cmake b/mkrules_usermod.cmake
index 9846dbe90..9b0f8de4d 100644
--- a/mkrules_usermod.cmake
+++ b/mkrules_usermod.cmake
@@ -5,6 +5,14 @@ find_program(AWK awk mawk gawk)
 
 set(LV_BINDINGS_DIR ${CMAKE_CURRENT_LIST_DIR})
 
+# first check
+if(NOT DEFINED LV_CONF_PATH)
+    set(LV_CONF_PATH "${LV_BINDINGS_DIR}/lv_conf.h")
+
+    message(STATUS "LV_CONF_PATH=${LV_CONF_PATH}")
+endif()
+get_filename_component(LV_CONF_PATH "${LV_CONF_PATH}" ABSOLUTE)
+
 # Common function for creating LV bindings
 
 function(lv_bindings)
@@ -25,13 +33,15 @@ function(lv_bindings)
         OUTPUT 
             ${LV_PP}
         COMMAND
-        ${CMAKE_C_COMPILER} -E -DPYCPARSER ${LV_COMPILE_OPTIONS} ${LV_PP_OPTIONS} "${LV_CFLAGS}" -I ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include ${MICROPY_CPP_FLAGS} ${LV_INPUT} > ${LV_PP}
+        ${CMAKE_C_COMPILER} -E -DPYCPARSER -DLV_CONF_PATH="${LV_CONF_PATH}" ${LV_COMPILE_OPTIONS} ${LV_PP_OPTIONS} "${LV_CFLAGS}" -I ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include ${MICROPY_CPP_FLAGS} ${LV_INPUT} > ${LV_PP}
         DEPENDS
             ${LV_INPUT}
             ${LV_DEPENDS}
             ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include
         IMPLICIT_DEPENDS
             C ${LV_INPUT}
+        COMMENT
+            "LV_BINDINGS CPP"
         VERBATIM
         COMMAND_EXPAND_LISTS
     )
@@ -60,6 +70,9 @@ function(lv_bindings)
                 ${AWK} ${LV_AWK_COMMAND} ${LV_PP} > ${LV_PP_FILTERED}
             DEPENDS
                 ${LV_PP}
+
+            COMMENT
+                "LV_BINDINGS: FILTER"
             VERBATIM
             COMMAND_EXPAND_LISTS
         )
@@ -71,10 +84,13 @@ function(lv_bindings)
         OUTPUT
             ${LV_OUTPUT}
         COMMAND
-            ${Python3_EXECUTABLE} ${LV_BINDINGS_DIR}/gen/gen_mpy.py ${LV_GEN_OPTIONS} -MD ${LV_MPY_METADATA} -E ${LV_PP_FILTERED} ${LV_INPUT} > ${LV_OUTPUT} || (rm -f ${LV_OUTPUT} && /bin/false)
+            ${Python3_EXECUTABLE} ${LV_BINDINGS_DIR}/gen/gen_mpy.py ${LV_GEN_OPTIONS} -DLV_CONF_PATH="${LV_CONF_PATH}" -MD ${LV_MPY_METADATA} -E ${LV_PP_FILTERED} ${LV_INPUT} > ${LV_OUTPUT} || (rm -f ${LV_OUTPUT} && /bin/false)
         DEPENDS
             ${LV_BINDINGS_DIR}/gen/gen_mpy.py
             ${LV_PP_FILTERED}
+
+        COMMENT
+            "LV_BINDINGS GEN MPY"
         COMMAND_EXPAND_LISTS
     )
 
@@ -95,7 +111,7 @@ function(all_lv_bindings)
 
     # LVGL bindings
 
-    file(GLOB_RECURSE LVGL_HEADERS ${LVGL_DIR}/src/*.h ${LV_BINDINGS_DIR}/lv_conf.h)
+    file(GLOB_RECURSE LVGL_HEADERS ${LVGL_DIR}/src/*.h ${LV_CONF_PATH})
     lv_bindings(
         OUTPUT
             ${LV_MP}

From ec8a9b63d911a733e1a08e62a43122376ca6ea0d Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Tue, 22 Oct 2024 14:42:48 +1100
Subject: [PATCH 17/18] c_modules: Add mimxrt support.

---
 micropython.mk | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/micropython.mk b/micropython.mk
index 8620dcb03..8b6d66578 100644
--- a/micropython.mk
+++ b/micropython.mk
@@ -61,6 +61,15 @@ FROZEN_MANIFEST += $(LVGL_BINDING_DIR)/manifest.py
 # Per-port Support 
 
 MICROPY_PORT = $(notdir $(CURDIR))
+$(info MICROPY_PORT: $(MICROPY_PORT))
+
+ifeq ($(MICROPY_PORT),mimxrt)
+CFLAGS_USERMOD += -DLV_USE_PXP=1 -DLV_USE_DRAW_PXP=1 -DLV_USE_GPU_NXP_PXP=1 -DLV_USE_GPU_NXP_PXP_AUTO_INIT=1
+
+$(BUILD)/$(MOD_DIRNAME)/lvgl/src/draw/nxp/pxp/lv_draw_pxp.o: CFLAGS_USERMOD += -Wno-error=unused-variable
+$(BUILD)/$(MOD_DIRNAME)/lvgl/src/draw/nxp/pxp/lv_draw_pxp_img.o: CFLAGS_USERMOD += -Wno-error=float-conversion
+
+endif
 
 ifeq ($(MICROPY_PORT),unix)
 # This section only included when building the micropython unix port

From 08e17a3dfe2fac198720081c1440aed4061929ce Mon Sep 17 00:00:00 2001
From: Andrew Leech <andrew.leech@planetinnovation.com.au>
Date: Fri, 31 Jan 2025 15:13:20 +1100
Subject: [PATCH 18/18] lv_conf: Merge in changes / defaults from lvgl update.

---
 lv_conf.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lv_conf.h b/lv_conf.h
index 43e2a9451..d3a661e66 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -457,7 +457,7 @@ extern void mp_lv_deinit_gc();
 #define LV_FONT_MONTSERRAT_42 0
 #define LV_FONT_MONTSERRAT_44 0
 #define LV_FONT_MONTSERRAT_46 0
-#define LV_FONT_MONTSERRAT_48 0
+#define LV_FONT_MONTSERRAT_48 1
 
 /*Demonstrate special features*/
 #define LV_FONT_MONTSERRAT_28_COMPRESSED 0  /*bpp = 3*/
@@ -667,7 +667,7 @@ extern void mp_lv_deinit_gc();
 /*File system interfaces for common APIs */
 
 /*API for fopen, fread, etc*/
-#define LV_USE_FS_STDIO 1
+#define LV_USE_FS_STDIO 0
 #if LV_USE_FS_STDIO
     #define LV_FS_STDIO_LETTER 'A'      /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
     #define LV_FS_STDIO_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
@@ -675,7 +675,7 @@ extern void mp_lv_deinit_gc();
 #endif
 
 /*API for open, read, etc*/
-#define LV_USE_FS_POSIX 1
+#define LV_USE_FS_POSIX 0
 #if LV_USE_FS_POSIX
     #define LV_FS_POSIX_LETTER 'P'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
     #define LV_FS_POSIX_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
@@ -698,7 +698,7 @@ extern void mp_lv_deinit_gc();
 #endif
 
 /*API for memory-mapped file access. */
-#define LV_USE_FS_MEMFS 0
+#define LV_USE_FS_MEMFS 1
 #if LV_USE_FS_MEMFS
     #define LV_FS_MEMFS_LETTER 'M'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
 #endif