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/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() diff --git a/gen/gen_mpy.py b/gen/gen_mpy.py index 0b8437e27..5d930665c 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 @@ -622,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 = { @@ -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); @@ -1112,18 +1108,59 @@ 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() +{ + 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; + } +} + +#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. + 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 { @@ -1132,17 +1169,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 +1197,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 +1208,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 +1219,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 +1248,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 +1263,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 +1302,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 +1311,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 +1322,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 +1345,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 +1367,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 +1411,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 +1428,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 +1447,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 +1466,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 +1489,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 +1522,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 +1530,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 +1548,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 +1580,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 +1597,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 +1618,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 +1628,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 +1669,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 +1688,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 +1699,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 +1710,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 +1743,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 +2000,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 +2036,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 +2046,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 +2054,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 +2081,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 +2104,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 +2173,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 +2189,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 +2198,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 +2288,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 +2451,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 +2562,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 +2639,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 +2736,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 +2755,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 +2770,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 +2783,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 +2791,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 +2883,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 +2891,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 +2930,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,8 +3021,10 @@ 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}) }}, + {{ 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} @@ -3019,7 +3056,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 +3076,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 }}; diff --git a/lib/lv_utils.py b/lib/lv_utils.py index e920873ee..bb65d0f36 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,17 @@ 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': - # stm32 only supports SW timer -1 - default_timer_id = -1 - -if usys.platform == 'rp2': - # rp2 only supports SW timer -1 - default_timer_id = -1 - -# Try importing uasyncio, if available +# By default use soft timer +default_timer_id = -1 +# 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 +76,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 +130,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 +164,10 @@ 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) - event_loop.current_instance().deinit() + sys.print_exception(e) diff --git a/lv_conf.h b/lv_conf.h index c2d4e6c59..d3a661e66 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 @@ -295,11 +311,16 @@ /*------------- * 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*/ 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 @@ -422,7 +443,7 @@ extern void mp_lv_init_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 @@ -430,17 +451,18 @@ extern void mp_lv_init_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 #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*/ #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*/ @@ -539,6 +561,7 @@ extern void mp_lv_init_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 @@ -654,7 +677,7 @@ extern void mp_lv_init_gc(); /*API for open, read, etc*/ #define LV_USE_FS_POSIX 0 #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 @@ -692,6 +715,14 @@ extern void mp_lv_init_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 @@ -716,6 +747,7 @@ extern void mp_lv_init_gc(); #define LV_GIF_CACHE_DECODE_DATA 0 #endif + /*Decode bin images to RAM*/ #define LV_BIN_DECODER_RAM_LOAD 0 @@ -723,10 +755,10 @@ extern void mp_lv_init_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 @@ -769,18 +801,14 @@ extern void mp_lv_init_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 @@ -791,7 +819,7 @@ extern void mp_lv_init_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 @@ -906,7 +934,7 @@ extern void mp_lv_init_gc(); #define LV_USE_SDL 0 #endif #if LV_USE_SDL - #define LV_SDL_INCLUDE_PATH + #define LV_SDL_INCLUDE_PATH #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*/ @@ -981,10 +1009,10 @@ extern void mp_lv_init_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) diff --git a/lvgl b/lvgl index 905de3d7c..339e7d1d9 160000 --- a/lvgl +++ b/lvgl @@ -1 +1 @@ -Subproject commit 905de3d7c81367608577e1e38d032831a7871c16 +Subproject commit 339e7d1d964c40ef9d2de066172a14bdf0076412 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..db7adc553 --- /dev/null +++ b/micropython.cmake @@ -0,0 +1,65 @@ +# 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) +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) + +# 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}") +idf_build_set_property(__COMPONENT_TARGETS "${component_targets}") + +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}) +if (DEFINED LV_CONF_DIR) + target_include_directories(usermod_lvgl INTERFACE ${LV_CONF_DIR}) +endif() + +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 +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 new file mode 100644 index 000000000..8b6d66578 --- /dev/null +++ b/micropython.mk @@ -0,0 +1,118 @@ +################################################################################ +# 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 ($(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') $(LV_CONF_PATH) +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)) +$(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 +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 diff --git a/mkrules_usermod.cmake b/mkrules_usermod.cmake new file mode 100644 index 000000000..9b0f8de4d --- /dev/null +++ b/mkrules_usermod.cmake @@ -0,0 +1,173 @@ + + +find_package(Python3 REQUIRED COMPONENTS Interpreter) +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) + 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 -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 + ) + + # 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} + + COMMENT + "LV_BINDINGS: FILTER" + 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} -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 + ) + +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_CONF_PATH}) + 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") 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()