From 978f029f8628b789317876c5cca614d05d785c13 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 27 Oct 2026 22:24:39 +0100 Subject: [PATCH 1/2] Improved simulator --- Devices/simulator/Source/Simulator.h | 4 + Devices/simulator/Source/SimulatorProfile.cpp | 15 ++-- Devices/simulator/Source/SimulatorProfile.h | 5 +- .../simulator/Source/SimulatorProfileApp.cpp | 5 +- .../{sdl_pointer.cpp => sdl_touch.cpp} | 6 +- Devices/simulator/Source/module.cpp | 15 ++-- Devices/simulator/simulator.dts | 5 ++ .../bindings/mock,power-supply.yaml | 8 ++ .../include/bindings/mock_power_supply.h | 7 ++ .../include/mock/mock_power_supply.h | 14 +++ .../source/mock_power_supply.cpp | 85 +++++++++++++++++++ Drivers/mock-drivers-module/source/module.cpp | 2 + .../lvgl-module/source/devices/display.cpp | 3 +- Platforms/platform-posix/source/freertos.c | 7 +- TactilityKernel/include/tactility/log.h | 6 ++ TactilityKernel/source/log_queue.cpp | 18 ++++ 16 files changed, 180 insertions(+), 25 deletions(-) rename Devices/simulator/Source/drivers/{sdl_pointer.cpp => sdl_touch.cpp} (92%) create mode 100644 Drivers/mock-drivers-module/bindings/mock,power-supply.yaml create mode 100644 Drivers/mock-drivers-module/include/bindings/mock_power_supply.h create mode 100644 Drivers/mock-drivers-module/include/mock/mock_power_supply.h create mode 100644 Drivers/mock-drivers-module/source/mock_power_supply.cpp diff --git a/Devices/simulator/Source/Simulator.h b/Devices/simulator/Source/Simulator.h index bf37f6277..c754c4232 100644 --- a/Devices/simulator/Source/Simulator.h +++ b/Devices/simulator/Source/Simulator.h @@ -4,6 +4,8 @@ #include "SimulatorProfile.h" #include "drivers/sdl_bridge.h" +#include + #include #include #include @@ -37,6 +39,8 @@ int main(int, char** argv) { sigdelset(&all_signals_except_sigint, SIGINT); pthread_sigmask(SIG_SETMASK, &all_signals_except_sigint, nullptr); + log_set_direct_write_for_current_thread(); + // FreeRTOS and app_main() run on a separate thread: macOS requires SDL/Cocoa window creation, // event pumping and rendering to happen on the real OS main thread, which sdl_bridge_run_main_loop() // below takes over. freertosMain() never returns, so this thread is detached rather than joined. diff --git a/Devices/simulator/Source/SimulatorProfile.cpp b/Devices/simulator/Source/SimulatorProfile.cpp index 8ae7943ed..1c6d2fe0d 100644 --- a/Devices/simulator/Source/SimulatorProfile.cpp +++ b/Devices/simulator/Source/SimulatorProfile.cpp @@ -1,5 +1,7 @@ #include "SimulatorProfile.h" +#include "lv_conf.h" + #include #include @@ -26,13 +28,12 @@ constexpr auto* DISPLAY_SETTINGS_PATH = "data/tactility/user/app/tactility.displ constexpr auto* ORIENTATION_KEY = "orientation="; static const Profile PROFILES[PROFILE_COUNT] = { - { "Default", 640, 320, false, false, false, true, true }, - { "CL-32", 384, 168, false, true, true, true, false }, - { "LilyGO T-Deck", 320, 240, false, false, false, true, true }, - { "LilyGO T-Lora Pager", 480, 222, false, false, false, true, false }, - { "M5Stack Cardputer", 240, 135, false, true, false, true, false }, - // Half the res on each axis to compensate for DPI differences - { "M5Stack Tab5", 640, 360, true, false, false, true, true }, + { "Default", 640, 320, LV_DPI_DEF, false, false, false, true, true }, + { "CL-32", 384, 168, 139, false, true, true, true, false }, + { "LilyGO T-Deck", 320, 240, 143, false, false, false, true, true }, + { "LilyGO T-Lora Pager", 480, 222, 150, false, false, false, true, false }, + { "M5Stack Cardputer", 240, 135, 139, false, true, false, true, false }, + { "M5Stack Tab5", 1280, 720, 250, true, false, false, true, true }, }; static size_t activeProfileIndex = 0; diff --git a/Devices/simulator/Source/SimulatorProfile.h b/Devices/simulator/Source/SimulatorProfile.h index 7000392dc..94271c3ab 100644 --- a/Devices/simulator/Source/SimulatorProfile.h +++ b/Devices/simulator/Source/SimulatorProfile.h @@ -13,6 +13,7 @@ struct Profile { const char* name; uint16_t width; uint16_t height; + uint16_t dpi; // lvgl.dpi from device.properties (if set), otherwise display.dpi /** The default rotation: the display is taller than wide. The width and height values may be given in either order. */ bool portrait; /** Compact UI density */ @@ -20,10 +21,10 @@ struct Profile { /** Greyscale display, which makes the monochrome theme apply */ bool mono; bool keyboard; - bool pointer; + bool touch; }; -constexpr size_t PROFILE_COUNT = 6; +constexpr size_t PROFILE_COUNT = 7; /** Overrides the default orientation of the selected profile */ enum class OrientationOverride { diff --git a/Devices/simulator/Source/SimulatorProfileApp.cpp b/Devices/simulator/Source/SimulatorProfileApp.cpp index 7da2629f7..8513caacc 100644 --- a/Devices/simulator/Source/SimulatorProfileApp.cpp +++ b/Devices/simulator/Source/SimulatorProfileApp.cpp @@ -1,6 +1,7 @@ #include "SimulatorProfile.h" #include +#include #include #include #include @@ -284,9 +285,9 @@ extern const ::AppManifest profileAppManifest = { .id = "tactility.simulator", .name = "Simulator", .category = APP_CATEGORY_SETTINGS, - .location = { APP_LOCATION_MEMORY, reinterpret_cast(appMain) }, + .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, .flags = 0, - .stack = {} + .stack = {}, }; } diff --git a/Devices/simulator/Source/drivers/sdl_pointer.cpp b/Devices/simulator/Source/drivers/sdl_touch.cpp similarity index 92% rename from Devices/simulator/Source/drivers/sdl_pointer.cpp rename to Devices/simulator/Source/drivers/sdl_touch.cpp index 0d8a1dd74..d4c14cbe2 100644 --- a/Devices/simulator/Source/drivers/sdl_pointer.cpp +++ b/Devices/simulator/Source/drivers/sdl_touch.cpp @@ -54,9 +54,9 @@ static const TouchApi sdl_touch_api = { extern Module simulator_module; -Driver sdl_pointer_driver = { - .name = "sdl-pointer", - .compatible = (const char*[]) { "tactility,sdl-pointer", nullptr }, +Driver sdl_touch_driver = { + .name = "sdl-touch", + .compatible = (const char*[]) { "tactility,sdl-touch", nullptr }, .start_device = start, .stop_device = stop, .probe = nullptr, diff --git a/Devices/simulator/Source/module.cpp b/Devices/simulator/Source/module.cpp index cea09c7e2..d33e768a5 100644 --- a/Devices/simulator/Source/module.cpp +++ b/Devices/simulator/Source/module.cpp @@ -1,5 +1,6 @@ #include "SimulatorProfile.h" #include "drivers/sdl_display.h" +#include "lv_conf.h" #include @@ -20,12 +21,12 @@ constexpr auto* TAG = "Simulator"; extern "C" { extern Driver sdl_display_driver; -extern Driver sdl_pointer_driver; +extern Driver sdl_touch_driver; extern Driver sdl_keyboard_driver; static Driver* const simulator_drivers[] = { &sdl_display_driver, - &sdl_pointer_driver, + &sdl_touch_driver, &sdl_keyboard_driver, nullptr }; @@ -88,8 +89,10 @@ static void on_root_started(Device* device, DeviceEvent event, void* context) { } const auto& profile = simulator::getProfile(simulator::getActiveProfileIndex()); - const uint16_t short_side = std::min(profile.width, profile.height); - const uint16_t long_side = std::max(profile.width, profile.height); + const auto width = static_cast(profile.width * LV_DPI_DEF / profile.dpi); + const auto height = static_cast(profile.height * LV_DPI_DEF / profile.dpi); + const uint16_t short_side = std::min(width, height); + const uint16_t long_side = std::max(width, height); sdl_display_config = { profile.portrait ? short_side : long_side, profile.portrait ? long_side : short_side, @@ -97,8 +100,8 @@ static void on_root_started(Device* device, DeviceEvent event, void* context) { }; construct_add_start(&sdl_display_device, device, "display0", &sdl_display_config, "tactility,sdl-display"); - if (profile.pointer) { - construct_add_start(&sdl_pointer_device, device, "pointer0", nullptr, "tactility,sdl-pointer"); + if (profile.touch) { + construct_add_start(&sdl_pointer_device, device, "touch0", nullptr, "tactility,sdl-touch"); } if (profile.keyboard) { construct_add_start(&sdl_keyboard_device, device, "keyboard0", nullptr, "tactility,sdl-keyboard"); diff --git a/Devices/simulator/simulator.dts b/Devices/simulator/simulator.dts index c02d535a6..6e5f99d22 100644 --- a/Devices/simulator/simulator.dts +++ b/Devices/simulator/simulator.dts @@ -4,6 +4,7 @@ #include #include #include +#include / { model = "Simulator"; @@ -20,4 +21,8 @@ bluetooth0 { compatible = "mock,bluetooth"; }; + + power0 { + compatible = "mock,power-supply"; + }; }; diff --git a/Drivers/mock-drivers-module/bindings/mock,power-supply.yaml b/Drivers/mock-drivers-module/bindings/mock,power-supply.yaml new file mode 100644 index 000000000..6b6632a5c --- /dev/null +++ b/Drivers/mock-drivers-module/bindings/mock,power-supply.yaml @@ -0,0 +1,8 @@ +description: Mock power supply with a fixed battery state and toggleable charging + +compatible: "mock,power-supply" + +properties: + _unused: + type: int + default: 0 diff --git a/Drivers/mock-drivers-module/include/bindings/mock_power_supply.h b/Drivers/mock-drivers-module/include/bindings/mock_power_supply.h new file mode 100644 index 000000000..935bb6d05 --- /dev/null +++ b/Drivers/mock-drivers-module/include/bindings/mock_power_supply.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(power_supply, struct MockPowerSupplyConfig) diff --git a/Drivers/mock-drivers-module/include/mock/mock_power_supply.h b/Drivers/mock-drivers-module/include/mock/mock_power_supply.h new file mode 100644 index 000000000..5b8f0d672 --- /dev/null +++ b/Drivers/mock-drivers-module/include/mock/mock_power_supply.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct MockPowerSupplyConfig { + int _unused; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/mock-drivers-module/source/mock_power_supply.cpp b/Drivers/mock-drivers-module/source/mock_power_supply.cpp new file mode 100644 index 000000000..912d2dbda --- /dev/null +++ b/Drivers/mock-drivers-module/source/mock_power_supply.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +#include + +// region PowerSupplyApi + +static std::atomic allowed_to_charge { true }; + +static bool supports_property(Device*, PowerSupplyProperty property) { + switch (property) { + case POWER_SUPPLY_PROP_IS_CHARGING: + case POWER_SUPPLY_PROP_CURRENT: + case POWER_SUPPLY_PROP_VOLTAGE: + case POWER_SUPPLY_PROP_CAPACITY: + return true; + default: + return false; + } +} + +static error_t get_property(Device*, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + const bool charging = allowed_to_charge.load(); + switch (property) { + case POWER_SUPPLY_PROP_IS_CHARGING: + out_value->int_value = charging ? 1 : 0; + return ERROR_NONE; + case POWER_SUPPLY_PROP_CURRENT: + out_value->int_value = charging ? 500 : -150; + return ERROR_NONE; + case POWER_SUPPLY_PROP_VOLTAGE: + out_value->int_value = 3950; + return ERROR_NONE; + case POWER_SUPPLY_PROP_CAPACITY: + out_value->int_value = 80; + return ERROR_NONE; + default: + return ERROR_NOT_SUPPORTED; + } +} + +static bool supports_charge_control(Device*) { return true; } +static bool is_allowed_to_charge(Device*) { return allowed_to_charge.load(); } + +static error_t set_allowed_to_charge(Device*, bool allowed) { + allowed_to_charge.store(allowed); + return ERROR_NONE; +} + +static bool supports_quick_charge(Device*) { return false; } +static bool is_quick_charge_enabled(Device*) { return false; } +static error_t set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool supports_power_off(Device*) { return false; } +static error_t power_off(Device*) { return ERROR_NOT_SUPPORTED; } + +// endregion + +static const PowerSupplyApi mock_power_supply_api = { + .supports_property = supports_property, + .get_property = get_property, + .supports_charge_control = supports_charge_control, + .is_allowed_to_charge = is_allowed_to_charge, + .set_allowed_to_charge = set_allowed_to_charge, + .supports_quick_charge = supports_quick_charge, + .is_quick_charge_enabled = is_quick_charge_enabled, + .set_quick_charge_enabled = set_quick_charge_enabled, + .supports_power_off = supports_power_off, + .power_off = power_off, +}; + +extern "C" { + +Driver mock_power_supply_driver = { + .name = "mock_power_supply", + .compatible = (const char*[]) { "mock,power-supply", nullptr }, + .start_device = nullptr, + .stop_device = nullptr, + .api = (const void*)&mock_power_supply_api, + .device_type = &POWER_SUPPLY_TYPE, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/mock-drivers-module/source/module.cpp b/Drivers/mock-drivers-module/source/module.cpp index a0a5dd43f..12cc87c20 100644 --- a/Drivers/mock-drivers-module/source/module.cpp +++ b/Drivers/mock-drivers-module/source/module.cpp @@ -7,11 +7,13 @@ extern "C" { extern Driver mock_wifi_driver; extern Driver mock_radio_driver; extern Driver mock_bluetooth_driver; +extern Driver mock_power_supply_driver; static Driver* const mock_drivers[] = { &mock_wifi_driver, &mock_radio_driver, &mock_bluetooth_driver, + &mock_power_supply_driver, nullptr }; diff --git a/Modules/lvgl-module/source/devices/display.cpp b/Modules/lvgl-module/source/devices/display.cpp index 3e0ff0ee2..290d3fb47 100644 --- a/Modules/lvgl-module/source/devices/display.cpp +++ b/Modules/lvgl-module/source/devices/display.cpp @@ -437,7 +437,8 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* } else { uint16_t buf_height = config->force_full_frame || config->buffer_height == 0 ? vres : config->buffer_height; - buf_size_bytes = (size_t)hres * buf_height * bpp; + // LVGL rounds the size it needs for an area up to LV_DRAW_BUF_ALIGN + buf_size_bytes = LV_ROUND_UP((size_t)hres * buf_height * bpp, LV_DRAW_BUF_ALIGN); ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes, config->prefer_external_ram); if (ctx->buf1 == NULL) { diff --git a/Platforms/platform-posix/source/freertos.c b/Platforms/platform-posix/source/freertos.c index e4a149ed9..a7b1c418d 100644 --- a/Platforms/platform-posix/source/freertos.c +++ b/Platforms/platform-posix/source/freertos.c @@ -1,17 +1,16 @@ +#include #include #if defined(__GLIBC__) #include #endif #include -#include - -#define TAG "freertos" /** * Assert implementation as defined in the FreeRTOSConfig.h */ void vAssertCalled(unsigned long line, const char* const file) { - LOG_E("Assert triggered at {}:{}", file, line); + // Logging can recurse into this assert when it happens from inside the log queue + fprintf(stderr, "Assert triggered at %s:%lu\n", file, line); #if defined(__GLIBC__) __assert_fail("assert failed", file, line, ""); #else diff --git a/TactilityKernel/include/tactility/log.h b/TactilityKernel/include/tactility/log.h index 1a702cc0b..e9665dd00 100644 --- a/TactilityKernel/include/tactility/log.h +++ b/TactilityKernel/include/tactility/log.h @@ -23,6 +23,12 @@ enum LogLevel { void log_generic(enum LogLevel level, const char* tag, const char* format, ...); +/** + * Makes logging on the calling thread write directly to the console. + * Must be called by threads that are not FreeRTOS tasks before they log, because the log queue can only be used from tasks. + */ +void log_set_direct_write_for_current_thread(void); + #define LOG_E(tag, ...) log_generic(LOG_LEVEL_ERROR, tag, ##__VA_ARGS__) #define LOG_W(tag, ...) log_generic(LOG_LEVEL_WARNING, tag, ##__VA_ARGS__) #define LOG_I(tag, ...) log_generic(LOG_LEVEL_INFO, tag, ##__VA_ARGS__) diff --git a/TactilityKernel/source/log_queue.cpp b/TactilityKernel/source/log_queue.cpp index 0ffeef5e4..26fe440c1 100644 --- a/TactilityKernel/source/log_queue.cpp +++ b/TactilityKernel/source/log_queue.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include @@ -26,6 +27,10 @@ struct LogQueueMessage { std::atomic g_queue { nullptr }; +#if !defined(ESP_PLATFORM) +thread_local bool g_direct_write = false; +#endif + // The one and only place a real console write happens for log output - deliberately never // through app_io_write()'s fd-table redirection (Modules/app-module/source/io.cpp). That // redirection only ever triggers for a task app_task_main() (Modules/app-module/source/ @@ -162,11 +167,24 @@ void log_queue_init(void) { #endif } +#if !defined(ESP_PLATFORM) +void log_set_direct_write_for_current_thread(void) { + g_direct_write = true; +} +#endif + void log_queue_write(const char* data, size_t length) { if (data == nullptr || length == 0) { return; } +#if !defined(ESP_PLATFORM) + if (g_direct_write) { + write_real(data, length); + return; + } +#endif + QueueHandle_t queue = g_queue.load(std::memory_order_acquire); if (queue == nullptr) { write_real(data, length); // pre-init fallback From e0b95413d5b31d30c6046f3ff83011a60df52a14 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 27 Oct 2026 22:37:42 +0100 Subject: [PATCH 2/2] Fix for profile count --- Devices/simulator/Source/SimulatorProfile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Devices/simulator/Source/SimulatorProfile.h b/Devices/simulator/Source/SimulatorProfile.h index 94271c3ab..13fdf8c5a 100644 --- a/Devices/simulator/Source/SimulatorProfile.h +++ b/Devices/simulator/Source/SimulatorProfile.h @@ -24,7 +24,7 @@ struct Profile { bool touch; }; -constexpr size_t PROFILE_COUNT = 7; +constexpr size_t PROFILE_COUNT = 6; /** Overrides the default orientation of the selected profile */ enum class OrientationOverride {