From 8f5e92d6e4bd0bebc8e197e08af3860f65590f04 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 13 Jul 2026 21:38:28 +0200 Subject: [PATCH] Device implementations and touch calibration updates --- Devices/cyd-2432s024r/CMakeLists.txt | 5 +- .../cyd-2432s024r/Source/Configuration.cpp | 21 ---- .../cyd-2432s024r/Source/devices/Display.cpp | 46 --------- .../cyd-2432s024r/Source/devices/Display.h | 28 ------ Devices/cyd-2432s024r/cyd,2432s024r.dts | 32 ++++++- Devices/cyd-2432s024r/device.properties | 2 + Devices/cyd-2432s024r/devicetree.yaml | 2 + .../{Source => source}/module.cpp | 0 Devices/cyd-2432s028r/device.properties | 3 + Devices/unphone/device.properties | 3 + Drivers/XPT2046/Source/Xpt2046Touch.cpp | 22 +---- Drivers/XPT2046/Source/Xpt2046Touch.h | 2 - .../XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp | 4 - .../XPT2046SoftSPI/Source/Xpt2046SoftSpi.h | 1 - Firmware/Kconfig | 7 ++ .../include/tactility/lvgl_pointer.h | 50 ++++++++++ Modules/lvgl-module/source/lvgl_pointer.c | 95 ++++++++++++++++++- .../app/touchcalibration/TouchCalibration.h | 6 ++ .../settings/TouchCalibrationSettings.h | 20 ++-- Tactility/Source/Tactility.cpp | 4 + .../app/kerneldisplay/KernelDisplay.cpp | 26 ++++- Tactility/Source/app/setup/Setup.cpp | 13 +++ Tactility/Source/app/timezone/TimeZone.cpp | 10 +- .../app/touchcalibration/TouchCalibration.cpp | 91 +++++++++++++++--- Tactility/Source/lvgl/Lvgl.cpp | 20 ++++ .../settings/TouchCalibrationSettings.cpp | 64 +------------ device.py | 87 +++++++++-------- 27 files changed, 400 insertions(+), 264 deletions(-) delete mode 100644 Devices/cyd-2432s024r/Source/Configuration.cpp delete mode 100644 Devices/cyd-2432s024r/Source/devices/Display.cpp delete mode 100644 Devices/cyd-2432s024r/Source/devices/Display.h rename Devices/cyd-2432s024r/{Source => source}/module.cpp (100%) diff --git a/Devices/cyd-2432s024r/CMakeLists.txt b/Devices/cyd-2432s024r/CMakeLists.txt index 1e6393dd4..a832a83a2 100644 --- a/Devices/cyd-2432s024r/CMakeLists.txt +++ b/Devices/cyd-2432s024r/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x XPT2046 PwmBacklight driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-2432s024r/Source/Configuration.cpp b/Devices/cyd-2432s024r/Source/Configuration.cpp deleted file mode 100644 index 91782dd35..000000000 --- a/Devices/cyd-2432s024r/Source/Configuration.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "devices/Display.h" - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT); -} - -static DeviceVector createDevices() { - return { - createDisplay() - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/cyd-2432s024r/Source/devices/Display.cpp b/Devices/cyd-2432s024r/Source/devices/Display.cpp deleted file mode 100644 index f8aef5e46..000000000 --- a/Devices/cyd-2432s024r/Source/devices/Display.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Display.h" -#include "Xpt2046Touch.h" -#include -#include - -static std::shared_ptr createTouch(esp_lcd_spi_bus_handle_t spiDevice) { - auto configuration = std::make_unique( - spiDevice, - TOUCH_CS_PIN, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION, - true, // swapXY - false, // mirrorX - true // mirrorY - ); - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - Ili934xDisplay::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = true, - .mirrorX = true, - .mirrorY = true, - .invertColor = false, - .swapBytes = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(spi_configuration->spiHostDevice), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = LCD_PIN_RST, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB - }; - - return std::make_shared(panel_configuration, spi_configuration, true); -} diff --git a/Devices/cyd-2432s024r/Source/devices/Display.h b/Devices/cyd-2432s024r/Source/devices/Display.h deleted file mode 100644 index d8a8ae6c4..000000000 --- a/Devices/cyd-2432s024r/Source/devices/Display.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Display -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; -constexpr auto LCD_PIN_RST = GPIO_NUM_NC; // tied to ESP32 RST -constexpr auto LCD_PIN_CLK = GPIO_NUM_14; -constexpr auto LCD_PIN_MOSI = GPIO_NUM_13; -constexpr auto LCD_PIN_MISO = GPIO_NUM_12; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; -constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; - -// Backlight -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_27; - -// Touch -constexpr auto TOUCH_CS_PIN = GPIO_NUM_33; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36; - -std::shared_ptr createDisplay(); diff --git a/Devices/cyd-2432s024r/cyd,2432s024r.dts b/Devices/cyd-2432s024r/cyd,2432s024r.dts index a5e3ccd0e..928fb9a14 100644 --- a/Devices/cyd-2432s024r/cyd,2432s024r.dts +++ b/Devices/cyd-2432s024r/cyd,2432s024r.dts @@ -5,9 +5,10 @@ #include #include #include -#include -#include #include +#include +#include +#include / { compatible = "root"; @@ -23,6 +24,15 @@ gpio-count = <40>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; @@ -33,11 +43,23 @@ <&gpio0 33 GPIO_FLAG_NONE>; // Touch display@0 { - compatible = "display-placeholder"; + compatible = "ilitek,ili9341"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + swap-xy; + mirror-x; + mirror-y; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; touch@1 { - compatible = "pointer-placeholder"; + compatible = "xptek,xpt2046"; + x-max = <240>; + y-max = <320>; + swap-xy; + mirror-y; }; }; @@ -61,4 +83,4 @@ pin-tx = <&gpio0 1 GPIO_FLAG_NONE>; pin-rx = <&gpio0 3 GPIO_FLAG_NONE>; }; -}; \ No newline at end of file +}; diff --git a/Devices/cyd-2432s024r/device.properties b/Devices/cyd-2432s024r/device.properties index b2e05d040..57b8d394a 100644 --- a/Devices/cyd-2432s024r/device.properties +++ b/Devices/cyd-2432s024r/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.4" diff --git a/Devices/cyd-2432s024r/devicetree.yaml b/Devices/cyd-2432s024r/devicetree.yaml index ab75fd2c2..0f60266f2 100644 --- a/Devices/cyd-2432s024r/devicetree.yaml +++ b/Devices/cyd-2432s024r/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9341-module + - Drivers/xpt2046-module dts: cyd,2432s024r.dts diff --git a/Devices/cyd-2432s024r/Source/module.cpp b/Devices/cyd-2432s024r/source/module.cpp similarity index 100% rename from Devices/cyd-2432s024r/Source/module.cpp rename to Devices/cyd-2432s024r/source/module.cpp diff --git a/Devices/cyd-2432s028r/device.properties b/Devices/cyd-2432s028r/device.properties index c0047a338..847dc1fb0 100644 --- a/Devices/cyd-2432s028r/device.properties +++ b/Devices/cyd-2432s028r/device.properties @@ -13,6 +13,9 @@ display.size=2.8" display.shape=rectangle display.dpi=143 +touch.calibrationSupported=true +touch.calibrationRequired=false + cdn.warningMessage=There are 3 hardware variants of this board. This build works on the original variant only ("v1"). lvgl.colorDepth=16 diff --git a/Devices/unphone/device.properties b/Devices/unphone/device.properties index 3b2f5c7a3..45de3205b 100644 --- a/Devices/unphone/device.properties +++ b/Devices/unphone/device.properties @@ -16,6 +16,9 @@ display.size=3.5" display.shape=rectangle display.dpi=165 +touch.calibrationSupported=true +touch.calibrationRequired=true + cdn.warningMessage=Put the device into bootloader mode by pressing the center nav button and reset for 2-3 seconds, then release reset, then release the nav button.
After flashing is finished, press the reset button to reboot. lvgl.colorDepth=24 diff --git a/Drivers/XPT2046/Source/Xpt2046Touch.cpp b/Drivers/XPT2046/Source/Xpt2046Touch.cpp index 325d7a05b..4c39a63cb 100644 --- a/Drivers/XPT2046/Source/Xpt2046Touch.cpp +++ b/Drivers/XPT2046/Source/Xpt2046Touch.cpp @@ -1,30 +1,10 @@ #include "Xpt2046Touch.h" -#include #include -#include #include #include -static void processCoordinates(esp_lcd_touch_handle_t tp, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) { - (void)strength; - if (tp == nullptr || x == nullptr || y == nullptr || pointCount == nullptr || *pointCount == 0) { - return; - } - - auto* config = static_cast(tp->config.user_data); - if (config == nullptr) { - return; - } - - const auto settings = tt::settings::touch::getActive(); - const auto points = std::min(*pointCount, maxPointCount); - for (uint8_t i = 0; i < points; i++) { - tt::settings::touch::applyCalibration(settings, config->xMax, config->yMax, x[i], y[i]); - } -} - bool Xpt2046Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(configuration->spiPinCs); return esp_lcd_new_panel_io_spi(configuration->spiDevice, &io_config, &outHandle) == ESP_OK; @@ -49,7 +29,7 @@ esp_lcd_touch_config_t Xpt2046Touch::createEspLcdTouchConfig() { .mirror_x = configuration->mirrorX, .mirror_y = configuration->mirrorY, }, - .process_coordinates = processCoordinates, + .process_coordinates = nullptr, .interrupt_callback = nullptr, .user_data = configuration.get(), .driver_data = nullptr diff --git a/Drivers/XPT2046/Source/Xpt2046Touch.h b/Drivers/XPT2046/Source/Xpt2046Touch.h index eb65787f2..975f4a279 100644 --- a/Drivers/XPT2046/Source/Xpt2046Touch.h +++ b/Drivers/XPT2046/Source/Xpt2046Touch.h @@ -56,6 +56,4 @@ public: std::string getName() const final { return "XPT2046"; } std::string getDescription() const final { return "XPT2046 SPI touch driver"; } - - bool supportsCalibration() const override { return true; } }; diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp index d1c30471b..e8546b958 100644 --- a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp +++ b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp @@ -1,7 +1,6 @@ #include "Xpt2046SoftSpi.h" #include -#include #include @@ -198,9 +197,6 @@ bool Xpt2046SoftSpi::getTouchPoint(Point& point) { uint16_t x = static_cast(std::clamp(mappedX, 0, static_cast(configuration->xMax))); uint16_t y = static_cast(std::clamp(mappedY, 0, static_cast(configuration->yMax))); - const auto calibration = tt::settings::touch::getActive(); - tt::settings::touch::applyCalibration(calibration, configuration->xMax, configuration->yMax, x, y); - point.x = x; point.y = y; return true; diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h index 1bdddcc52..40ab7a0a9 100644 --- a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h +++ b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h @@ -99,7 +99,6 @@ public: bool stopLvgl() override; bool supportsTouchDriver() override { return true; } - bool supportsCalibration() const override { return true; } std::shared_ptr getTouchDriver() override; lv_indev_t* getLvglIndev() override { return lvglDevice; } diff --git a/Firmware/Kconfig b/Firmware/Kconfig index 95fce6704..3c575ca17 100644 --- a/Firmware/Kconfig +++ b/Firmware/Kconfig @@ -102,4 +102,11 @@ menu "Tactility App" help The minimum time to show the splash screen in milliseconds. When set to 0, startup will continue to desktop as soon as boot operations are finished. + config TT_TOUCH_CALIBRATION_SUPPORTED + bool "Set true when a touch screen calibration app should be included" + default n + config TT_TOUCH_CALIBRATION_REQUIRED + bool "Set true when a touch screen calibration is required before the device is usable" + default n + depends on TT_TOUCH_CALIBRATION_SUPPORTED endmenu diff --git a/Modules/lvgl-module/include/tactility/lvgl_pointer.h b/Modules/lvgl-module/include/tactility/lvgl_pointer.h index 363ae0bc8..b42495941 100644 --- a/Modules/lvgl-module/include/tactility/lvgl_pointer.h +++ b/Modules/lvgl-module/include/tactility/lvgl_pointer.h @@ -10,6 +10,56 @@ extern "C" { #include #include +/** + * @brief Linear per-axis calibration range for raw pointer coordinates. + * + * Values are the raw (pre-calibration) coordinates that should map to the display's + * [0, hor_res-1] / [0, ver_res-1] range. Corrects scale+offset error only; axis + * swap/mirror is handled separately by PointerApi and applied by the driver before + * lvgl_pointer_read_cb() sees the coordinates. + */ +struct LvglPointerCalibration { + int32_t x_min; + int32_t x_max; + int32_t y_min; + int32_t y_max; +}; + +/** + * @brief Sets (or clears, when calibration is NULL) the calibration applied to raw coordinates + * read from the device before they are written into LVGL indev data, on an indev previously + * created with lvgl_pointer_add(). + * + * @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h). + * + * @param[in] indev an indev previously created by lvgl_pointer_add() + * @param[in] calibration the calibration range to apply, or NULL to clear/disable calibration + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_ARGUMENT if indev is NULL, or calibration is non-NULL but invalid + * (x_max <= x_min, y_max <= y_min, or either span smaller than the minimum allowed range) + */ +error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration); + +/** + * @brief Retrieves the calibration currently active on indev, if any. + * @warning Caller must hold the LVGL lock. + * @return true when a calibration is currently set on indev (out_calibration is filled), false otherwise + */ +bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration); + +/** + * @brief Returns the first indev created by lvgl_pointer_add() that hasn't been removed yet. + * + * Unlike iterating LVGL's own indev list, this only ever returns an indev created by + * lvgl_pointer_add() — safe to pass to lvgl_pointer_set_calibration()/lvgl_pointer_get_calibration() + * without risking a foreign indev (e.g. one registered by the deprecated HAL layer) whose driver + * data isn't a struct LvglPointerCtx*. + * + * @warning Caller must hold the LVGL lock. + * @return the indev, or NULL if none is currently registered. + */ +lv_indev_t* lvgl_pointer_get_default(void); + /** * @brief Creates an lv_indev_t bound to the given POINTER_TYPE device and registers a read callback * that polls the device through its PointerApi. diff --git a/Modules/lvgl-module/source/lvgl_pointer.c b/Modules/lvgl-module/source/lvgl_pointer.c index 21894464f..1f64c08a4 100644 --- a/Modules/lvgl-module/source/lvgl_pointer.c +++ b/Modules/lvgl-module/source/lvgl_pointer.c @@ -7,11 +7,52 @@ struct LvglPointerCtx { struct Device* device; + bool calibration_enabled; + struct LvglPointerCalibration calibration; }; // Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll. static const TickType_t LVGL_POINTER_READ_TIMEOUT = pdMS_TO_TICKS(10); +// Tracks the first indev created by lvgl_pointer_add() still alive, for lvgl_pointer_get_default(). +// Only ever set/cleared by lvgl_pointer_add()/lvgl_pointer_remove(), so it can never point at an +// indev created by other code (e.g. the deprecated HAL's own LVGL pointer registration). +static lv_indev_t* default_pointer_indev = NULL; + +// Mirrors Tactility/Source/settings/TouchCalibrationSettings.cpp's isValid(). +static const int32_t LVGL_POINTER_CALIBRATION_MIN_RANGE = 20; + +static bool lvgl_pointer_calibration_is_valid(const struct LvglPointerCalibration* calibration) { + return calibration->x_max > calibration->x_min && + calibration->y_max > calibration->y_min && + (calibration->x_max - calibration->x_min) >= LVGL_POINTER_CALIBRATION_MIN_RANGE && + (calibration->y_max - calibration->y_min) >= LVGL_POINTER_CALIBRATION_MIN_RANGE; +} + +// Linear per-axis rescale of [x_min,x_max]/[y_min,y_max] onto [0,target_x_max]/[0,target_y_max], +// clamped. Mirrors TouchCalibrationSettings.cpp's applyCalibration(). Kept as a standalone +// function (not inlined into the read callback) so the math is isolated and easy to reason about. +static void lvgl_pointer_calibration_apply( + const struct LvglPointerCalibration* calibration, + int32_t target_x_max, + int32_t target_y_max, + uint16_t* x, + uint16_t* y +) { + int64_t mapped_x = ((int64_t)*x - calibration->x_min) * target_x_max / + ((int64_t)calibration->x_max - calibration->x_min); + int64_t mapped_y = ((int64_t)*y - calibration->y_min) * target_y_max / + ((int64_t)calibration->y_max - calibration->y_min); + + if (mapped_x < 0) mapped_x = 0; + if (mapped_x > target_x_max) mapped_x = target_x_max; + if (mapped_y < 0) mapped_y = 0; + if (mapped_y > target_y_max) mapped_y = target_y_max; + + *x = (uint16_t)mapped_x; + *y = (uint16_t)mapped_y; +} + static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); @@ -26,6 +67,16 @@ static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { bool touched = pointer_get_touched_points(ctx->device, &x, &y, NULL, &point_count, 1); if (touched && point_count > 0) { + if (ctx->calibration_enabled) { + lv_display_t* display = lv_indev_get_display(indev); + if (display != NULL) { + int32_t target_x_max = lv_display_get_horizontal_resolution(display) - 1; + int32_t target_y_max = lv_display_get_vertical_resolution(display) - 1; + if (target_x_max > 0 && target_y_max > 0) { + lvgl_pointer_calibration_apply(&ctx->calibration, target_x_max, target_y_max, &x, &y); + } + } + } data->point.x = x; data->point.y = y; data->state = LV_INDEV_STATE_PRESSED; @@ -42,7 +93,7 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_ return ERROR_INVALID_ARGUMENT; } - struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)malloc(sizeof(struct LvglPointerCtx)); + struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)calloc(1, sizeof(struct LvglPointerCtx)); if (ctx == NULL) { return ERROR_OUT_OF_MEMORY; } @@ -61,16 +112,58 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_ lv_indev_set_display(indev, display); } + if (default_pointer_indev == NULL) { + default_pointer_indev = indev; + } + *out_indev = indev; return ERROR_NONE; } +lv_indev_t* lvgl_pointer_get_default(void) { + return default_pointer_indev; +} + +error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration) { + if (indev == NULL) { + return ERROR_INVALID_ARGUMENT; + } + struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev); + + if (calibration == NULL) { + ctx->calibration_enabled = false; + return ERROR_NONE; + } + if (!lvgl_pointer_calibration_is_valid(calibration)) { + return ERROR_INVALID_ARGUMENT; + } + + ctx->calibration = *calibration; + ctx->calibration_enabled = true; + return ERROR_NONE; +} + +bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration) { + if (indev == NULL || out_calibration == NULL) { + return false; + } + struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + if (!ctx->calibration_enabled) { + return false; + } + *out_calibration = ctx->calibration; + return true; +} + void lvgl_pointer_remove(lv_indev_t* indev) { if (indev == NULL) { return; } struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + if (default_pointer_indev == indev) { + default_pointer_indev = NULL; + } lv_indev_delete(indev); free(ctx); } diff --git a/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h b/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h index 560f2e15e..6a85b6041 100644 --- a/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h +++ b/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h @@ -1,5 +1,9 @@ #pragma once +#include + +#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) + #include namespace tt::app::touchcalibration { @@ -7,3 +11,5 @@ namespace tt::app::touchcalibration { LaunchId start(); } // namespace tt::app::touchcalibration + +#endif diff --git a/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h b/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h index 1d80f7279..410c17099 100644 --- a/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h +++ b/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h @@ -4,6 +4,14 @@ namespace tt::settings::touch { +/** + * @brief Persisted touch calibration coefficients. + * + * Shape mirrors struct LvglPointerCalibration (tactility/lvgl_pointer.h): xMin/xMax/yMin/yMax are + * the raw touch coordinate range that should map onto the display's full resolution. This struct + * only concerns itself with persistence - applying it to a live pointer indev is the caller's + * responsibility (see lvgl_pointer_set_calibration()). + */ struct TouchCalibrationSettings { bool enabled = false; int32_t xMin = 0; @@ -14,20 +22,12 @@ struct TouchCalibrationSettings { TouchCalibrationSettings getDefault(); +bool isValid(const TouchCalibrationSettings& settings); + bool load(TouchCalibrationSettings& settings); TouchCalibrationSettings loadOrGetDefault(); bool save(const TouchCalibrationSettings& settings); -bool isValid(const TouchCalibrationSettings& settings); - -TouchCalibrationSettings getActive(); - -void setRuntimeCalibrationEnabled(bool enabled); - -void invalidateCache(); - -bool applyCalibration(const TouchCalibrationSettings& settings, uint16_t xMax, uint16_t yMax, uint16_t& x, uint16_t& y); - } // namespace tt::settings::touch diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 66af4d9e9..b363ef002 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -132,7 +132,9 @@ namespace app { namespace setup { extern const AppManifest manifest; } namespace systeminfo { extern const AppManifest manifest; } namespace timedatesettings { extern const AppManifest manifest; } +#ifdef CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED namespace touchcalibration { extern const AppManifest manifest; } +#endif namespace timezone { extern const AppManifest manifest; } namespace usbsettings { extern const AppManifest manifest; } namespace btmanage { extern const AppManifest manifest; } @@ -193,7 +195,9 @@ static void registerInternalApps() { addAppManifest(app::setup::manifest); addAppManifest(app::systeminfo::manifest); addAppManifest(app::timedatesettings::manifest); +#ifdef CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED addAppManifest(app::touchcalibration::manifest); +#endif addAppManifest(app::timezone::manifest); addAppManifest(app::wifiapsettings::manifest); addAppManifest(app::wificonnect::manifest); diff --git a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp index f1d2c0370..4ed1494ae 100644 --- a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp +++ b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp @@ -11,10 +11,12 @@ #include #endif #include +#include #include #include #include +#include namespace tt::app::kerneldisplay { @@ -98,6 +100,10 @@ class KernelDisplayApp final : public App { } } + static void onCalibrateTouchClicked(lv_event_t*) { + app::touchcalibration::start(); + } + static void onScreensaverChanged(lv_event_t* event) { auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); @@ -251,8 +257,24 @@ public: } } - // Note: no touch calibration section here - unlike HalDisplayApp, the kernel PointerApi has - // no calibration support yet. + if (lvgl_pointer_get_default() != nullptr) { + auto* calibrate_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(calibrate_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(calibrate_wrapper, 0, LV_STATE_DEFAULT); + + auto* calibrate_label = lv_label_create(calibrate_wrapper); + lv_label_set_text(calibrate_label, "Touch calibration"); + lv_obj_align(calibrate_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* calibrate_button = lv_button_create(calibrate_wrapper); + lv_obj_align(calibrate_button, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(calibrate_button, onCalibrateTouchClicked, LV_EVENT_SHORT_CLICKED, this); + + auto* calibrate_button_label = lv_label_create(calibrate_button); + lv_label_set_text(calibrate_button_label, "Calibrate"); + lv_obj_center(calibrate_button_label); + } } void onHide(AppContext& app) override { diff --git a/Tactility/Source/app/setup/Setup.cpp b/Tactility/Source/app/setup/Setup.cpp index d9bfff498..4336023ae 100644 --- a/Tactility/Source/app/setup/Setup.cpp +++ b/Tactility/Source/app/setup/Setup.cpp @@ -17,6 +17,12 @@ #include #include +#include + +#if defined(CONFIG_TT_TOUCH_CALIBRATION_REQUIRED) +#include +#endif + namespace tt::app::setup { extern const AppManifest manifest; @@ -139,6 +145,13 @@ public: void onCreate(AppContext& app) override { steps = { +#if defined(CONFIG_TT_TOUCH_CALIBRATION_REQUIRED) + { + .title = "Touch Calibration", + .description = "Let's calibrate the touch screen.", + .run = [] { touchcalibration::start(); } + }, +#endif { .title = "Time Zone Setup", .description = "Let's set the time zone.", diff --git a/Tactility/Source/app/timezone/TimeZone.cpp b/Tactility/Source/app/timezone/TimeZone.cpp index 11ab9992c..3d913db11 100644 --- a/Tactility/Source/app/timezone/TimeZone.cpp +++ b/Tactility/Source/app/timezone/TimeZone.cpp @@ -166,16 +166,16 @@ class TimeZoneApp final : public App { } void updateList() { - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + if (lvgl::lock(200 / portTICK_PERIOD_MS)) { std::string filter = string::lowercase(std::string(lv_textarea_get_text(filterTextareaWidget))); - readTimeZones(filter); lvgl::unlock(); + readTimeZones(filter); } else { - LOG_E(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); + LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "TimeZone LVGL"); return; } - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + if (lvgl::lock(200 / portTICK_PERIOD_MS)) { if (mutex.lock(100 / portTICK_PERIOD_MS)) { lv_obj_clean(listWidget); @@ -189,6 +189,8 @@ class TimeZoneApp final : public App { } lvgl::unlock(); + } else { + LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "TimeZone LVGL"); } } diff --git a/Tactility/Source/app/touchcalibration/TouchCalibration.cpp b/Tactility/Source/app/touchcalibration/TouchCalibration.cpp index 4af6013ab..01380492d 100644 --- a/Tactility/Source/app/touchcalibration/TouchCalibration.cpp +++ b/Tactility/Source/app/touchcalibration/TouchCalibration.cpp @@ -1,10 +1,13 @@ -#include - #include +#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) + +#include #include #include +#include +#include #include #include @@ -30,6 +33,7 @@ class TouchCalibrationApp final : public App { Sample samples[4] = {}; uint8_t sampleCount = 0; + bool calibrationApplied = false; lv_obj_t* root = nullptr; lv_obj_t* target = nullptr; @@ -73,12 +77,33 @@ class TouchCalibrationApp final : public App { } void finishCalibration() { - constexpr int32_t MIN_RANGE = 20; + const int32_t xLow = (static_cast(samples[0].x) + static_cast(samples[3].x)) / 2; + const int32_t xHigh = (static_cast(samples[1].x) + static_cast(samples[2].x)) / 2; + const int32_t yLow = (static_cast(samples[0].y) + static_cast(samples[1].y)) / 2; + const int32_t yHigh = (static_cast(samples[2].y) + static_cast(samples[3].y)) / 2; - const int32_t xMin = (static_cast(samples[0].x) + static_cast(samples[3].x)) / 2; - const int32_t xMax = (static_cast(samples[1].x) + static_cast(samples[2].x)) / 2; - const int32_t yMin = (static_cast(samples[0].y) + static_cast(samples[1].y)) / 2; - const int32_t yMax = (static_cast(samples[2].y) + static_cast(samples[3].y)) / 2; + // Targets sit TARGET_MARGIN in from each edge (see getTargetPoint()), not at the screen + // edges themselves - xLow/xHigh/yLow/yHigh are raw samples at those inset positions, not + // at 0/width or 0/height. Extrapolate them out to the true edges so the saved range (which + // lvgl_pointer.h maps onto the full [0, resolution) display range) lines up correctly + // across the whole screen instead of being off by a margin's worth of scale and offset. + const auto width = lv_obj_get_content_width(root); + const auto height = lv_obj_get_content_height(root); + const int32_t xSpan = static_cast(width) - 2 * TARGET_MARGIN; + const int32_t ySpan = static_cast(height) - 2 * TARGET_MARGIN; + + if (xSpan <= 0 || ySpan <= 0) { + lv_label_set_text(titleLabel, "Calibration Failed"); + lv_label_set_text(hintLabel, "Screen too small. Tap to close."); + lv_obj_add_flag(target, LV_OBJ_FLAG_HIDDEN); + setResult(Result::Error); + return; + } + + const int32_t xMin = xLow - (xHigh - xLow) * TARGET_MARGIN / xSpan; + const int32_t xMax = xHigh + (xHigh - xLow) * TARGET_MARGIN / xSpan; + const int32_t yMin = yLow - (yHigh - yLow) * TARGET_MARGIN / ySpan; + const int32_t yMax = yHigh + (yHigh - yLow) * TARGET_MARGIN / ySpan; settings::touch::TouchCalibrationSettings settings = settings::touch::getDefault(); settings.enabled = true; @@ -87,7 +112,7 @@ class TouchCalibrationApp final : public App { settings.yMin = yMin; settings.yMax = yMax; - if ((xMax - xMin) < MIN_RANGE || (yMax - yMin) < MIN_RANGE || !settings::touch::isValid(settings)) { + if (!settings::touch::isValid(settings)) { lv_label_set_text(titleLabel, "Calibration Failed"); lv_label_set_text(hintLabel, "Range invalid. Tap to close."); lv_obj_add_flag(target, LV_OBJ_FLAG_HIDDEN); @@ -103,6 +128,20 @@ class TouchCalibrationApp final : public App { return; } + LvglPointerCalibration calibration = { + .x_min = xMin, + .x_max = xMax, + .y_min = yMin, + .y_max = yMax, + }; + lvgl_lock(); + auto* indev = lvgl_pointer_get_default(); + if (indev != nullptr) { + lvgl_pointer_set_calibration(indev, &calibration); + } + lvgl_unlock(); + calibrationApplied = true; + LOG_I(TAG, "Saved calibration x=[%d, %d] y=[%d, %d]", xMin, xMax, yMin, yMax); lv_label_set_text(titleLabel, "Calibration Complete"); lv_label_set_text(hintLabel, "Touch anywhere to continue."); @@ -141,14 +180,36 @@ public: void onCreate(AppContext& app) override { (void)app; - settings::touch::setRuntimeCalibrationEnabled(false); - settings::touch::invalidateCache(); + // Clear any active calibration so the taps sampled below are raw, uncalibrated coordinates. + lvgl_lock(); + auto* indev = lvgl_pointer_get_default(); + if (indev != nullptr) { + lvgl_pointer_set_calibration(indev, nullptr); + } + lvgl_unlock(); } void onDestroy(AppContext& app) override { (void)app; - settings::touch::setRuntimeCalibrationEnabled(true); - settings::touch::invalidateCache(); + // finishCalibration() already applied a new calibration on success. On cancel/failure, + // restore whatever calibration was on disk before onCreate() cleared it above. + if (calibrationApplied) { + return; + } + + settings::touch::TouchCalibrationSettings settings; + lvgl_lock(); + auto* indev = lvgl_pointer_get_default(); + if (indev != nullptr && settings::touch::load(settings) && settings.enabled && settings::touch::isValid(settings)) { + LvglPointerCalibration calibration = { + .x_min = settings.xMin, + .x_max = settings.xMax, + .y_min = settings.yMin, + .y_max = settings.yMax, + }; + lvgl_pointer_set_calibration(indev, &calibration); + } + lvgl_unlock(); } void onShow(AppContext& app, lv_obj_t* parent) override { @@ -196,9 +257,11 @@ public: extern const AppManifest manifest = { .appId = "TouchCalibration", .appName = "Touch Calibration", - .appCategory = Category::System, - .appFlags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden, + .appCategory = Category::Settings, + .appFlags = AppManifest::Flags::HideStatusBar, .createApp = create }; } // namespace tt::app::touchcalibration + +#endif // defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index f1ee46ba7..601114be9 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -8,11 +8,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -71,6 +73,24 @@ void attachDevices() { } } + // Apply touch calibration (kernel POINTER_TYPE model only - see tactility/lvgl_pointer.h) + LOG_I(TAG, "Apply touch calibration"); + auto touch_calibration_settings = settings::touch::loadOrGetDefault(); + if (touch_calibration_settings.enabled && settings::touch::isValid(touch_calibration_settings)) { + auto* pointer_indev = lvgl_pointer_get_default(); + if (pointer_indev != nullptr) { + struct LvglPointerCalibration calibration = { + .x_min = touch_calibration_settings.xMin, + .x_max = touch_calibration_settings.xMax, + .y_min = touch_calibration_settings.yMin, + .y_max = touch_calibration_settings.yMax, + }; + if (lvgl_pointer_set_calibration(pointer_indev, &calibration) != ERROR_NONE) { + LOG_E(TAG, "Failed to apply saved touch calibration"); + } + } + } + // Start keyboards LOG_I(TAG, "Start keyboards"); auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); diff --git a/Tactility/Source/settings/TouchCalibrationSettings.cpp b/Tactility/Source/settings/TouchCalibrationSettings.cpp index dab64f86b..d83f742c4 100644 --- a/Tactility/Source/settings/TouchCalibrationSettings.cpp +++ b/Tactility/Source/settings/TouchCalibrationSettings.cpp @@ -2,10 +2,8 @@ #include #include -#include #include -#include #include #include #include @@ -24,11 +22,6 @@ constexpr auto* SETTINGS_KEY_X_MAX = "xMax"; constexpr auto* SETTINGS_KEY_Y_MIN = "yMin"; constexpr auto* SETTINGS_KEY_Y_MAX = "yMax"; -static bool runtimeCalibrationEnabled = true; -static bool cacheInitialized = false; -static TouchCalibrationSettings cachedSettings; -static tt::Mutex cacheMutex; - static bool toBool(const std::string& value) { return value == "1" || value == "true" || value == "True"; } @@ -127,62 +120,7 @@ bool save(const TouchCalibrationSettings& settings) { return false; } - if (!file::savePropertiesFile(settings_path, map)) { - return false; - } - - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - cachedSettings = settings; - cacheInitialized = true; - return true; -} - -TouchCalibrationSettings getActive() { - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - if (!cacheInitialized) { - cachedSettings = loadOrGetDefault(); - cacheInitialized = true; - } - if (!runtimeCalibrationEnabled) { - auto disabled = cachedSettings; - disabled.enabled = false; - return disabled; - } - return cachedSettings; -} - -void setRuntimeCalibrationEnabled(bool enabled) { - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - runtimeCalibrationEnabled = enabled; -} - -void invalidateCache() { - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - cacheInitialized = false; -} - -bool applyCalibration(const TouchCalibrationSettings& settings, uint16_t xMax, uint16_t yMax, uint16_t& x, uint16_t& y) { - if (!settings.enabled || !isValid(settings)) { - return false; - } - - const int32_t in_x = static_cast(x); - const int32_t in_y = static_cast(y); - - const int64_t mapped_x = (static_cast(in_x) - static_cast(settings.xMin)) * - static_cast(xMax) / - (static_cast(settings.xMax) - static_cast(settings.xMin)); - const int64_t mapped_y = (static_cast(in_y) - static_cast(settings.yMin)) * - static_cast(yMax) / - (static_cast(settings.yMax) - static_cast(settings.yMin)); - - x = static_cast(std::clamp(mapped_x, 0, static_cast(xMax))); - y = static_cast(std::clamp(mapped_y, 0, static_cast(yMax))); - return true; + return file::savePropertiesFile(settings_path, map); } } // namespace tt::settings::touch diff --git a/device.py b/device.py index 570add48a..c3aa1f232 100644 --- a/device.py +++ b/device.py @@ -62,20 +62,19 @@ def has_group(properties: dict, group: str): prefix = f"{group}." return any(key.startswith(prefix) for key in properties) -def get_property_or_exit(properties: dict, group: str, key: str): - full_key = f"{group}.{key}" - if full_key not in properties: - exit_with_error(f"Device properties does not contain key: {full_key}") - return properties[full_key] +def get_property_or_exit(properties: dict, key: str): + if key not in properties: + exit_with_error(f"Device properties does not contain key: {key}") + return properties[key] -def get_property_or_default(properties: dict, group: str, key: str, default): - return properties.get(f"{group}.{key}", default) +def get_property_or_default(properties: dict, key: str, default): + return properties.get(key, default) -def get_property_or_none(properties: dict, group: str, key: str): - return get_property_or_default(properties, group, key, None) +def get_property_or_none(properties: dict, key: str): + return get_property_or_default(properties, key, None) -def get_boolean_property_or_false(properties: dict, group: str, key: str): - return properties.get(f"{group}.{key}") == "true" +def get_boolean_property_or_false(properties: dict, key: str): + return properties.get(key) == "true" def safe_int(value: str, error_message: str): try: @@ -95,13 +94,13 @@ def write_defaults(output_file): output_file.write(default_properties) def get_user_data_location(device_properties: dict): - user_data_location = get_property_or_exit(device_properties, "storage", "userDataLocation") + user_data_location = get_property_or_exit(device_properties, "storage.userDataLocation") if user_data_location not in ("SD", "Internal"): exit_with_error(f"storage.userDataLocation must be 'SD' or 'Internal', but was: '{user_data_location}'") return user_data_location def write_partition_table(output_file, device_properties: dict, is_dev: bool): - flash_size = get_property_or_exit(device_properties, "hardware", "flashSize") + flash_size = get_property_or_exit(device_properties, "hardware.flashSize") if not flash_size.endswith("MB"): exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)") flash_size_number = flash_size[:-2] @@ -122,8 +121,8 @@ def write_partition_table(output_file, device_properties: dict, is_dev: bool): def write_tactility_variables(output_file, device_properties: dict, device_id: str): # Board and vendor - board_vendor = get_property_or_exit(device_properties, "general", "vendor").replace("\"", "\\\"") - board_name = get_property_or_exit(device_properties, "general", "name").replace("\"", "\\\"") + board_vendor = get_property_or_exit(device_properties, "general.vendor").replace("\"", "\\\"") + board_name = get_property_or_exit(device_properties, "general.name").replace("\"", "\\\"") if board_name == board_vendor or board_vendor == "": output_file.write(f"CONFIG_TT_DEVICE_NAME=\"{board_name}\"\n") else: @@ -134,10 +133,10 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s if device_id == "lilygo-tdeck": output_file.write("CONFIG_TT_TDECK_WORKAROUND=y\n") # Launcher app id - launcher_app_id = get_property_or_exit(device_properties, "apps", "launcherAppId").replace("\"", "\\\"") + launcher_app_id = get_property_or_exit(device_properties, "apps.launcherAppId").replace("\"", "\\\"") output_file.write(f"CONFIG_TT_LAUNCHER_APP_ID=\"{launcher_app_id}\"\n") # Auto start app id - auto_start_app_id = get_property_or_none(device_properties, "apps", "autoStartAppId") + auto_start_app_id = get_property_or_none(device_properties, "apps.autoStartAppId") if auto_start_app_id is not None: safe_auto_start_app_id = auto_start_app_id.replace("\"", "\\\"") output_file.write(f"CONFIG_TT_AUTO_START_APP_ID=\"{safe_auto_start_app_id}\"\n") @@ -148,7 +147,7 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s output_file.write("CONFIG_TT_USER_DATA_LOCATION_INTERNAL=y\n") def write_core_variables(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() output_file.write("# Target\n") output_file.write(f"CONFIG_IDF_TARGET=\"{idf_target}\"\n") output_file.write("# CPU\n") @@ -171,28 +170,28 @@ def write_core_variables(output_file, device_properties: dict): output_file.write("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y\n") output_file.write("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=y\n") # Usage of tt::hal can be disabled to simplify dependency wiring (device depends on TactilityKernel instead of Tactility) - use_deprecated_hal = get_property_or_none(device_properties, "dependencies", "useDeprecatedHal") + use_deprecated_hal = get_property_or_none(device_properties, "dependencies.useDeprecatedHal") if use_deprecated_hal is None or use_deprecated_hal.lower() == "true": output_file.write("CONFIG_TT_USE_DEPRECATED_HAL=y\n") else: output_file.write("CONFIG_TT_USE_DEPRECATED_HAL=n\n") def write_flash_variables(output_file, device_properties: dict): - flash_size = get_property_or_exit(device_properties, "hardware", "flashSize") + flash_size = get_property_or_exit(device_properties, "hardware.flashSize") if not flash_size.endswith("MB"): exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)") output_file.write("# Flash\n") flash_size_number = flash_size[:-2] output_file.write(f"CONFIG_ESPTOOLPY_FLASHSIZE_{flash_size_number}MB=y\n") - flash_mode = get_property_or_default(device_properties, "hardware", "flashMode", 'QIO') + flash_mode = get_property_or_default(device_properties, "hardware.flashMode", 'QIO') output_file.write(f"CONFIG_FLASHMODE_{flash_mode}=y\n") - esptool_flash_freq = get_property_or_none(device_properties, "hardware", "esptoolFlashFreq") + esptool_flash_freq = get_property_or_none(device_properties, "hardware.esptoolFlashFreq") if esptool_flash_freq is not None: output_file.write(f"CONFIG_ESPTOOLPY_FLASHFREQ_{esptool_flash_freq}=y\n") def write_spiram_variables(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() - has_spiram = get_property_or_exit(device_properties, "hardware", "spiRam") + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() + has_spiram = get_property_or_exit(device_properties, "hardware.spiRam") if has_spiram != "true": return output_file.write("# SPIRAM\n") @@ -201,7 +200,7 @@ def write_spiram_variables(output_file, device_properties: dict): # Enable output_file.write("CONFIG_SPIRAM=y\n") output_file.write(f"CONFIG_{idf_target.upper()}_SPIRAM_SUPPORT=y\n") - mode = get_property_or_exit(device_properties, "hardware", "spiRamMode") + mode = get_property_or_exit(device_properties, "hardware.spiRamMode") if mode == "OPI": mode = "OCT" # Mode @@ -209,7 +208,7 @@ def write_spiram_variables(output_file, device_properties: dict): output_file.write(f"CONFIG_SPIRAM_MODE_{mode}=y\n") else: output_file.write("CONFIG_SPIRAM_TYPE_AUTO=y\n") - speed = get_property_or_exit(device_properties, "hardware", "spiRamSpeed") + speed = get_property_or_exit(device_properties, "hardware.spiRamSpeed") # Speed output_file.write(f"CONFIG_SPIRAM_SPEED_{speed}=y\n") output_file.write(f"CONFIG_SPIRAM_SPEED={speed}\n") @@ -223,7 +222,7 @@ def write_spiram_variables(output_file, device_properties: dict): output_file.write("CONFIG_SPIRAM_XIP_FROM_PSRAM=y\n") def write_performance_improvements(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() if idf_target == "esp32s3": output_file.write("# Performance improvement: Fixes glitches in the RGB display driver when rendering new screens/apps\n") output_file.write("CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y\n") @@ -246,16 +245,16 @@ def write_lvgl_variables(output_file, device_properties: dict): write_lvgl_variable_placeholders(output_file) return # LVGL DPI overrides the real DPI settings - dpi_text = get_property_or_none(device_properties, "lvgl", "dpi") + dpi_text = get_property_or_none(device_properties, "lvgl.dpi") if dpi_text is None: - dpi_text = get_property_or_exit(device_properties, "display", "dpi") + dpi_text = get_property_or_exit(device_properties, "display.dpi") dpi = safe_int(dpi_text, f"DPI must be an integer, but was: '{dpi_text}'") output_file.write(f"CONFIG_LV_DPI_DEF={dpi}\n") - color_depth = get_property_or_exit(device_properties, "lvgl", "colorDepth") + color_depth = get_property_or_exit(device_properties, "lvgl.colorDepth") output_file.write(f"CONFIG_LV_COLOR_DEPTH={color_depth}\n") output_file.write(f"CONFIG_LV_COLOR_DEPTH_{color_depth}=y\n") output_file.write("CONFIG_LV_DISP_DEF_REFR_PERIOD=10\n") - theme = get_property_or_default(device_properties, "lvgl", "theme", "DefaultDark") + theme = get_property_or_default(device_properties, "lvgl.theme", "DefaultDark") if theme == "DefaultDark": output_file.write("CONFIG_LV_THEME_DEFAULT_DARK=y\n") elif theme == "DefaultLight": @@ -264,7 +263,7 @@ def write_lvgl_variables(output_file, device_properties: dict): output_file.write("CONFIG_LV_USE_THEME_MONO=y\n") else: exit_with_error(f"Unknown theme: {theme}") - font_height_text = get_property_or_default(device_properties, "lvgl", "fontSize", "14") + font_height_text = get_property_or_default(device_properties, "lvgl.fontSize", "14") font_height = safe_int(font_height_text, f"Font height must be an integer, but was: '{font_height_text}'") if font_height <= 12: output_file.write("CONFIG_LV_FONT_MONTSERRAT_8=y\n") @@ -333,14 +332,23 @@ def write_lvgl_variables(output_file, device_properties: dict): output_file.write("CONFIG_TT_LVGL_LAUNCHER_ICON_SIZE=72\n") output_file.write("CONFIG_TT_LVGL_SHARED_ICON_SIZE=32\n") +def write_touch_calibration_variables(output_file, device_properties: dict): + calibration_supported = get_property_or_none(device_properties, "touch.calibrationSupported") + if calibration_supported is not None and calibration_supported.lower() == "true": + output_file.write("# Touch calibration\n") + output_file.write("CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED=y\n") + calibration_required = get_property_or_none(device_properties, "touch.calibrationRequired") + if calibration_required is not None and calibration_required.lower() == "true": + output_file.write("CONFIG_TT_TOUCH_CALIBRATION_REQUIRED=y\n") + def write_usb_variables(output_file, device_properties: dict): - has_tiny_usb = get_boolean_property_or_false(device_properties, "hardware", "tinyUsb") + has_tiny_usb = get_boolean_property_or_false(device_properties, "hardware.tinyUsb") if has_tiny_usb: output_file.write("# TinyUSB\n") output_file.write("CONFIG_TINYUSB_MSC_ENABLED=y\n") output_file.write("CONFIG_TINYUSB_MSC_MOUNT_PATH=\"/sdcard\"\n") - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() if idf_target == "esp32p4": # P4 has two USB-DWC controllers (HS/UTMI and FS/FSLS). esp_tinyusb defaults to # RHPORT_HS (UTMI), which is the same controller claimed by usbhost0's @@ -349,8 +357,8 @@ def write_usb_variables(output_file, device_properties: dict): output_file.write("CONFIG_TINYUSB_RHPORT_FS=y\n") def write_bluetooth_variables(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() - has_bluetooth = get_boolean_property_or_false(device_properties, "hardware", "bluetooth") + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() + has_bluetooth = get_boolean_property_or_false(device_properties, "hardware.bluetooth") if has_bluetooth: output_file.write("# Bluetooth (NimBLE)\n") output_file.write("CONFIG_BT_ENABLED=y\n") @@ -365,7 +373,7 @@ def write_bluetooth_variables(output_file, device_properties: dict): # and does not suffer from the same fragmentation — enabling reliable re-init. # Also frees significant internal RAM on memory-constrained targets (e.g. S3). # Dependency: CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC (set by write_spiram_variables). - has_spiram = get_boolean_property_or_false(device_properties, "hardware", "spiRam") + has_spiram = get_boolean_property_or_false(device_properties, "hardware.spiRam") if has_spiram: output_file.write("CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y\n") # Expand NimBLE's GAP device name buffer to match BLE_DEVICE_NAME_MAX. @@ -382,7 +390,7 @@ def write_bluetooth_variables(output_file, device_properties: dict): output_file.write("CONFIG_BT_NIMBLE_NVS_PERSIST=y\n") def write_usbhost_variables(output_file, device_properties: dict): - has_usbhost = get_boolean_property_or_false(device_properties, "hardware", "usbHostEnabled") + has_usbhost = get_boolean_property_or_false(device_properties, "hardware.usbHostEnabled") if has_usbhost: output_file.write("# USB Host\n") output_file.write("CONFIG_FATFS_VOLUME_COUNT=6\n") @@ -416,6 +424,7 @@ def write_properties(output_file, device_properties: dict, device_id: str, is_de write_usbhost_variables(output_file, device_properties) write_custom_sdkconfig(output_file, device_properties) write_lvgl_variables(output_file, device_properties) + write_touch_calibration_variables(output_file, device_properties) def get_current_sdkconfig_target(sdkconfig_path: str): if not os.path.isfile(sdkconfig_path): @@ -448,7 +457,7 @@ def main(device_id: str, is_dev: bool): output_file_path = "sdkconfig" # Clean build dirs if target changes device_properties = read_device_properties(device_id) - new_target = get_property_or_exit(device_properties, "hardware", "target").lower() + new_target = get_property_or_exit(device_properties, "hardware.target").lower() sdkconfig_target = get_current_sdkconfig_target(output_file_path) clean_build_dirs_on_platform_change(sdkconfig_target, new_target) if os.path.isfile(output_file_path):