From da51c9b1db1a6fbdd0543acef97a0007fc94e69c Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:26:49 +0200 Subject: [PATCH] LVGL add logging before crash when heap allocation fails (#25170) --- CHANGELOG.md | 1 + .../lv_binding_berry/src/lv_colorwheel.c | 14 +++++----- .../lv_binding_berry/src/lv_stripes.c | 24 ++++++++--------- tasmota/lvgl_berry/tasmota_lv_assert.h | 26 +++++++++++++++++++ tasmota/lvgl_berry/tasmota_lv_conf.h | 6 ++--- tasmota/tasmota_xdrv_driver/xdrv_54_lvgl.ino | 17 ++++++++++++ 6 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 tasmota/lvgl_berry/tasmota_lv_assert.h diff --git a/CHANGELOG.md b/CHANGELOG.md index c83a2b40e..5290be90a 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - Support MAC OUI wildcard for command MI32Keys in case multiple devices use same bindkey like IoTorero: `mi32keys 0C3D5EFFFFFF=48656c6c6f204254486f6d65210d0a00` - uDisplay support for SPI QSPI displays (#25156) - LVGL/HASPmota visual touch pointer (dot under finger) (#25158) +- LVGL add logging before crash when heap allocation fails ### Breaking Changed - BTHome un-reverse MAC address usage for command `MI32Keys` to make it consistent diff --git a/lib/libesp32_lvgl/lv_binding_berry/src/lv_colorwheel.c b/lib/libesp32_lvgl/lv_binding_berry/src/lv_colorwheel.c index f91fab814..e92191f63 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/src/lv_colorwheel.c +++ b/lib/libesp32_lvgl/lv_binding_berry/src/lv_colorwheel.c @@ -125,7 +125,7 @@ bool lv_colorwheel_set_hsv(lv_obj_t * obj, lv_color_hsv_t hsv) if(hsv.s > 100) hsv.s = 100; if(hsv.v > 100) hsv.v = 100; - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return false); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; if(colorwheel->hsv.h == hsv.h && colorwheel->hsv.s == hsv.s && colorwheel->hsv.v == hsv.v) return false; @@ -160,7 +160,7 @@ bool lv_colorwheel_set_rgb(lv_obj_t * obj, lv_color_t color) */ void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; colorwheel->mode = mode; @@ -175,7 +175,7 @@ void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode) */ void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; colorwheel->mode_fixed = fixed; @@ -192,7 +192,7 @@ void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed) */ lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return (lv_color_hsv_t){0}); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; return colorwheel->hsv; @@ -205,7 +205,7 @@ lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj) */ lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return lv_color_black()); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; return lv_color_hsv_to_rgb(colorwheel->hsv.h, colorwheel->hsv.s, colorwheel->hsv.v); @@ -218,7 +218,7 @@ lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj) */ lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return LV_COLORWHEEL_MODE_HUE); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; return colorwheel->mode; @@ -231,7 +231,7 @@ lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj) */ bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return false); lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; return colorwheel->mode_fixed; diff --git a/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c b/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c index 023ba6552..bfa01ceb2 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c +++ b/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c @@ -88,7 +88,7 @@ lv_obj_t * lv_stripes_create(lv_obj_t * parent) void lv_stripes_set_stripe_color(lv_obj_t * obj, lv_color_t color) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_stripes_t * stripes = (lv_stripes_t *)obj; if(lv_color_eq(stripes->stripe_color, color)) return; @@ -98,7 +98,7 @@ void lv_stripes_set_stripe_color(lv_obj_t * obj, lv_color_t color) void lv_stripes_set_stripe_opa(lv_obj_t * obj, lv_opa_t opa) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_stripes_t * stripes = (lv_stripes_t *)obj; if(stripes->stripe_opa == opa) return; @@ -108,7 +108,7 @@ void lv_stripes_set_stripe_opa(lv_obj_t * obj, lv_opa_t opa) void lv_stripes_set_stripe_width(lv_obj_t * obj, int32_t width) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_stripes_t * stripes = (lv_stripes_t *)obj; if(width < 1) width = 1; @@ -119,7 +119,7 @@ void lv_stripes_set_stripe_width(lv_obj_t * obj, int32_t width) void lv_stripes_set_gap_width(lv_obj_t * obj, int32_t width) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_stripes_t * stripes = (lv_stripes_t *)obj; if(width < 0) width = 0; @@ -130,7 +130,7 @@ void lv_stripes_set_gap_width(lv_obj_t * obj, int32_t width) void lv_stripes_set_angle(lv_obj_t * obj, int32_t angle) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_stripes_t * stripes = (lv_stripes_t *)obj; angle %= 360; @@ -142,7 +142,7 @@ void lv_stripes_set_angle(lv_obj_t * obj, int32_t angle) void lv_stripes_set_offset(lv_obj_t * obj, int32_t offset) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return); lv_stripes_t * stripes = (lv_stripes_t *)obj; if(stripes->offset == offset) return; @@ -156,42 +156,42 @@ void lv_stripes_set_offset(lv_obj_t * obj, int32_t offset) lv_color_t lv_stripes_get_stripe_color(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return lv_color_black()); lv_stripes_t * stripes = (lv_stripes_t *)obj; return stripes->stripe_color; } lv_opa_t lv_stripes_get_stripe_opa(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return LV_OPA_TRANSP); lv_stripes_t * stripes = (lv_stripes_t *)obj; return stripes->stripe_opa; } int32_t lv_stripes_get_stripe_width(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return 0); lv_stripes_t * stripes = (lv_stripes_t *)obj; return stripes->stripe_width; } int32_t lv_stripes_get_gap_width(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return 0); lv_stripes_t * stripes = (lv_stripes_t *)obj; return stripes->gap_width; } int32_t lv_stripes_get_angle(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return 0); lv_stripes_t * stripes = (lv_stripes_t *)obj; return stripes->angle; } int32_t lv_stripes_get_offset(lv_obj_t * obj) { - LV_ASSERT_OBJ(obj, MY_CLASS); + LV_CHECK_OBJ(obj, MY_CLASS, return 0); lv_stripes_t * stripes = (lv_stripes_t *)obj; return stripes->offset; } diff --git a/tasmota/lvgl_berry/tasmota_lv_assert.h b/tasmota/lvgl_berry/tasmota_lv_assert.h new file mode 100644 index 000000000..8b56cc1ae --- /dev/null +++ b/tasmota/lvgl_berry/tasmota_lv_assert.h @@ -0,0 +1,26 @@ +/** + * @file tasmota_lv_assert.h + * + * Custom LVGL assert handler for Tasmota, included by lv_conf_internal.h + * when LV_ASSERT_USE_CUSTOM_INCLUDE is 1 (see tasmota_lv_conf.h). + * + * Replaces LVGL's default `while(1);` with a call that prints heap state + * and aborts, so a failed assert gives a decodable backtrace and a reboot + * instead of a silent hang. Implemented in xdrv_54_lvgl.ino. + */ +#ifndef TASMOTA_LV_ASSERT_H +#define TASMOTA_LV_ASSERT_H + +#ifdef __cplusplus +extern "C" { +#endif + +void lv_tasmota_assert_failed(void) __attribute__((noreturn)); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#define LV_ASSERT_HANDLER lv_tasmota_assert_failed(); + +#endif /*TASMOTA_LV_ASSERT_H*/ diff --git a/tasmota/lvgl_berry/tasmota_lv_conf.h b/tasmota/lvgl_berry/tasmota_lv_conf.h index ac51cb460..61bc8e5f8 100644 --- a/tasmota/lvgl_berry/tasmota_lv_conf.h +++ b/tasmota/lvgl_berry/tasmota_lv_conf.h @@ -2439,7 +2439,7 @@ #define LV_USE_ASSERT 0 /** LV_ASSERT_MALLOC */ -#define LV_USE_ASSERT_MALLOC 0 +#define LV_USE_ASSERT_MALLOC 1 /** LV_ASSERT_NULL */ #define LV_USE_ASSERT_NULL 0 @@ -2471,7 +2471,7 @@ /** Include the header given by LV_ASSERT_CUSTOM_INCLUDE, which must define * a custom LV_ASSERT_HANDLER. */ -#define LV_ASSERT_USE_CUSTOM_INCLUDE 0 +#define LV_ASSERT_USE_CUSTOM_INCLUDE 1 // TASMOTA #if LV_ASSERT_USE_CUSTOM_INCLUDE /** Optional header to override assert-related macros. @@ -2480,7 +2480,7 @@ * Statement to execute when an assertion fails. * e.g. #define LV_ASSERT_HANDLER my_assert_failed(__FILE__, __LINE__); */ -#define LV_ASSERT_CUSTOM_INCLUDE "" +#define LV_ASSERT_CUSTOM_INCLUDE "tasmota_lv_assert.h" // TASMOTA #endif /*LV_ASSERT_USE_CUSTOM_INCLUDE*/ diff --git a/tasmota/tasmota_xdrv_driver/xdrv_54_lvgl.ino b/tasmota/tasmota_xdrv_driver/xdrv_54_lvgl.ino index 214fba2f1..2acd5c716 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_54_lvgl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_54_lvgl.ino @@ -33,6 +33,8 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" +#include "esp_heap_caps.h" +#include "esp_rom_sys.h" // esp_rom_printf, used by lv_tasmota_assert_failed // callback type when a screen paint is done typedef void (*lv_paint_cb_t)(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t *pixels); @@ -400,6 +402,21 @@ extern "C" { free(ptr); } + // LVGL assert handler (LV_ASSERT_HANDLER, see tasmota/lvgl_berry/tasmota_lv_assert.h) + // Print heap state without allocating, then abort() to get a backtrace and a reboot + void lv_tasmota_assert_failed(void) { + esp_rom_printf("\nLVG: assert failed, heap internal free %u largest %u", + heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT), + heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) { + esp_rom_printf(", psram free %u largest %u", + heap_caps_get_free_size(MALLOC_CAP_SPIRAM), + heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM)); + } + esp_rom_printf("\n"); + abort(); + } + #ifdef USE_LVGL_PNG_DECODER // for PNG decoder, use same allocators as LVGL void* lodepng_malloc(size_t size) { return lvbe_malloc(size); }