From 50007ea9ed29d7dfca20908f18610034a4255f94 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 30 Aug 2025 21:54:55 +0200 Subject: [PATCH] Merge develop into main (#307) ## Launcher - Launcher now has optional power button to show - Launcher layout improvements - Removed text from Launcher (translations with larger amounts of text did not fit small device formats) ## T-Lora Pager - Implement power off (created `BQ25896` driver) - Implemented haptics (created `DRV2605` driver project) and buzz on startup - Reversed scroll wheel - Created `TloraEncoder` device and relocated its logic from `TloraKeyboard` - Disabled SPIRAM test to save 0.5 seconds of boot time (current boot time is very slow) - Update `ST7796` esp_lcd driver to v1.3.4 - Fixed keyboard bug: delete queue in destructor - Fixed driver dependencies: Avoiding usage of global static shared_ptr. Properly constructor-inject everywhere, or use `tt::hal::findDevices()` - I2C configuration is now immutable (you cannot disable it anymore from the I2C Settings app, as it would break crucial drivers) - Renamed I2C and UART subsystems to "Internal" ## Drivers - On/off interface added to `PowerDevice` - Created `tt::hal::Configuration.createDevices`, which is intended to replace all custom create calls for display, keyboard, etc. - Created `EncoderDevice` as a `Device` subtype ## Other Improvements - Changed `findDevices(type, function)` into a templatized function. - Improved SD card mounting ## Fixes - Show Screenshot app again - Fixed Statusbar: some updates were allowed to time out and fail silently: When the Statusbar service would do a state update, the LVGL statusbar would never get updated due to this timeout. - Fixed memory leaks in all `createSdCard()` functions (in most board implementations) --- App/idf_component.yml | 2 +- .../CYD-2432S024C/Source/hal/YellowSdCard.cpp | 12 +- Boards/CYD-2432S032C/Source/hal/CydSdCard.cpp | 8 +- .../Source/hal/YellowSdCard.cpp | 12 +- .../Source/hal/CrowPanelSdCard.cpp | 12 +- .../Source/hal/CrowPanelSdCard.cpp | 12 +- .../Source/hal/CrowPanelSdCard.cpp | 8 +- .../Source/hal/CrowPanelSdCard.cpp | 10 +- .../Source/hal/CrowPanelSdCard.cpp | 10 +- .../Source/hal/CrowPanelSdCard.cpp | 8 +- Boards/LilygoTLoraPager/CMakeLists.txt | 2 +- Boards/LilygoTLoraPager/Source/Init.cpp | 43 ++--- .../Source/LilygoTloraPager.cpp | 62 +++++-- .../Source/hal/TpagerEncoder.cpp | 141 +++++++++++++++ .../Source/hal/TpagerEncoder.h | 29 +++ .../Source/hal/TpagerKeyboard.cpp | 165 ++---------------- .../Source/hal/TpagerKeyboard.h | 20 ++- .../Source/hal/TpagerPower.cpp | 41 ++--- .../LilygoTLoraPager/Source/hal/TpagerPower.h | 7 +- .../Source/hal/TpagerSdCard.cpp | 22 ++- Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp | 16 +- .../M5stackCore2/Source/hal/Core2SdCard.cpp | 10 +- .../M5stackCoreS3/Source/hal/CoreS3SdCard.cpp | 14 +- Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp | 10 +- .../Source/hal/WaveshareSdCard.cpp | 8 +- Data/system/app/Launcher/i18n/en-GB.i18n | 3 - Data/system/app/Launcher/i18n/en-US.i18n | 3 - Data/system/app/Launcher/i18n/fr-FR.i18n | 3 - Data/system/app/Launcher/i18n/nl-BE.i18n | 3 - Data/system/app/Launcher/i18n/nl-NL.i18n | 3 - Documentation/ideas.md | 2 +- Drivers/BQ25896/CMakeLists.txt | 5 + Drivers/BQ25896/README.md | 5 + Drivers/BQ25896/Source/Bq25896.cpp | 13 ++ Drivers/BQ25896/Source/Bq25896.h | 22 +++ Drivers/BQ27220/Source/Bq27220.cpp | 2 +- Drivers/BQ27220/Source/Bq27220.h | 7 +- Drivers/DRV2605/CMakeLists.txt | 5 + Drivers/DRV2605/README.md | 5 + Drivers/DRV2605/Source/Drv2605.cpp | 82 +++++++++ Drivers/DRV2605/Source/Drv2605.h | 94 ++++++++++ Drivers/ST7796/README.md | 1 + Drivers/ST7796/Source/St7796Display.cpp | 26 +-- Drivers/ST7796/Source/St7796Display.h | 1 - Drivers/TCA8418/Source/Tca8418.h | 2 - .../Include/Tactility/hal/Configuration.h | 10 ++ Tactility/Include/Tactility/hal/Device.h | 12 +- .../Tactility/hal/encoder/EncoderDevice.h | 23 +++ .../Tactility/hal/keyboard/KeyboardDevice.h | 2 + .../Include/Tactility/hal/power/PowerDevice.h | 3 + .../Tactility/hal/sdcard/SdCardMounting.h | 7 + Tactility/Source/Tactility.cpp | 14 +- Tactility/Source/TactilityHeadless.cpp | 5 +- Tactility/Source/app/boot/Boot.cpp | 7 +- Tactility/Source/app/launcher/Launcher.cpp | 138 ++++++++------- .../Source/app/screenshot/Screenshot.cpp | 2 +- Tactility/Source/hal/Device.cpp | 9 - Tactility/Source/hal/Hal.cpp | 44 +++-- Tactility/Source/hal/i2c/I2cDevice.cpp | 14 +- .../Source/hal/sdcard/SdCardMounting.cpp | 40 +++++ Tactility/Source/hal/usb/Usb.cpp | 33 ++-- Tactility/Source/lvgl/Lvgl.cpp | 141 +++++++++------ Tactility/Source/lvgl/Statusbar.cpp | 12 +- Tactility/Source/service/sdcard/Sdcard.cpp | 23 ++- .../Source/service/statusbar/Statusbar.cpp | 19 +- TactilityC/Source/tt_hal_device.cpp | 2 +- .../Include/Tactility/MessageQueue.h | 1 - Translations/Translations.ods | Bin 13804 -> 13652 bytes sdkconfig.board.lilygo-tlora-pager | 2 + 69 files changed, 957 insertions(+), 577 deletions(-) create mode 100644 Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.cpp create mode 100644 Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.h delete mode 100644 Data/system/app/Launcher/i18n/en-GB.i18n delete mode 100644 Data/system/app/Launcher/i18n/en-US.i18n delete mode 100644 Data/system/app/Launcher/i18n/fr-FR.i18n delete mode 100644 Data/system/app/Launcher/i18n/nl-BE.i18n delete mode 100644 Data/system/app/Launcher/i18n/nl-NL.i18n create mode 100644 Drivers/BQ25896/CMakeLists.txt create mode 100644 Drivers/BQ25896/README.md create mode 100644 Drivers/BQ25896/Source/Bq25896.cpp create mode 100644 Drivers/BQ25896/Source/Bq25896.h create mode 100644 Drivers/DRV2605/CMakeLists.txt create mode 100644 Drivers/DRV2605/README.md create mode 100644 Drivers/DRV2605/Source/Drv2605.cpp create mode 100644 Drivers/DRV2605/Source/Drv2605.h create mode 100644 Tactility/Include/Tactility/hal/encoder/EncoderDevice.h create mode 100644 Tactility/Private/Tactility/hal/sdcard/SdCardMounting.h create mode 100644 Tactility/Source/hal/sdcard/SdCardMounting.cpp diff --git a/App/idf_component.yml b/App/idf_component.yml index fdb96675..f6067125 100644 --- a/App/idf_component.yml +++ b/App/idf_component.yml @@ -13,7 +13,7 @@ dependencies: rules: - if: "target in [esp32s3, esp32p4]" espressif/esp_lcd_st7796: - version: "1.3.2" + version: "1.3.4" espressif/esp_lcd_panel_io_additions: "1.0.1" espressif/esp_tinyusb: version: "1.7.6~1" diff --git a/Boards/CYD-2432S024C/Source/hal/YellowSdCard.cpp b/Boards/CYD-2432S024C/Source/hal/YellowSdCard.cpp index 804958ec..ceb4c53e 100644 --- a/Boards/CYD-2432S024C/Source/hal/YellowSdCard.cpp +++ b/Boards/CYD-2432S024C/Source/hal/YellowSdCard.cpp @@ -4,13 +4,13 @@ #include -#define SDCARD_SPI_HOST SPI3_HOST -#define SDCARD_PIN_CS GPIO_NUM_5 +constexpr auto SDCARD_SPI_HOST = SPI3_HOST; +constexpr auto SDCARD_PIN_CS = GPIO_NUM_5; using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createYellowSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, @@ -21,10 +21,8 @@ std::shared_ptr createYellowSdCard() { SDCARD_SPI_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/CYD-2432S032C/Source/hal/CydSdCard.cpp b/Boards/CYD-2432S032C/Source/hal/CydSdCard.cpp index 38b1e952..3ee10478 100644 --- a/Boards/CYD-2432S032C/Source/hal/CydSdCard.cpp +++ b/Boards/CYD-2432S032C/Source/hal/CydSdCard.cpp @@ -9,7 +9,7 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, @@ -20,10 +20,8 @@ std::shared_ptr createSdCard() { SDCARD_SPI_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/CYD-JC2432W328C/Source/hal/YellowSdCard.cpp b/Boards/CYD-JC2432W328C/Source/hal/YellowSdCard.cpp index b1a2fa20..db09d6a7 100644 --- a/Boards/CYD-JC2432W328C/Source/hal/YellowSdCard.cpp +++ b/Boards/CYD-JC2432W328C/Source/hal/YellowSdCard.cpp @@ -5,13 +5,13 @@ #include #include -#define SDCARD_SPI_HOST SPI3_HOST -#define SDCARD_PIN_CS GPIO_NUM_5 +constexpr auto SDCARD_SPI_HOST = SPI3_HOST; +constexpr auto SDCARD_PIN_CS = GPIO_NUM_5; using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createYellowSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, @@ -22,10 +22,8 @@ std::shared_ptr createYellowSdCard() { SDCARD_SPI_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/ElecrowCrowpanelAdvance28/Source/hal/CrowPanelSdCard.cpp b/Boards/ElecrowCrowpanelAdvance28/Source/hal/CrowPanelSdCard.cpp index af404a9c..caefc81c 100644 --- a/Boards/ElecrowCrowpanelAdvance28/Source/hal/CrowPanelSdCard.cpp +++ b/Boards/ElecrowCrowpanelAdvance28/Source/hal/CrowPanelSdCard.cpp @@ -7,23 +7,21 @@ using tt::hal::sdcard::SpiSdCardDevice; -#define CROWPANEL_SDCARD_PIN_CS GPIO_NUM_7 +constexpr auto CROWPANEL_SDCARD_PIN_CS = GPIO_NUM_7; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( CROWPANEL_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - {}, + std::vector(), SPI3_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/ElecrowCrowpanelAdvance35/Source/hal/CrowPanelSdCard.cpp b/Boards/ElecrowCrowpanelAdvance35/Source/hal/CrowPanelSdCard.cpp index af404a9c..caefc81c 100644 --- a/Boards/ElecrowCrowpanelAdvance35/Source/hal/CrowPanelSdCard.cpp +++ b/Boards/ElecrowCrowpanelAdvance35/Source/hal/CrowPanelSdCard.cpp @@ -7,23 +7,21 @@ using tt::hal::sdcard::SpiSdCardDevice; -#define CROWPANEL_SDCARD_PIN_CS GPIO_NUM_7 +constexpr auto CROWPANEL_SDCARD_PIN_CS = GPIO_NUM_7; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( CROWPANEL_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - {}, + std::vector(), SPI3_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/ElecrowCrowpanelAdvance50/Source/hal/CrowPanelSdCard.cpp b/Boards/ElecrowCrowpanelAdvance50/Source/hal/CrowPanelSdCard.cpp index f6cb6d1d..124e9efc 100644 --- a/Boards/ElecrowCrowpanelAdvance50/Source/hal/CrowPanelSdCard.cpp +++ b/Boards/ElecrowCrowpanelAdvance50/Source/hal/CrowPanelSdCard.cpp @@ -8,7 +8,7 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( // See https://github.com/Elecrow-RD/CrowPanel-Advance-HMI-ESP32-AI-Display/blob/master/5.0/factory_code/factory_code.ino GPIO_NUM_0, // It's actually not connected, but in the demo pin 0 is used GPIO_NUM_NC, @@ -17,9 +17,7 @@ std::shared_ptr createSdCard() { SdCardDevice::MountBehaviour::AtBoot ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/ElecrowCrowpanelBasic28/Source/hal/CrowPanelSdCard.cpp b/Boards/ElecrowCrowpanelBasic28/Source/hal/CrowPanelSdCard.cpp index 0959c55c..826ac9bf 100644 --- a/Boards/ElecrowCrowpanelBasic28/Source/hal/CrowPanelSdCard.cpp +++ b/Boards/ElecrowCrowpanelBasic28/Source/hal/CrowPanelSdCard.cpp @@ -8,20 +8,18 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( GPIO_NUM_5, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - {}, + std::vector(), SPI3_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/ElecrowCrowpanelBasic35/Source/hal/CrowPanelSdCard.cpp b/Boards/ElecrowCrowpanelBasic35/Source/hal/CrowPanelSdCard.cpp index 0274fd3e..2696e05c 100644 --- a/Boards/ElecrowCrowpanelBasic35/Source/hal/CrowPanelSdCard.cpp +++ b/Boards/ElecrowCrowpanelBasic35/Source/hal/CrowPanelSdCard.cpp @@ -6,20 +6,18 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( GPIO_NUM_5, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - {}, + std::vector(), SPI3_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/ElecrowCrowpanelBasic50/Source/hal/CrowPanelSdCard.cpp b/Boards/ElecrowCrowpanelBasic50/Source/hal/CrowPanelSdCard.cpp index 605f3109..cca2eebe 100644 --- a/Boards/ElecrowCrowpanelBasic50/Source/hal/CrowPanelSdCard.cpp +++ b/Boards/ElecrowCrowpanelBasic50/Source/hal/CrowPanelSdCard.cpp @@ -8,7 +8,7 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( GPIO_NUM_10, GPIO_NUM_NC, GPIO_NUM_NC, @@ -16,9 +16,7 @@ std::shared_ptr createSdCard() { SdCardDevice::MountBehaviour::AtBoot ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/LilygoTLoraPager/CMakeLists.txt b/Boards/LilygoTLoraPager/CMakeLists.txt index 5746b57d..02e89f02 100644 --- a/Boards/LilygoTLoraPager/CMakeLists.txt +++ b/Boards/LilygoTLoraPager/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lcd ST7796 BQ27220 TCA8418 PwmBacklight driver esp_adc + REQUIRES Tactility esp_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 PwmBacklight driver esp_adc ) diff --git a/Boards/LilygoTLoraPager/Source/Init.cpp b/Boards/LilygoTLoraPager/Source/Init.cpp index 72a70096..cf1c7ce7 100644 --- a/Boards/LilygoTLoraPager/Source/Init.cpp +++ b/Boards/LilygoTLoraPager/Source/Init.cpp @@ -1,22 +1,14 @@ -#include "PwmBacklight.h" -#include "Tactility/kernel/SystemEvents.h" -#include "Tactility/service/gps/GpsService.h" - +#include #include +#include +#include #include #include -#include -#include +#include -#define TAG "tpager" - -// Power on -#define TDECK_POWERON_GPIO GPIO_NUM_10 - -std::shared_ptr bq27220; -std::shared_ptr tca8418; +constexpr auto* TAG = "TLoraPager"; bool tpagerInit() { ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START); @@ -29,21 +21,29 @@ bool tpagerInit() { return false; } - bq27220 = std::make_shared(I2C_NUM_0); - tt::hal::registerDevice(bq27220); + tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](auto) { + tt::hal::findDevices([](auto device) { + if (device->getName() == "BQ27220") { + auto bq27220 = std::reinterpret_pointer_cast(device); + if (bq27220 != nullptr) { + bq27220->configureCapacity(1500, 1500); + return false; + } + } - tca8418 = std::make_shared(I2C_NUM_0); - tt::hal::registerDevice(tca8418); - - tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { - bq27220->configureCapacity(1500, 1500); + return true; + }); auto gps_service = tt::service::gps::findGpsService(); if (gps_service != nullptr) { std::vector gps_configurations; gps_service->getGpsConfigurations(gps_configurations); if (gps_configurations.empty()) { - if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "Grove", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) { + if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration { + .uartName = "Internal", + .baudRate = 38400, + .model = tt::hal::gps::GpsModel::UBLOX10 + })) { TT_LOG_I(TAG, "Configured internal GPS"); } else { TT_LOG_E(TAG, "Failed to configure internal GPS"); @@ -51,5 +51,6 @@ bool tpagerInit() { } } }); + return true; } diff --git a/Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp b/Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp index f37e054c..3394eb0b 100644 --- a/Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp +++ b/Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp @@ -1,10 +1,13 @@ #include "Tactility/lvgl/LvglSync.h" #include "hal/TpagerDisplay.h" +#include "hal/TpagerEncoder.h" #include "hal/TpagerDisplayConstants.h" #include "hal/TpagerKeyboard.h" #include "hal/TpagerPower.h" #include "hal/TpagerSdCard.h" +#include +#include #include #define TPAGER_SPI_TRANSFER_SIZE_LIMIT (TPAGER_LCD_HORIZONTAL_RESOLUTION * TPAGER_LCD_SPI_TRANSFER_HEIGHT * (LV_COLOR_DEPTH / 8)) @@ -13,18 +16,35 @@ bool tpagerInit(); using namespace tt::hal; +DeviceVector createDevices() { + auto bq27220 = std::make_shared(I2C_NUM_0); + auto power = std::make_shared(bq27220); + + auto tca8418 = std::make_shared(I2C_NUM_0); + auto keyboard = std::make_shared(tca8418); + + return std::vector> { + tca8418, + std::make_shared(I2C_NUM_0), + bq27220, + std::make_shared(I2C_NUM_0), + power, + createTpagerSdCard(), + createDisplay(), + keyboard, + std::make_shared() + }; +} + extern const Configuration lilygo_tlora_pager = { .initBoot = tpagerInit, - .createDisplay = createDisplay, - .createKeyboard = createKeyboard, - .sdcard = createTpagerSdCard(), - .power = tpager_get_power, + .createDevices = createDevices, .i2c = { i2c::Configuration { - .name = "Shared", + .name = "Internal", .port = I2C_NUM_0, .initMode = i2c::InitMode::ByTactility, - .isMutable = true, + .isMutable = false, .config = (i2c_config_t) { .mode = I2C_MODE_MASTER, .sda_io_num = GPIO_NUM_3, @@ -41,24 +61,28 @@ extern const Configuration lilygo_tlora_pager = { .spi {spi::Configuration { .device = SPI2_HOST, .dma = SPI_DMA_CH_AUTO, - .config = {.mosi_io_num = GPIO_NUM_34, .miso_io_num = GPIO_NUM_33, .sclk_io_num = GPIO_NUM_35, - .quadwp_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported - .quadhd_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported - .data4_io_num = GPIO_NUM_NC, - .data5_io_num = GPIO_NUM_NC, - .data6_io_num = GPIO_NUM_NC, - .data7_io_num = GPIO_NUM_NC, - .data_io_default_level = false, - .max_transfer_sz = TPAGER_SPI_TRANSFER_SIZE_LIMIT, - .flags = 0, - .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, - .intr_flags = 0}, + .config = { + .mosi_io_num = GPIO_NUM_34, + .miso_io_num = GPIO_NUM_33, + .sclk_io_num = GPIO_NUM_35, + .quadwp_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported + .quadhd_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported + .data4_io_num = GPIO_NUM_NC, + .data5_io_num = GPIO_NUM_NC, + .data6_io_num = GPIO_NUM_NC, + .data7_io_num = GPIO_NUM_NC, + .data_io_default_level = false, + .max_transfer_sz = TPAGER_SPI_TRANSFER_SIZE_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 + }, .initMode = spi::InitMode::ByTactility, .isMutable = false, .lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display }}, .uart {uart::Configuration { - .name = "Grove", + .name = "Internal", .port = UART_NUM_1, .rxPin = GPIO_NUM_4, .txPin = GPIO_NUM_12, diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.cpp new file mode 100644 index 00000000..25514564 --- /dev/null +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.cpp @@ -0,0 +1,141 @@ +#include "TpagerEncoder.h" + +#include +#include + +constexpr auto* TAG = "TpagerEncoder"; +constexpr auto ENCODER_A = GPIO_NUM_40; +constexpr auto ENCODER_B = GPIO_NUM_41; +constexpr auto ENCODER_ENTER = GPIO_NUM_7; + +void TpagerEncoder::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { + TpagerEncoder* encoder = static_cast(lv_indev_get_user_data(indev)); + constexpr int enter_filter_threshold = 2; + static int enter_filter = 0; + constexpr int pulses_click = 4; + static int pulses_prev = 0; + + // Defaults + data->enc_diff = 0; + data->state = LV_INDEV_STATE_RELEASED; + + int pulses = encoder->getEncoderPulses(); + int pulse_diff = (pulses - pulses_prev); + if ((pulse_diff > pulses_click) || (pulse_diff < -pulses_click)) { + data->enc_diff = pulse_diff / pulses_click; + pulses_prev = pulses; + } + + bool enter = !gpio_get_level(ENCODER_ENTER); + if (enter && (enter_filter < enter_filter_threshold)) { + enter_filter++; + } + if (!enter && (enter_filter > 0)) { + enter_filter--; + } + + if (enter_filter == enter_filter_threshold) { + data->state = LV_INDEV_STATE_PRESSED; + } +} + +void TpagerEncoder::initEncoder() { + constexpr int LOW_LIMIT = -127; + constexpr int HIGH_LIMIT = 126; + + // Accum. count makes it that over- and underflows are automatically compensated. + // Prerequisite: watchpoints at low and high limit + pcnt_unit_config_t unit_config = { + .low_limit = LOW_LIMIT, + .high_limit = HIGH_LIMIT, + .flags = {.accum_count = 1}, + }; + + if (pcnt_new_unit(&unit_config, &encPcntUnit) != ESP_OK) { + TT_LOG_E(TAG, "Pulsecounter intialization failed"); + } + + pcnt_glitch_filter_config_t filter_config = { + .max_glitch_ns = 1000, + }; + + if (pcnt_unit_set_glitch_filter(encPcntUnit, &filter_config) != ESP_OK) { + TT_LOG_E(TAG, "Pulsecounter glitch filter config failed"); + } + + pcnt_chan_config_t chan_1_config = { + .edge_gpio_num = ENCODER_B, + .level_gpio_num = ENCODER_A, + }; + + pcnt_chan_config_t chan_2_config = { + .edge_gpio_num = ENCODER_A, + .level_gpio_num = ENCODER_B, + }; + + pcnt_channel_handle_t pcnt_chan_1 = nullptr; + pcnt_channel_handle_t pcnt_chan_2 = nullptr; + + if ((pcnt_new_channel(encPcntUnit, &chan_1_config, &pcnt_chan_1) != ESP_OK) || + (pcnt_new_channel(encPcntUnit, &chan_2_config, &pcnt_chan_2) != ESP_OK)) { + TT_LOG_E(TAG, "Pulsecounter channel config failed"); + } + + // Second argument is rising edge, third argument is falling edge + if ((pcnt_channel_set_edge_action(pcnt_chan_1, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE) != ESP_OK) || + (pcnt_channel_set_edge_action(pcnt_chan_2, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE) != ESP_OK)) { + TT_LOG_E(TAG, "Pulsecounter edge action config failed"); + } + + // Second argument is low level, third argument is high level + if ((pcnt_channel_set_level_action(pcnt_chan_1, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE) != ESP_OK) || + (pcnt_channel_set_level_action(pcnt_chan_2, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE) != ESP_OK)) { + TT_LOG_E(TAG, "Pulsecounter level action config failed"); + } + + if ((pcnt_unit_add_watch_point(encPcntUnit, LOW_LIMIT) != ESP_OK) || + (pcnt_unit_add_watch_point(encPcntUnit, HIGH_LIMIT) != ESP_OK)) { + TT_LOG_E(TAG, "Pulsecounter watch point config failed"); + } + + if (pcnt_unit_enable(encPcntUnit) != ESP_OK) { + TT_LOG_E(TAG, "Pulsecounter could not be enabled"); + } + + if (pcnt_unit_clear_count(encPcntUnit) != ESP_OK) { + TT_LOG_E(TAG, "Pulsecounter could not be cleared"); + } + + if (pcnt_unit_start(encPcntUnit) != ESP_OK) { + TT_LOG_E(TAG, "Pulsecounter could not be started"); + } +} + +int TpagerEncoder::getEncoderPulses() { + int pulses = 0; + pcnt_unit_get_count(encPcntUnit, &pulses); + return pulses; +} + + +bool TpagerEncoder::startLvgl(lv_display_t* display) { + initEncoder(); + + gpio_input_enable(ENCODER_ENTER); + + encHandle = lv_indev_create(); + + lv_indev_set_type(encHandle, LV_INDEV_TYPE_ENCODER); + lv_indev_set_read_cb(encHandle, &readCallback); + lv_indev_set_display(encHandle, display); + lv_indev_set_user_data(encHandle, this); + + return true; +} + +bool TpagerEncoder::stopLvgl() { + lv_indev_delete(encHandle); + encHandle = nullptr; + + return true; +} diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.h b/Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.h new file mode 100644 index 00000000..52699e42 --- /dev/null +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +class TpagerEncoder final : public tt::hal::encoder::EncoderDevice { + + lv_indev_t* _Nullable encHandle = nullptr; + pcnt_unit_handle_t encPcntUnit = nullptr; + + void initEncoder(); + + static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); + +public: + + TpagerEncoder() {} + ~TpagerEncoder() {} + + std::string getName() const override { return "T-Lora Pager Encoder"; } + std::string getDescription() const override { return "The encoder wheel next to the display"; } + + bool startLvgl(lv_display_t* display) override; + bool stopLvgl() override; + + int getEncoderPulses(); + + lv_indev_t* _Nullable getLvglIndev() override { return encHandle; } +}; diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp index 21f40397..40c94162 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp @@ -7,15 +7,12 @@ #include -#define TAG "tpager_keyboard" +constexpr auto* TAG = "TpagerKeyboard"; -#define ENCODER_A GPIO_NUM_40 -#define ENCODER_B GPIO_NUM_41 -#define ENCODER_ENTER GPIO_NUM_7 -#define BACKLIGHT GPIO_NUM_46 +constexpr auto BACKLIGHT = GPIO_NUM_46; -#define KB_ROWS 4 -#define KB_COLS 11 +constexpr auto KB_ROWS = 4; +constexpr auto KB_COLS = 11; // Lowercase Keymap static constexpr char keymap_lc[KB_ROWS][KB_COLS] = { @@ -41,58 +38,16 @@ static constexpr char keymap_sy[KB_ROWS][KB_COLS] = { {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'} }; -static QueueHandle_t keyboardMsg; - -static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { - TpagerKeyboard* kb = (TpagerKeyboard*)lv_indev_get_user_data(indev); - static bool enter_prev = false; +void TpagerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { + auto keyboard = static_cast(lv_indev_get_user_data(indev)); char keypress = 0; - // Defaults - data->key = 0; - data->state = LV_INDEV_STATE_RELEASED; - - if (xQueueReceive(keyboardMsg, &keypress, pdMS_TO_TICKS(50)) == pdPASS) { + if (xQueueReceive(keyboard->queue, &keypress, pdMS_TO_TICKS(50)) == pdPASS) { data->key = keypress; data->state = LV_INDEV_STATE_PRESSED; - } -} - -static void encoder_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { - TpagerKeyboard* kb = (TpagerKeyboard*)lv_indev_get_user_data(indev); - const int enter_filter_threshold = 2; - static int enter_filter = 0; - const int pulses_click = 4; - static int pulses_prev = 0; - bool anyinput = false; - - // Defaults - data->enc_diff = 0; - data->state = LV_INDEV_STATE_RELEASED; - - int pulses = kb->getEncoderPulses(); - int pulse_diff = (pulses - pulses_prev); - if ((pulse_diff > pulses_click) || (pulse_diff < -pulses_click)) { - data->enc_diff = pulse_diff / pulses_click; - pulses_prev = pulses; - anyinput = true; - } - - bool enter = !gpio_get_level(ENCODER_ENTER); - if (enter && (enter_filter < enter_filter_threshold)) { - enter_filter++; - } - if (!enter && (enter_filter > 0)) { - enter_filter--; - } - - if (enter_filter == enter_filter_threshold) { - data->state = LV_INDEV_STATE_PRESSED; - anyinput = true; - } - - if (anyinput) { - kb->makeBacklightImpulse(); + } else { + data->key = 0; + data->state = LV_INDEV_STATE_RELEASED; } } @@ -136,7 +91,7 @@ void TpagerKeyboard::processKeyboard() { chr = keymap_lc[row][col]; } - if (chr != '\0') xQueueSend(keyboardMsg, (void*)&chr, portMAX_DELAY); + if (chr != '\0') xQueueSend(queue, &chr, portMAX_DELAY); } for (int i = 0; i < keypad->released_key_count; i++) { @@ -163,9 +118,7 @@ void TpagerKeyboard::processKeyboard() { bool TpagerKeyboard::startLvgl(lv_display_t* display) { backlightOkay = initBacklight(BACKLIGHT, 30000, LEDC_TIMER_0, LEDC_CHANNEL_1); - initEncoder(); keypad->init(KB_ROWS, KB_COLS); - gpio_input_enable(ENCODER_ENTER); assert(inputTimer == nullptr); inputTimer = std::make_unique(tt::Timer::Type::Periodic, [this] { @@ -174,21 +127,15 @@ bool TpagerKeyboard::startLvgl(lv_display_t* display) { assert(backlightImpulseTimer == nullptr); backlightImpulseTimer = std::make_unique(tt::Timer::Type::Periodic, [this] { - processBacklightImpuse(); + processBacklightImpulse(); }); kbHandle = lv_indev_create(); lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD); - lv_indev_set_read_cb(kbHandle, &keyboard_read_callback); + lv_indev_set_read_cb(kbHandle, &readCallback); lv_indev_set_display(kbHandle, display); lv_indev_set_user_data(kbHandle, this); - encHandle = lv_indev_create(); - lv_indev_set_type(encHandle, LV_INDEV_TYPE_ENCODER); - lv_indev_set_read_cb(encHandle, &encoder_read_callback); - lv_indev_set_display(encHandle, display); - lv_indev_set_user_data(encHandle, this); - inputTimer->start(20 / portTICK_PERIOD_MS); backlightImpulseTimer->start(50 / portTICK_PERIOD_MS); @@ -206,8 +153,6 @@ bool TpagerKeyboard::stopLvgl() { lv_indev_delete(kbHandle); kbHandle = nullptr; - lv_indev_delete(encHandle); - encHandle = nullptr; return true; } @@ -215,81 +160,6 @@ bool TpagerKeyboard::isAttached() const { return tt::hal::i2c::masterHasDeviceAtAddress(keypad->getPort(), keypad->getAddress(), 100); } -void TpagerKeyboard::initEncoder(void) { - const int low_limit = -127; - const int high_limit = 126; - - // Accum. count makes it that over- and underflows are automatically compensated. - // Prerequisite: watchpoints at low and high limit - pcnt_unit_config_t unit_config = { - .low_limit = low_limit, - .high_limit = high_limit, - .flags = {.accum_count = 1}, - }; - - if (pcnt_new_unit(&unit_config, &encPcntUnit) != ESP_OK) { - TT_LOG_E(TAG, "Pulsecounter intialization failed"); - } - - pcnt_glitch_filter_config_t filter_config = { - .max_glitch_ns = 5000, - }; - if (pcnt_unit_set_glitch_filter(encPcntUnit, &filter_config) != ESP_OK) { - TT_LOG_E(TAG, "Pulsecounter glitch filter config failed"); - } - - pcnt_chan_config_t chan_1_config = { - .edge_gpio_num = ENCODER_B, - .level_gpio_num = ENCODER_A, - }; - pcnt_chan_config_t chan_2_config = { - .edge_gpio_num = ENCODER_A, - .level_gpio_num = ENCODER_B, - }; - - pcnt_channel_handle_t pcnt_chan_1 = NULL; - pcnt_channel_handle_t pcnt_chan_2 = NULL; - - if ((pcnt_new_channel(encPcntUnit, &chan_1_config, &pcnt_chan_1) != ESP_OK) || - (pcnt_new_channel(encPcntUnit, &chan_2_config, &pcnt_chan_2) != ESP_OK)) { - TT_LOG_E(TAG, "Pulsecounter channel config failed"); - } - - // second argument is rising edge, third argument is falling edge - if ((pcnt_channel_set_edge_action(pcnt_chan_1, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE) != ESP_OK) || - (pcnt_channel_set_edge_action(pcnt_chan_2, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE) != ESP_OK)) { - TT_LOG_E(TAG, "Pulsecounter edge action config failed"); - } - - // second argument is low level, third argument is high level - if ((pcnt_channel_set_level_action(pcnt_chan_1, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE) != ESP_OK) || - (pcnt_channel_set_level_action(pcnt_chan_2, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE) != ESP_OK)) { - TT_LOG_E(TAG, "Pulsecounter level action config failed"); - } - - if ((pcnt_unit_add_watch_point(encPcntUnit, low_limit) != ESP_OK) || - (pcnt_unit_add_watch_point(encPcntUnit, high_limit) != ESP_OK)) { - TT_LOG_E(TAG, "Pulsecounter watch point config failed"); - } - - if (pcnt_unit_enable(encPcntUnit) != ESP_OK) { - TT_LOG_E(TAG, "Pulsecounter could not be enabled"); - } - if (pcnt_unit_clear_count(encPcntUnit) != ESP_OK) { - TT_LOG_E(TAG, "Pulsecounter could not be cleared"); - } - if (pcnt_unit_start(encPcntUnit) != ESP_OK) { - TT_LOG_E(TAG, "Pulsecounter could not be started"); - } -} - -int TpagerKeyboard::getEncoderPulses() { - int pulses = 0; - pcnt_unit_get_count(encPcntUnit, &pulses); - return pulses; -} - - bool TpagerKeyboard::initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel) { backlightPin = pin; backlightTimer = timer; @@ -344,16 +214,9 @@ void TpagerKeyboard::makeBacklightImpulse() { setBacklightDuty(backlightImpulseDuty); } -void TpagerKeyboard::processBacklightImpuse() { +void TpagerKeyboard::processBacklightImpulse() { if (backlightImpulseDuty > 64) { backlightImpulseDuty--; setBacklightDuty(backlightImpulseDuty); } } - -extern std::shared_ptr tca8418; -std::shared_ptr createKeyboard() { - keyboardMsg = xQueueCreate(20, sizeof(char)); - - return std::make_shared(tca8418); -} diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h index 5f09dcd7..9a51a911 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h @@ -5,33 +5,38 @@ #include #include #include -#include #include class TpagerKeyboard final : public tt::hal::keyboard::KeyboardDevice { lv_indev_t* _Nullable kbHandle = nullptr; - lv_indev_t* _Nullable encHandle = nullptr; - pcnt_unit_handle_t encPcntUnit = nullptr; gpio_num_t backlightPin = GPIO_NUM_NC; ledc_timer_t backlightTimer; ledc_channel_t backlightChannel; bool backlightOkay = false; int backlightImpulseDuty = 0; + QueueHandle_t queue; std::shared_ptr keypad; std::unique_ptr inputTimer; std::unique_ptr backlightImpulseTimer; - void initEncoder(void); bool initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel); void processKeyboard(); - void processBacklightImpuse(); + void processBacklightImpulse(); + + static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); public: - TpagerKeyboard(std::shared_ptr tca) : keypad(std::move(tca)) {} + TpagerKeyboard(const std::shared_ptr& tca) : keypad(tca) { + queue = xQueueCreate(20, sizeof(char)); + } + + ~TpagerKeyboard() { + vQueueDelete(queue); + } std::string getName() const override { return "T-Lora Pager Keyboard"; } std::string getDescription() const override { return "T-Lora Pager I2C keyboard with encoder"; } @@ -42,9 +47,6 @@ public: bool isAttached() const override; lv_indev_t* _Nullable getLvglIndev() override { return kbHandle; } - int getEncoderPulses(); bool setBacklightDuty(uint8_t duty); void makeBacklightImpulse(); }; - -std::shared_ptr createKeyboard(); diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp index d39e3216..98dfa04e 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp @@ -1,15 +1,11 @@ #include "TpagerPower.h" +#include #include -#define TAG "power" +constexpr auto* TAG = "TpagerPower"; -#define TPAGER_GAUGE_I2C_BUS_HANDLE I2C_NUM_0 - -/* -TpagerPower::TpagerPower() : gauge(TPAGER_GAUGE_I2C_BUS_HANDLE) { - gauge->configureCapacity(1500, 1500); -}*/ +constexpr auto TPAGER_GAUGE_I2C_BUS_HANDLE = I2C_NUM_0; TpagerPower::~TpagerPower() {} @@ -29,12 +25,6 @@ bool TpagerPower::supportsMetric(MetricType type) const { } bool TpagerPower::getMetric(MetricType type, MetricData& data) { - /* IsCharging, // bool - Current, // int32_t, mAh - battery current: either during charging (positive value) or discharging (negative value) - BatteryVoltage, // uint32_t, mV - ChargeLevel, // uint8_t [0, 100] -*/ - uint16_t u16 = 0; int16_t s16 = 0; switch (type) { @@ -46,7 +36,6 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) { return true; } return false; - break; case Current: if (gauge->getCurrent(s16)) { data.valueAsInt32 = s16; @@ -54,7 +43,6 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) { } else { return false; } - break; case BatteryVoltage: if (gauge->getVoltage(u16)) { data.valueAsUint32 = u16; @@ -62,7 +50,6 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) { } else { return false; } - break; case ChargeLevel: if (gauge->getStateOfCharge(u16)) { data.valueAsUint8 = u16; @@ -70,21 +57,23 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) { } else { return false; } - break; default: return false; - break; } - - return false; // Safety guard for when new enum values are introduced } -static std::shared_ptr power; -extern std::shared_ptr bq27220; +void TpagerPower::powerOff() { + auto device = tt::hal::findDevice([](auto device) { + return device->getName() == "BQ25896"; + }); -std::shared_ptr tpager_get_power() { - if (power == nullptr) { - power = std::make_shared(bq27220); + if (device == nullptr) { + TT_LOG_E(TAG, "BQ25896 not found"); + return; + } + + auto bq25896 = std::reinterpret_pointer_cast(device); + if (bq25896 != nullptr) { + bq25896->powerOff(); } - return power; } diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerPower.h b/Boards/LilygoTLoraPager/Source/hal/TpagerPower.h index a215d3e7..345ce00c 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerPower.h +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerPower.h @@ -11,7 +11,7 @@ class TpagerPower : public PowerDevice { public: - TpagerPower(std::shared_ptr bq) : gauge(std::move(bq)) {} + TpagerPower(const std::shared_ptr& bq) : gauge(bq) {} ~TpagerPower(); std::string getName() const final { return "T-LoRa Pager Power measument"; } @@ -20,7 +20,6 @@ public: bool supportsMetric(MetricType type) const override; bool getMetric(MetricType type, MetricData& data) override; -private: + bool supportsPowerOff() const override { return true; } + void powerOff() override; }; - -std::shared_ptr tpager_get_power(); diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp index 38340ec2..29a21630 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp @@ -3,29 +3,27 @@ #include #include -#include - using tt::hal::sdcard::SpiSdCardDevice; -#define TPAGER_SDCARD_PIN_CS GPIO_NUM_21 -#define TPAGER_LCD_PIN_CS GPIO_NUM_38 -#define TPAGER_RADIO_PIN_CS GPIO_NUM_36 +constexpr auto TPAGER_SDCARD_PIN_CS = GPIO_NUM_21; +constexpr auto TPAGER_LCD_PIN_CS = GPIO_NUM_38; +constexpr auto TPAGER_RADIO_PIN_CS = GPIO_NUM_36; std::shared_ptr createTpagerSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( TPAGER_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - {TPAGER_RADIO_PIN_CS, - TPAGER_LCD_PIN_CS} + std::vector { + TPAGER_RADIO_PIN_CS, + TPAGER_LCD_PIN_CS + } ); - auto* sdcard = (SdCardDevice*)new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp index 486d3d13..f4f3119e 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp +++ b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp @@ -7,27 +7,25 @@ using tt::hal::sdcard::SpiSdCardDevice; -#define TDECK_SDCARD_PIN_CS GPIO_NUM_39 -#define TDECK_LCD_PIN_CS GPIO_NUM_12 -#define TDECK_RADIO_PIN_CS GPIO_NUM_9 +constexpr auto TDECK_SDCARD_PIN_CS = GPIO_NUM_39; +constexpr auto TDECK_LCD_PIN_CS = GPIO_NUM_12; +constexpr auto TDECK_RADIO_PIN_CS = GPIO_NUM_9; std::shared_ptr createTdeckSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( TDECK_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - { + std::vector { TDECK_RADIO_PIN_CS, TDECK_LCD_PIN_CS } ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp b/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp index 59893496..e39f59fb 100644 --- a/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp +++ b/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp @@ -11,21 +11,19 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( CORE2_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - { + std::vector { CORE2_LCD_PIN_CS } ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp b/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp index b2bb087a..a815b5fa 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp @@ -5,28 +5,26 @@ #include -#define CORES3_SDCARD_PIN_CS GPIO_NUM_4 -#define CORES3_LCD_PIN_CS GPIO_NUM_3 +constexpr auto CORES3_SDCARD_PIN_CS = GPIO_NUM_4; +constexpr auto CORES3_LCD_PIN_CS = GPIO_NUM_3; using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( CORES3_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - { + std::vector { CORES3_LCD_PIN_CS }, SPI3_HOST ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp b/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp index d3595ad8..bee87647 100644 --- a/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp +++ b/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp @@ -13,23 +13,21 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createUnPhoneSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( UNPHONE_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, tt::lvgl::getSyncLock(), - { + std::vector { UNPHONE_LORA_PIN_CS, UNPHONE_LCD_PIN_CS, UNPHONE_TOUCH_PIN_CS } ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Boards/WaveshareS3Touch43/Source/hal/WaveshareSdCard.cpp b/Boards/WaveshareS3Touch43/Source/hal/WaveshareSdCard.cpp index c0446d5b..885ba208 100644 --- a/Boards/WaveshareS3Touch43/Source/hal/WaveshareSdCard.cpp +++ b/Boards/WaveshareS3Touch43/Source/hal/WaveshareSdCard.cpp @@ -5,7 +5,7 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createSdCard() { - auto* configuration = new SpiSdCardDevice::Config( + auto configuration = std::make_unique( GPIO_NUM_10, GPIO_NUM_NC, GPIO_NUM_NC, @@ -13,9 +13,7 @@ std::shared_ptr createSdCard() { SdCardDevice::MountBehaviour::AtBoot ); - auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( - std::unique_ptr(configuration) + return std::make_shared( + std::move(configuration) ); - - return std::shared_ptr(sdcard); } diff --git a/Data/system/app/Launcher/i18n/en-GB.i18n b/Data/system/app/Launcher/i18n/en-GB.i18n deleted file mode 100644 index 5226a6e5..00000000 --- a/Data/system/app/Launcher/i18n/en-GB.i18n +++ /dev/null @@ -1,3 +0,0 @@ -Apps -Files -Settings diff --git a/Data/system/app/Launcher/i18n/en-US.i18n b/Data/system/app/Launcher/i18n/en-US.i18n deleted file mode 100644 index 5226a6e5..00000000 --- a/Data/system/app/Launcher/i18n/en-US.i18n +++ /dev/null @@ -1,3 +0,0 @@ -Apps -Files -Settings diff --git a/Data/system/app/Launcher/i18n/fr-FR.i18n b/Data/system/app/Launcher/i18n/fr-FR.i18n deleted file mode 100644 index 663a29ea..00000000 --- a/Data/system/app/Launcher/i18n/fr-FR.i18n +++ /dev/null @@ -1,3 +0,0 @@ -Appli -Fichiers -Réglages diff --git a/Data/system/app/Launcher/i18n/nl-BE.i18n b/Data/system/app/Launcher/i18n/nl-BE.i18n deleted file mode 100644 index 89a1e21f..00000000 --- a/Data/system/app/Launcher/i18n/nl-BE.i18n +++ /dev/null @@ -1,3 +0,0 @@ -Apps -Bestanden -Instellingen diff --git a/Data/system/app/Launcher/i18n/nl-NL.i18n b/Data/system/app/Launcher/i18n/nl-NL.i18n deleted file mode 100644 index 89a1e21f..00000000 --- a/Data/system/app/Launcher/i18n/nl-NL.i18n +++ /dev/null @@ -1,3 +0,0 @@ -Apps -Bestanden -Instellingen diff --git a/Documentation/ideas.md b/Documentation/ideas.md index e055f691..4cb1a80e 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -16,7 +16,7 @@ ## Lower Priority -- Support hot-plugging SD card +- Support hot-plugging SD card (note: this is not possible if they require the CS pin hack) - Create more unit tests for `tactility` - Explore LVGL9's FreeRTOS functionality - CrashHandler: use "corrupted" flag diff --git a/Drivers/BQ25896/CMakeLists.txt b/Drivers/BQ25896/CMakeLists.txt new file mode 100644 index 00000000..8074f3b3 --- /dev/null +++ b/Drivers/BQ25896/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility +) diff --git a/Drivers/BQ25896/README.md b/Drivers/BQ25896/README.md new file mode 100644 index 00000000..5e77d4c7 --- /dev/null +++ b/Drivers/BQ25896/README.md @@ -0,0 +1,5 @@ +# BQ25896 Power Management IC + +[Product page](https://www.ti.com/product/BQ25896) +[Data sheet](https://www.ti.com/lit/gpn/bq25896) +[Refence implementation](https://github.com/lewisxhe/XPowersLib/blob/73b92a6641b72c0aca2be989207689cb05da9788/src/PowersBQ25896.tpp) diff --git a/Drivers/BQ25896/Source/Bq25896.cpp b/Drivers/BQ25896/Source/Bq25896.cpp new file mode 100644 index 00000000..27c66d5e --- /dev/null +++ b/Drivers/BQ25896/Source/Bq25896.cpp @@ -0,0 +1,13 @@ +#include "Bq25896.h" + +#define TAG "BQ27220" + +void Bq25896::powerOff() { + TT_LOG_I(TAG, "Power off"); + bitOn(0x09, BIT(5)); +} + +void Bq25896::powerOn() { + TT_LOG_I(TAG, "Power on"); + bitOff(0x09, BIT(5)); +} diff --git a/Drivers/BQ25896/Source/Bq25896.h b/Drivers/BQ25896/Source/Bq25896.h new file mode 100644 index 00000000..38fd702d --- /dev/null +++ b/Drivers/BQ25896/Source/Bq25896.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +constexpr auto BQ25896_ADDRESS = 0x6b; + +class Bq25896 final : public tt::hal::i2c::I2cDevice { + +public: + + explicit Bq25896(i2c_port_t port) : I2cDevice(port, BQ25896_ADDRESS) { + powerOn(); + } + + std::string getName() const override { return "BQ25896"; } + + std::string getDescription() const override { return "I2C 1 cell 3A buck battery charger with power path and PSEL"; } + + void powerOff(); + + void powerOn(); +}; diff --git a/Drivers/BQ27220/Source/Bq27220.cpp b/Drivers/BQ27220/Source/Bq27220.cpp index cbf31ebb..1596af07 100644 --- a/Drivers/BQ27220/Source/Bq27220.cpp +++ b/Drivers/BQ27220/Source/Bq27220.cpp @@ -127,7 +127,7 @@ bool Bq27220::getCurrent(int16_t &value) { return false; } -bool Bq27220::getBatteryStatus(Bq27220::BatteryStatus &batt_sta) { +bool Bq27220::getBatteryStatus(BatteryStatus &batt_sta) { if (readRegister16(registers::CMD_BATTERY_STATUS, batt_sta.full)) { swapEndianess(batt_sta.full); return true; diff --git a/Drivers/BQ27220/Source/Bq27220.h b/Drivers/BQ27220/Source/Bq27220.h index be96f551..1e5ac6d8 100644 --- a/Drivers/BQ27220/Source/Bq27220.h +++ b/Drivers/BQ27220/Source/Bq27220.h @@ -6,7 +6,6 @@ class Bq27220 final : public tt::hal::i2c::I2cDevice { -private: uint32_t accessKey; bool unsealDevice(); @@ -15,7 +14,7 @@ private: bool sendSubCommand(uint16_t subCmd, bool waitConfirm = false); bool writeConfig16(uint16_t address, uint16_t value); bool configPreamble(bool &isSealed); - bool configEpilouge(const bool isSealed); + bool configEpilouge(bool isSealed); template bool performConfigUpdate(T configUpdateFunc) @@ -86,9 +85,9 @@ public: uint16_t full; }; - std::string getName() const final { return "BQ27220"; } + std::string getName() const override { return "BQ27220"; } - std::string getDescription() const final { return "I2C-controlled CEDV battery fuel gauge"; } + std::string getDescription() const override { return "I2C-controlled CEDV battery fuel gauge"; } explicit Bq27220(i2c_port_t port) : I2cDevice(port, BQ27220_ADDRESS), accessKey(0xFFFFFFFF) {} diff --git a/Drivers/DRV2605/CMakeLists.txt b/Drivers/DRV2605/CMakeLists.txt new file mode 100644 index 00000000..8074f3b3 --- /dev/null +++ b/Drivers/DRV2605/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility +) diff --git a/Drivers/DRV2605/README.md b/Drivers/DRV2605/README.md new file mode 100644 index 00000000..ec4035c7 --- /dev/null +++ b/Drivers/DRV2605/README.md @@ -0,0 +1,5 @@ +# DRV2605 Haptic Driver + +[Datasheet](https://www.ti.com/product/DRV2605) +[Reference implementation](https://github.com/lewisxhe/SensorLib/blob/master/src/SensorDRV2605.hpp) +[Usage in T-Lora Pager code from LilyGO](https://github.com/Xinyuan-LilyGO/LilyGoLib/blob/6b534a28b0ec31313e4a7e89c5e8b7e4437e6fd1/src/LilyGo_LoRa_Pager.cpp#L956) diff --git a/Drivers/DRV2605/Source/Drv2605.cpp b/Drivers/DRV2605/Source/Drv2605.cpp new file mode 100644 index 00000000..a554ce02 --- /dev/null +++ b/Drivers/DRV2605/Source/Drv2605.cpp @@ -0,0 +1,82 @@ +#include "Drv2605.h" + +bool Drv2605::init() { + uint8_t status; + if (!readRegister8(static_cast(Register::Status), status)) { + TT_LOG_E(TAG, "Failed to read status"); + return false; + } + status >>= 5; + + ChipId chip_id = static_cast(status); + if (chip_id != ChipId::DRV2604 && chip_id != ChipId::DRV2604L && chip_id != ChipId::DRV2605 && chip_id != ChipId::DRV2605L) { + TT_LOG_E(TAG, "Unknown chip id %02x", chip_id); + return false; + } + + writeRegister(Register::Mode, 0x00); // Get out of standby + + writeRegister(Register::RealtimePlaybackInput, 0x00); // Disable + + + setWaveFormForClick(); + + // ERM open loop + + uint8_t feedback; + if (!readRegister(Register::Feedback, feedback)) { + TT_LOG_E(TAG, "Failed to read feedback"); + return false; + } + + writeRegister(Register::Feedback, feedback & 0x7F); // N_ERM_LRA off + + bitOnByIndex(static_cast(Register::Control3), 5); // ERM_OPEN_LOOP on + + return true; +} + +void Drv2605::setWaveFormForBuzz() { + writeRegister(Register::WaveSequence1, 1); // Strong click + writeRegister(Register::WaveSequence2, 1); // Strong click + writeRegister(Register::WaveSequence3, 1); // Strong click + writeRegister(Register::WaveSequence4, 0); // End sequence + + writeRegister(Register::OverdriveTimeOffset, 0); // No overdrive + + writeRegister(Register::SustainTimeOffsetPostivie, 0); + writeRegister(Register::SustainTimeOffsetNegative, 0); + writeRegister(Register::BrakeTimeOffset, 0); + + writeRegister(Register::AudioInputLevelMax, 0x64); +} + +void Drv2605::setWaveFormForClick() { + writeRegister(Register::WaveSequence1, 1); // Strong click + writeRegister(Register::WaveSequence2, 0); // End sequence + + writeRegister(Register::OverdriveTimeOffset, 0); // No overdrive + + writeRegister(Register::SustainTimeOffsetPostivie, 0); + writeRegister(Register::SustainTimeOffsetNegative, 0); + writeRegister(Register::BrakeTimeOffset, 0); + + writeRegister(Register::AudioInputLevelMax, 0x64); +} + +void Drv2605::setWaveForm(uint8_t slot, uint8_t waveform) { + writeRegister8(static_cast(Register::WaveSequence1) + slot, waveform); +} + +void Drv2605::selectLibrary(uint8_t library) { + writeRegister(Register::WaveLibrarySelect, library); +} + +void Drv2605::startPlayback() { + writeRegister(Register::Go, 0x01); +} + +void Drv2605::stopPlayback() { + writeRegister(Register::Go, 0x00); +} + diff --git a/Drivers/DRV2605/Source/Drv2605.h b/Drivers/DRV2605/Source/Drv2605.h new file mode 100644 index 00000000..b29461ec --- /dev/null +++ b/Drivers/DRV2605/Source/Drv2605.h @@ -0,0 +1,94 @@ +#pragma once + +#include + +class Drv2605 : public tt::hal::i2c::I2cDevice { + + static constexpr auto* TAG = "DRV2605"; + static constexpr auto ADDRESS = 0x5A; + + bool autoPlayStartupBuzz; + + // Chip IDs + enum class ChipId { + DRV2604 = 0x04, // Has RAM. Doesn't havew licensed ROM library. + DRV2605 = 0x03, // Has licensed ROM library. Doesn't have RAM. + DRV2604L = 0x06, // Low-voltage variant of the DRV2604. + DRV2605L = 0x07 // Low-voltage variant of the DRV2605. + }; + + enum class Register { + Status = 0x00, + Mode = 0x01, + RealtimePlaybackInput = 0x02, + WaveLibrarySelect = 0x03, + WaveSequence1 = 0x04, + WaveSequence2 = 0x05, + WaveSequence3 = 0x06, + WaveSequence4 = 0x07, + WaveSequence5 = 0x08, + WaveSequence6 = 0x09, + WaveSequence7 = 0x0A, + WaveSequence8 = 0x0B, + Go = 0x0C, + OverdriveTimeOffset = 0x0D, + SustainTimeOffsetPostivie = 0x0E, + SustainTimeOffsetNegative = 0x0F, + BrakeTimeOffset = 0x10, + AudioControl = 0x11, + AudioInputLevelMin = 0x12, + AudioInputLevelMax = 0x13, + AudioOutputLevelMin = 0x14, + AudioOutputLevelMax = 0x15, + RatedVoltage = 0x16, + OverdriveClampVoltage = 0x17, + AutoCalibrationCompensation = 0x18, + AutoCalibrationBackEmf = 0x19, + Feedback = 0x1A, + Control1 = 0x1B, + Control2 = 0x1C, + Control3 = 0x1D, + Control4 = 0x1E, + Vbat = 0x21, + LraResonancePeriod = 0x22, + }; + + bool writeRegister(Register reg, const uint8_t value) const { + return writeRegister8(static_cast(reg), value); + } + + bool readRegister(Register reg, uint8_t& value) const { + return readRegister8(static_cast(reg), value); + } + +public: + + explicit Drv2605(i2c_port_t port, bool autoPlayStartupBuzz = true) : I2cDevice(port, ADDRESS), autoPlayStartupBuzz(autoPlayStartupBuzz) { + if (!init()) { + TT_LOG_E(TAG, "Failed to initialize DRV2605"); + } + + if (autoPlayStartupBuzz) { + setWaveFormForBuzz(); + startPlayback(); + } + } + + std::string getName() const final { return "DRV2605"; } + std::string getDescription() const final { return "Haptic driver for ERM/LRA with waveform library & auto-resonance tracking"; } + + bool init(); + + void setWaveFormForBuzz(); + void setWaveFormForClick(); + + /** + * + * @param slot a value from 0 to 7 + * @param waveform + */ + void setWaveForm(uint8_t slot, uint8_t waveform); + void selectLibrary(uint8_t library); + void startPlayback(); + void stopPlayback(); +}; diff --git a/Drivers/ST7796/README.md b/Drivers/ST7796/README.md index eae8f783..5c8a5ee2 100644 --- a/Drivers/ST7796/README.md +++ b/Drivers/ST7796/README.md @@ -1,3 +1,4 @@ # ST7796 A basic ESP32 LVGL driver for ST7796 displays. + diff --git a/Drivers/ST7796/Source/St7796Display.cpp b/Drivers/ST7796/Source/St7796Display.cpp index 04cfd9b1..ccb5c1c3 100644 --- a/Drivers/ST7796/Source/St7796Display.cpp +++ b/Drivers/ST7796/Source/St7796Display.cpp @@ -67,27 +67,12 @@ bool St7796Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lc const esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = configuration->resetPin, // Set to -1 if not use -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - .color_space = ESP_LCD_COLOR_SPACE_RGB, -#else .color_space = LCD_RGB_ELEMENT_ORDER_RGB, .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, -#endif .bits_per_pixel = 16, .vendor_config = &vendor_config }; - /* - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = configuration->resetPin, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, - .bits_per_pixel = 16, - .flags = { - .reset_active_high = false - }, - .vendor_config = nullptr - }; - */ + if (esp_lcd_new_panel_st7796(ioHandle, &panel_config, &panelHandle) != ESP_OK) { TT_LOG_E(TAG, "Failed to create panel"); return false; @@ -148,7 +133,14 @@ lvgl_port_display_cfg_t St7796Display::getLvglPortDisplayConfig(esp_lcd_panel_io .mirror_y = configuration->mirrorY, }, .color_format = LV_COLOR_FORMAT_NATIVE, - .flags = {.buff_dma = true, .buff_spiram = false, .sw_rotate = false, .swap_bytes = true, .full_refresh = false, .direct_mode = false} + .flags = { + .buff_dma = true, + .buff_spiram = false, + .sw_rotate = false, + .swap_bytes = true, + .full_refresh = false, + .direct_mode = false + } }; } diff --git a/Drivers/ST7796/Source/St7796Display.h b/Drivers/ST7796/Source/St7796Display.h index a5ee3ef9..cbee3753 100644 --- a/Drivers/ST7796/Source/St7796Display.h +++ b/Drivers/ST7796/Source/St7796Display.h @@ -4,7 +4,6 @@ #include #include -#include #include class St7796Display final : public EspLcdDisplay { diff --git a/Drivers/TCA8418/Source/Tca8418.h b/Drivers/TCA8418/Source/Tca8418.h index d48d14a3..81248093 100644 --- a/Drivers/TCA8418/Source/Tca8418.h +++ b/Drivers/TCA8418/Source/Tca8418.h @@ -9,8 +9,6 @@ class Tca8418 final : public tt::hal::i2c::I2cDevice { -private: - uint8_t tca8418_address; uint32_t last_update_micros; uint32_t this_update_micros; diff --git a/Tactility/Include/Tactility/hal/Configuration.h b/Tactility/Include/Tactility/hal/Configuration.h index 949cc2b9..0d97f86d 100644 --- a/Tactility/Include/Tactility/hal/Configuration.h +++ b/Tactility/Include/Tactility/hal/Configuration.h @@ -17,6 +17,10 @@ typedef std::shared_ptr (*CreateDisplay)(); typedef std::shared_ptr (*CreateKeyboard)(); typedef std::shared_ptr (*CreatePower)(); +typedef std::vector> DeviceVector; + +typedef std::shared_ptr (*CreateDevice)(); + enum class LvglInit { Default, None @@ -33,17 +37,23 @@ struct Configuration { const LvglInit lvglInit = LvglInit::Default; /** Display HAL functionality. */ + [[deprecated("use createDevices")]] const CreateDisplay _Nullable createDisplay = nullptr; /** Keyboard HAL functionality. */ + [[deprecated("use createDevices")]] const CreateKeyboard _Nullable createKeyboard = nullptr; /** An optional SD card interface. */ + [[deprecated("use createDevices")]] const std::shared_ptr _Nullable sdcard = nullptr; /** An optional power interface for battery or other power delivery. */ + [[deprecated("use createDevices")]] const CreatePower _Nullable power = nullptr; + std::function createDevices = [] { return std::vector>(); }; + /** A list of I2C interface configurations */ const std::vector i2c = {}; diff --git a/Tactility/Include/Tactility/hal/Device.h b/Tactility/Include/Tactility/hal/Device.h index abccbb09..71247213 100644 --- a/Tactility/Include/Tactility/hal/Device.h +++ b/Tactility/Include/Tactility/hal/Device.h @@ -20,6 +20,7 @@ public: Touch, SdCard, Keyboard, + Encoder, Power, Gps }; @@ -95,7 +96,16 @@ std::vector> findDevices(Device::Type type) { } } -void findDevices(Device::Type type, std::function&)> onDeviceFound); +template +void findDevices(Device::Type type, std::function&)> onDeviceFound) { + auto devices_view = findDevices(type); + for (auto& device : devices_view) { + auto typed_device = std::static_pointer_cast(device); + if (!onDeviceFound(typed_device)) { + break; + } + } +} /** Find the first device of the specified type and cast it to the specified class */ template diff --git a/Tactility/Include/Tactility/hal/encoder/EncoderDevice.h b/Tactility/Include/Tactility/hal/encoder/EncoderDevice.h new file mode 100644 index 00000000..82e8395e --- /dev/null +++ b/Tactility/Include/Tactility/hal/encoder/EncoderDevice.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Device.h" + +#include + +namespace tt::hal::encoder { + +class Display; + +class EncoderDevice : public Device { + +public: + + Type getType() const override { return Type::Encoder; } + + virtual bool startLvgl(lv_display_t* display) = 0; + virtual bool stopLvgl() = 0; + + virtual lv_indev_t* _Nullable getLvglIndev() = 0; +}; + +} diff --git a/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h b/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h index 599288f3..17defe10 100644 --- a/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h +++ b/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h @@ -16,6 +16,8 @@ public: virtual bool startLvgl(lv_display_t* display) = 0; virtual bool stopLvgl() = 0; + + /** @return true when the keyboard currently is physically attached */ virtual bool isAttached() const = 0; virtual lv_indev_t* _Nullable getLvglIndev() = 0; diff --git a/Tactility/Include/Tactility/hal/power/PowerDevice.h b/Tactility/Include/Tactility/hal/power/PowerDevice.h index 2525289d..392f971e 100644 --- a/Tactility/Include/Tactility/hal/power/PowerDevice.h +++ b/Tactility/Include/Tactility/hal/power/PowerDevice.h @@ -39,6 +39,9 @@ public: virtual bool supportsChargeControl() const { return false; } virtual bool isAllowedToCharge() const { return false; } virtual void setAllowedToCharge(bool canCharge) { /* NO-OP*/ } + + virtual bool supportsPowerOff() const { return false; } + virtual void powerOff() { /* NO-OP*/ } }; } // namespace tt diff --git a/Tactility/Private/Tactility/hal/sdcard/SdCardMounting.h b/Tactility/Private/Tactility/hal/sdcard/SdCardMounting.h new file mode 100644 index 00000000..163e5a7e --- /dev/null +++ b/Tactility/Private/Tactility/hal/sdcard/SdCardMounting.h @@ -0,0 +1,7 @@ +#pragma once + +namespace tt::hal::sdcard { + +void mountAll(); + +} diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 5250348c..a2516ae3 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -1,16 +1,16 @@ -#include "Tactility/Tactility.h" - -#include "Tactility/app/AppRegistration.h" -#include "Tactility/lvgl/LvglPrivate.h" -#include "Tactility/service/ServiceManifest.h" +#include +#include +#include #include +#include +#include #include #include namespace tt { -#define TAG "tactility" +#define TAG "Tactility" static const Configuration* config_instance = nullptr; @@ -114,7 +114,7 @@ static void registerSystemApps() { addApp(app::development::manifest); #endif - if (getConfiguration()->hardware->power != nullptr) { + if (hal::findDevices(hal::Device::Type::Power).size() > 0) { addApp(app::power::manifest); } } diff --git a/Tactility/Source/TactilityHeadless.cpp b/Tactility/Source/TactilityHeadless.cpp index a827e5ab..a74ff2bf 100644 --- a/Tactility/Source/TactilityHeadless.cpp +++ b/Tactility/Source/TactilityHeadless.cpp @@ -6,6 +6,7 @@ #include "Tactility/service/ServiceRegistration.h" #include +#include #include #ifdef ESP_PLATFORM @@ -14,7 +15,7 @@ namespace tt { -#define TAG "tactility" +constexpr auto* TAG = "Tactility"; namespace service::gps { extern const ServiceManifest manifest; } namespace service::wifi { extern const ServiceManifest manifest; } @@ -47,11 +48,11 @@ void initHeadless(const hal::Configuration& config) { hardwareConfig = &config; settings::initTimeZone(); hal::init(config); + hal::sdcard::mountAll(); network::ntp::init(); registerAndStartSystemServices(); } - Dispatcher& getMainDispatcher() { return mainDispatcher; } diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index f5d46196..631739d0 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -22,10 +22,10 @@ #define CONFIG_TT_SPLASH_DURATION 0 #endif -#define TAG "boot" - namespace tt::app::boot { +constexpr auto* TAG = "Boot"; + static std::shared_ptr getHalDisplay() { return hal::findFirstDevice(hal::Device::Type::Display); } @@ -60,6 +60,7 @@ class BootApp : public App { } } + static bool setupUsbBootMode() { if (!hal::usb::isUsbBootMode()) { return false; @@ -86,7 +87,7 @@ class BootApp : public App { kernel::publishSystemEvent(kernel::SystemEvent::BootSplash); - setupDisplay(); + setupDisplay(); // Set backlight if (!setupUsbBootMode()) { initFromBootApp(); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 1c994cc7..a6706212 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,5 +1,4 @@ #include "Tactility/app/AppContext.h" -#include "Tactility/app/launcher/TextResources.h" #include "Tactility/app/AppRegistration.h" #include "Tactility/service/loader/Loader.h" @@ -7,53 +6,61 @@ #include #include +#include namespace tt::app::launcher { constexpr auto* TAG = "Launcher"; +constexpr auto BUTTON_SIZE = 64; -static void onAppPressed(TT_UNUSED lv_event_t* e) { - auto* appId = (const char*)lv_event_get_user_data(e); - service::loader::startApp(appId); -} +class LauncherApp final : public App { -static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char* imageFile, const char* appId, int32_t buttonPaddingLeft) { - auto* wrapper = lv_obj_create(parent); - lv_obj_set_size(wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_pad_ver(wrapper, 0, 0); - lv_obj_set_style_pad_left(wrapper, buttonPaddingLeft, 0); - lv_obj_set_style_pad_right(wrapper, 0, 0); - lv_obj_set_style_border_width(wrapper, 0, 0); + static lv_obj_t* createAppButton(lv_obj_t* parent, const char* imageFile, const char* appId, int32_t horizontalMargin) { + auto* apps_button = lv_button_create(parent); + lv_obj_set_style_pad_all(apps_button, 0, LV_STATE_DEFAULT); + lv_obj_set_style_margin_hor(apps_button, horizontalMargin, LV_STATE_DEFAULT); + lv_obj_set_style_shadow_width(apps_button, 0, LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(apps_button, 0, LV_STATE_DEFAULT); - auto* apps_button = lv_button_create(wrapper); - lv_obj_set_style_pad_hor(apps_button, 0, 0); - lv_obj_set_style_pad_top(apps_button, 0, 0); - lv_obj_set_style_pad_bottom(apps_button, 8, 0); - lv_obj_set_style_shadow_width(apps_button, 0, 0); - lv_obj_set_style_border_width(apps_button, 0, 0); - lv_obj_set_style_bg_opa(apps_button, 0, LV_PART_MAIN); + auto* button_image = lv_image_create(apps_button); + lv_image_set_src(button_image, imageFile); + lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); + lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT); + // Ensure buttons are still tappable when the asset fails to load + // Icon images are 40x40, so we get some extra padding too + lv_obj_set_size(button_image, BUTTON_SIZE, BUTTON_SIZE); - auto* button_image = lv_image_create(apps_button); - lv_image_set_src(button_image, imageFile); - lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); - lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT); - // Ensure buttons are still tappable when asset fails to load - // Icon images are 40x40, so we get some extra padding too - lv_obj_set_size(button_image, 64, 64); + lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); - auto* label = lv_label_create(wrapper); - lv_label_set_text(label, title); - lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, 0); + return apps_button; + } - lv_obj_add_event_cb(wrapper, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); - lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); - lv_obj_add_event_cb(label, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); + static bool shouldShowPowerButton() { + bool show_power_button = false; + hal::findDevices(hal::Device::Type::Power, [&show_power_button](const auto& device) { + if (device->supportsPowerOff()) { + show_power_button = true; + return false; // stop iterating + } else { + return true; // continue iterating + } + }); + return show_power_button; + } - return wrapper; -} + static void onAppPressed(TT_UNUSED lv_event_t* e) { + auto* appId = static_cast(lv_event_get_user_data(e)); + service::loader::startApp(appId); + } -class LauncherApp : public App { - tt::i18n::TextResources textResources = tt::i18n::TextResources("/system/app/Launcher/i18n"); + static void onPowerOffPressed(lv_event_t* e) { + auto power = hal::findFirstDevice(hal::Device::Type::Power); + if (power != nullptr && power->supportsPowerOff()) { + power->powerOff(); + } + } + +public: void onCreate(TT_UNUSED AppContext& app) override { BootProperties boot_properties; @@ -64,41 +71,48 @@ class LauncherApp : public App { } void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) override { - textResources.load(); + auto* buttons_wrapper = lv_obj_create(parent); - auto* wrapper = lv_obj_create(parent); + lv_obj_align(buttons_wrapper, LV_ALIGN_CENTER, 0, 0); + // lv_obj_set_style_pad_all(buttons_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_size(buttons_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_border_width(buttons_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_grow(buttons_wrapper, 1); - lv_obj_align(wrapper, LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_pad_all(wrapper, 0, 0); - lv_obj_set_size(wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_border_width(wrapper, 0, 0); - lv_obj_set_flex_grow(wrapper, 1); - - auto* display = lv_obj_get_display(parent); - auto horizontal_px = lv_display_get_horizontal_resolution(display); - auto vertical_px = lv_display_get_vertical_resolution(display); - bool is_landscape_display = horizontal_px >= vertical_px; + const auto* display = lv_obj_get_display(parent); + const auto horizontal_px = lv_display_get_horizontal_resolution(display); + const auto vertical_px = lv_display_get_vertical_resolution(display); + const bool is_landscape_display = horizontal_px >= vertical_px; if (is_landscape_display) { - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_ROW); } else { - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN); } - int32_t available_width = lv_display_get_horizontal_resolution(display) - (3 * 80); - int32_t padding = is_landscape_display ? std::min(available_width / 4, (int32_t)64) : 0; + const int32_t available_width = lv_display_get_horizontal_resolution(display) - (3 * BUTTON_SIZE); + const int32_t margin = is_landscape_display ? std::min(available_width / 16, BUTTON_SIZE) : 0; - auto paths = app.getPaths(); - auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); - auto files_icon_path = paths->getSystemPathLvgl("icon_files.png"); - auto settings_icon_path = paths->getSystemPathLvgl("icon_settings.png"); + const auto paths = app.getPaths(); + const auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); + const auto files_icon_path = paths->getSystemPathLvgl("icon_files.png"); + const auto settings_icon_path = paths->getSystemPathLvgl("icon_settings.png"); - const auto& apps_title = textResources[i18n::Text::APPS]; - const auto& files_title = textResources[i18n::Text::FILES]; - const auto& settings_title = textResources[i18n::Text::SETTINGS]; + createAppButton(buttons_wrapper, apps_icon_path.c_str(), "AppList", margin); + createAppButton(buttons_wrapper, files_icon_path.c_str(), "Files", margin); + createAppButton(buttons_wrapper, settings_icon_path.c_str(), "Settings", margin); - createAppButton(wrapper, apps_title.c_str(), apps_icon_path.c_str(), "AppList", 0); - createAppButton(wrapper, files_title.c_str(), files_icon_path.c_str(), "Files", padding); - createAppButton(wrapper, settings_title.c_str(), settings_icon_path.c_str(), "Settings", padding); + if (shouldShowPowerButton()) { + auto* power_button = lv_btn_create(parent); + lv_obj_set_style_pad_all(power_button, 8, 0); + lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10); + lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, nullptr); + lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); + + auto* power_label = lv_label_create(power_button); + lv_label_set_text(power_label, LV_SYMBOL_POWER); + lv_obj_set_style_text_color(power_label, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); + } } }; diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index a87b22c2..3844f82b 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -37,7 +37,7 @@ class ScreenshotApp final : public App { public: ScreenshotApp(); - ~ScreenshotApp(); + ~ScreenshotApp() override; void onShow(AppContext& app, lv_obj_t* parent) override; void onStartPressed(); diff --git a/Tactility/Source/hal/Device.cpp b/Tactility/Source/hal/Device.cpp index c9dc4002..21d8a548 100644 --- a/Tactility/Source/hal/Device.cpp +++ b/Tactility/Source/hal/Device.cpp @@ -88,15 +88,6 @@ std::vector> findDevices(Device::Type type) { }); } -void findDevices(Device::Type type, std::function&)> onDeviceFound) { - auto devices_view = findDevices(type); - for (auto& device : devices_view) { - if (!onDeviceFound(device)) { - break; - } - } -} - std::vector> getDevices() { return devices; } diff --git a/Tactility/Source/hal/Hal.cpp b/Tactility/Source/hal/Hal.cpp index 59f79671..f46f00d0 100644 --- a/Tactility/Source/hal/Hal.cpp +++ b/Tactility/Source/hal/Hal.cpp @@ -8,12 +8,35 @@ #include -#define TAG "hal" - -#define TT_SDCARD_MOUNT_POINT "/sdcard" - namespace tt::hal { +constexpr auto* TAG = "hal"; + +void registerDevices(const Configuration& configuration) { + TT_LOG_I(TAG, "Registering devices"); + + if (configuration.sdcard != nullptr) { + registerDevice(configuration.sdcard); + } + + if (configuration.power != nullptr) { + std::shared_ptr power = configuration.power(); + registerDevice(power); + } + + if (configuration.createKeyboard) { + auto keyboard = configuration.createKeyboard(); + if (keyboard != nullptr) { + registerDevice(std::reinterpret_pointer_cast(keyboard)); + } + } + + auto devices = configuration.createDevices(); + for (auto& device : devices) { + registerDevice(device); + } +} + void init(const Configuration& configuration) { kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalBegin); @@ -34,18 +57,7 @@ void init(const Configuration& configuration) { tt_check(configuration.initBoot(), "Init power failed"); } - if (configuration.sdcard != nullptr) { - TT_LOG_I(TAG, "Mounting sdcard"); - if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT)) { - TT_LOG_W(TAG, "SD card mount failed (init can continue)"); - } - registerDevice(configuration.sdcard); - } - - if (configuration.power != nullptr) { - std::shared_ptr power = configuration.power(); - registerDevice(power); - } + registerDevices(configuration); kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalEnd); } diff --git a/Tactility/Source/hal/i2c/I2cDevice.cpp b/Tactility/Source/hal/i2c/I2cDevice.cpp index 1e96f8d5..3cc44080 100644 --- a/Tactility/Source/hal/i2c/I2cDevice.cpp +++ b/Tactility/Source/hal/i2c/I2cDevice.cpp @@ -5,11 +5,11 @@ namespace tt::hal::i2c { bool I2cDevice::read(uint8_t* data, size_t dataSize, TickType_t timeout) { - return tt::hal::i2c::masterRead(port, address, data, dataSize, timeout); + return masterRead(port, address, data, dataSize, timeout); } bool I2cDevice::write(const uint8_t* data, uint16_t dataSize, TickType_t timeout) { - return tt::hal::i2c::masterWrite(port, address, data, dataSize, timeout); + return masterWrite(port, address, data, dataSize, timeout); } bool I2cDevice::writeRead(const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) { @@ -22,7 +22,7 @@ bool I2cDevice::writeRegister(uint8_t reg, const uint8_t* data, uint16_t dataSiz bool I2cDevice::readRegister12(uint8_t reg, float& out) const { std::uint8_t data[2] = {0}; - if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { + if (masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { out = (data[0] & 0x0F) << 8 | data[1]; return true; } else { @@ -32,7 +32,7 @@ bool I2cDevice::readRegister12(uint8_t reg, float& out) const { bool I2cDevice::readRegister14(uint8_t reg, float& out) const { std::uint8_t data[2] = {0}; - if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { + if (masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { out = (data[0] & 0x3F) << 8 | data[1]; return true; } else { @@ -42,7 +42,7 @@ bool I2cDevice::readRegister14(uint8_t reg, float& out) const { bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const { std::uint8_t data[2] = {0}; - if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { + if (masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { out = data[0] << 8 | data[1]; return true; } else { @@ -51,11 +51,11 @@ bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const { } bool I2cDevice::readRegister8(uint8_t reg, uint8_t& result) const { - return tt::hal::i2c::masterWriteRead(port, address, ®, 1, &result, 1, DEFAULT_TIMEOUT); + return masterWriteRead(port, address, ®, 1, &result, 1, DEFAULT_TIMEOUT); } bool I2cDevice::writeRegister8(uint8_t reg, uint8_t value) const { - return tt::hal::i2c::masterWriteRegister(port, address, reg, &value, 1, DEFAULT_TIMEOUT); + return masterWriteRegister(port, address, reg, &value, 1, DEFAULT_TIMEOUT); } bool I2cDevice::bitOn(uint8_t reg, uint8_t bitmask) const { diff --git a/Tactility/Source/hal/sdcard/SdCardMounting.cpp b/Tactility/Source/hal/sdcard/SdCardMounting.cpp new file mode 100644 index 00000000..1a3079e0 --- /dev/null +++ b/Tactility/Source/hal/sdcard/SdCardMounting.cpp @@ -0,0 +1,40 @@ +#include +#include + +namespace tt::hal::sdcard { + +constexpr auto* TAG = "SdCardMounting"; +constexpr auto* TT_SDCARD_MOUNT_POINT = "/sdcard"; + +static void mount(const std::shared_ptr& sdcard, const std::string& path) { + sdcard->getLock()->withLock([&sdcard, &path] { + TT_LOG_I(TAG, "Mounting sdcard at %s", path.c_str()); + if (!sdcard->mount(path)) { + TT_LOG_W(TAG, "SD card mount failed for %s (init can continue)", path.c_str()); + } + }); +} + +void mountAll() { + auto sdcards = hal::findDevices(Device::Type::SdCard); + if (!sdcards.empty()) { + if (sdcards.size() == 1) { + // Fixed mount path name + auto sdcard = sdcards[0]; + if (!sdcard->isMounted()) { + mount(sdcard, TT_SDCARD_MOUNT_POINT); + } + } else { + // Numbered mount path name + for (int i = 0; i < sdcards.size(); i++) { + auto sdcard = sdcards[i]; + if (!sdcard->isMounted()) { + std::string mount_path = TT_SDCARD_MOUNT_POINT + std::to_string(i); + mount(sdcard, mount_path); + } + } + } + } +} + +} diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index ce522b3c..009507dd 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -21,30 +21,29 @@ static Mode currentMode = Mode::Default; static RTC_NOINIT_ATTR BootMode bootMode; sdmmc_card_t* _Nullable getCard() { - auto sdcard = getConfiguration()->sdcard; - if (sdcard == nullptr) { - TT_LOG_W(TAG, "No SD card configuration found"); + auto sdcards = findDevices(Device::Type::SdCard); + + std::shared_ptr usable_sdcard; + for (auto& sdcard : sdcards) { + auto sdcard_candidate = std::static_pointer_cast(sdcard); + if (sdcard_candidate != nullptr && sdcard_candidate->isMounted() && sdcard_candidate->getCard() != nullptr) { + usable_sdcard = sdcard_candidate; + break; + } + } + + if (usable_sdcard == nullptr) { + TT_LOG_W(TAG, "Couldn't find a mounted SpiSdCard"); return nullptr; } - if (!sdcard->isMounted()) { - TT_LOG_W(TAG, "SD card not mounted"); - return nullptr; - } - - auto spi_sdcard = std::static_pointer_cast(sdcard); - if (spi_sdcard == nullptr) { - TT_LOG_W(TAG, "SD card interface is not supported (must be SpiSdCard)"); - return nullptr; - } - - auto* card = spi_sdcard->getCard(); - if (card == nullptr) { + auto* sdmmc_card = usable_sdcard->getCard(); + if (sdmmc_card == nullptr) { TT_LOG_W(TAG, "SD card has no card object available"); return nullptr; } - return card; + return sdmmc_card; } static bool canStartNewMode() { diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index f6b73d41..9f77098a 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -1,43 +1,55 @@ -#include "Tactility/app/display/DisplaySettings.h" -#include "Tactility/lvgl/Keyboard.h" -#include "Tactility/lvgl/Lvgl.h" - -#include "Tactility/hal/display/DisplayDevice.h" -#include "Tactility/hal/touch/TouchDevice.h" +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include +#include #ifdef ESP_PLATFORM -#include "Tactility/lvgl/EspLvglPort.h" +#include #endif #include -#include -#include -#include namespace tt::lvgl { -#define TAG "Lvgl" +constexpr auto* TAG = "Lvgl"; static bool started = false; -static std::shared_ptr createDisplay(const hal::Configuration& config) { - assert(config.createDisplay); - auto display = config.createDisplay(); - assert(display != nullptr); - - if (!display->start()) { - TT_LOG_E(TAG, "Display start failed"); - return nullptr; +// TODO: Move to hal init +static void initDisplays(const hal::Configuration& config) { + TT_LOG_I(TAG, "Init displays"); + if (config.createDisplay != nullptr) { + auto display = config.createDisplay(); + if (display != nullptr) { + hal::registerDevice(display); + } } - if (display->supportsBacklightDuty()) { - display->setBacklightDuty(0); - } + TT_LOG_I(TAG, "Start displays"); + auto displays = hal::findDevices(hal::Device::Type::Display); + for (auto& display : displays) { + if (!display->start()) { + TT_LOG_E(TAG, "Display start failed"); + } - return display; + if (display->supportsBacklightDuty()) { + display->setBacklightDuty(0); + } + + auto touch = display->getTouchDevice(); + if (touch != nullptr) { + hal::registerDevice(touch); + touch->start(); + } + } } void init(const hal::Configuration& config) { @@ -49,25 +61,7 @@ void init(const hal::Configuration& config) { } #endif - auto display = createDisplay(config); - if (display == nullptr) { - return; - } - hal::registerDevice(display); - - auto touch = display->getTouchDevice(); - if (touch != nullptr) { - touch->start(); - hal::registerDevice(touch); - } - - auto configuration = hal::getConfiguration(); - if (configuration->createKeyboard) { - auto keyboard = configuration->createKeyboard(); - if (keyboard != nullptr) { - hal::registerDevice(keyboard); - } - } + initDisplays(config); start(); @@ -91,20 +85,27 @@ void start() { // Start displays (their related touch devices start automatically within) + TT_LOG_I(TAG, "Start displays"); auto displays = hal::findDevices(hal::Device::Type::Display); for (auto display : displays) { - if (display->supportsLvgl() && display->startLvgl()) { - auto lvgl_display = display->getLvglDisplay(); - assert(lvgl_display != nullptr); - lv_display_rotation_t rotation = app::display::getRotation(); - if (rotation != lv_display_get_rotation(lvgl_display)) { - lv_display_set_rotation(lvgl_display, rotation); + if (display->supportsLvgl()) { + if (display->startLvgl()) { + TT_LOG_I(TAG, "Started %s", display->getName().c_str()); + auto lvgl_display = display->getLvglDisplay(); + assert(lvgl_display != nullptr); + lv_display_rotation_t rotation = app::display::getRotation(); + if (rotation != lv_display_get_rotation(lvgl_display)) { + lv_display_set_rotation(lvgl_display, rotation); + } + } else { + TT_LOG_E(TAG, "Start failed for %s", display->getName().c_str()); } } } // Start touch + TT_LOG_I(TAG, "Start touch devices"); auto touch_devices = hal::findDevices(hal::Device::Type::Touch); for (auto touch_device : touch_devices) { if (displays.size() > 0) { @@ -112,13 +113,17 @@ void start() { auto display = displays[0]; // Start any touch devices that haven't been started yet if (touch_device->supportsLvgl() && touch_device->getLvglIndev() == nullptr) { - touch_device->startLvgl(display->getLvglDisplay()); + if (touch_device->startLvgl(display->getLvglDisplay())) { + TT_LOG_I(TAG, "Started %s", touch_device->getName().c_str()); + } else { + TT_LOG_E(TAG, "Start failed for %s", touch_device->getName().c_str()); + } } } } // Start keyboards - + TT_LOG_I(TAG, "Start keyboards"); auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); for (auto keyboard : keyboards) { if (displays.size() > 0) { @@ -128,14 +133,29 @@ void start() { if (keyboard->startLvgl(display->getLvglDisplay())) { lv_indev_t* keyboard_indev = keyboard->getLvglIndev(); hardware_keyboard_set_indev(keyboard_indev); - TT_LOG_I(TAG, "Keyboard started"); + TT_LOG_I(TAG, "Started %s", keyboard->getName().c_str()); } else { - TT_LOG_E(TAG, "Keyboard start failed"); + TT_LOG_E(TAG, "Start failed for %s", keyboard->getName().c_str()); } } } } + // Start encoders + TT_LOG_I(TAG, "Start encoders"); + auto encoders = hal::findDevices(hal::Device::Type::Encoder); + for (auto encoder : encoders) { + if (displays.size() > 0) { + // TODO: Consider implementing support for multiple displays + auto display = displays[0]; + if (encoder->startLvgl(display->getLvglDisplay())) { + TT_LOG_I(TAG, "Started %s", encoder->getName().c_str()); + } else { + TT_LOG_E(TAG, "Start failed for %s", encoder->getName().c_str()); + } + } + } + // Restart services if (service::getState("Gui") == service::State::Stopped) { @@ -158,7 +178,7 @@ void start() { } void stop() { - TT_LOG_I(TAG, "Stop LVGL"); + TT_LOG_I(TAG, "Stopping LVGL"); if (!started) { TT_LOG_W(TAG, "Can't stop LVGL: not started"); @@ -175,6 +195,7 @@ void stop() { // Stop keyboards + TT_LOG_I(TAG, "Stopping keyboards"); auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); for (auto keyboard : keyboards) { if (keyboard->getLvglIndev() != nullptr) { @@ -184,6 +205,7 @@ void stop() { // Stop touch + TT_LOG_I(TAG, "Stopping touch"); // The display generally stops their own touch devices, but we'll clean up anything that didn't auto touch_devices = hal::findDevices(hal::Device::Type::Touch); for (auto touch_device : touch_devices) { @@ -192,8 +214,19 @@ void stop() { } } + // Stop encoders + + TT_LOG_I(TAG, "Stopping encoders"); + // The display generally stops their own touch devices, but we'll clean up anything that didn't + auto encoder_devices = hal::findDevices(hal::Device::Type::Encoder); + for (auto encoder_device : encoder_devices) { + if (encoder_device->getLvglIndev() != nullptr) { + encoder_device->stopLvgl(); + } + } // Stop displays (and their touch devices) + TT_LOG_I(TAG, "Stopping displays"); auto displays = hal::findDevices(hal::Device::Type::Display); for (auto display : displays) { if (display->supportsLvgl() && display->getLvglDisplay() != nullptr && !display->stopLvgl()) { @@ -204,6 +237,8 @@ void stop() { started = false; kernel::publishSystemEvent(kernel::SystemEvent::LvglStopped); + + TT_LOG_I(TAG, "Stopped LVGL"); } } // namespace diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 68ca0deb..0e27a870 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -111,7 +111,7 @@ static const lv_obj_class_t statusbar_class = { static void statusbar_pubsub_event(TT_UNUSED const void* message, void* obj) { TT_LOG_D(TAG, "event"); auto* statusbar = static_cast(obj); - if (lock(kernel::millisToTicks(100))) { + if (lock(portMAX_DELAY)) { update_main(statusbar); lv_obj_invalidate(&statusbar->obj); unlock(); @@ -119,7 +119,7 @@ static void statusbar_pubsub_event(TT_UNUSED const void* message, void* obj) { } static void onTimeChanged(TT_UNUSED kernel::SystemEvent event) { - if (statusbar_data.mutex.lock(100 / portTICK_PERIOD_MS)) { + if (statusbar_data.mutex.lock()) { statusbar_data.time_update_timer->stop(); statusbar_data.time_update_timer->start(5); @@ -136,7 +136,7 @@ static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) statusbar->pubsub_subscription = statusbar_data.pubsub->subscribe(&statusbar_pubsub_event, statusbar); if (!statusbar_data.time_update_timer->isRunning()) { - statusbar_data.time_update_timer->start(50 / portTICK_PERIOD_MS); + statusbar_data.time_update_timer->start(200 / portTICK_PERIOD_MS); statusbar_data.systemEventSubscription = kernel::subscribeSystemEvent( kernel::SystemEvent::Time, onTimeChanged @@ -210,7 +210,7 @@ static void update_time(Statusbar* statusbar) { static void update_main(Statusbar* statusbar) { update_time(statusbar); - if (statusbar_lock(50 / portTICK_PERIOD_MS)) { + if (statusbar_lock(200 / portTICK_PERIOD_MS)) { for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) { update_icon(statusbar->icons[i], &(statusbar_data.icons[i])); } @@ -270,7 +270,7 @@ void statusbar_icon_remove(int8_t id) { void statusbar_icon_set_image(int8_t id, const std::string& image) { TT_LOG_D(TAG, "id %d: set image %s", id, image.empty() ? "(none)" : image.c_str()); tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT); - if (statusbar_lock(50 / portTICK_PERIOD_MS)) { + if (statusbar_lock()) { StatusbarIcon* icon = &statusbar_data.icons[id]; tt_check(icon->claimed); icon->image = image; @@ -282,7 +282,7 @@ void statusbar_icon_set_image(int8_t id, const std::string& image) { void statusbar_icon_set_visibility(int8_t id, bool visible) { TT_LOG_D(TAG, "id %d: set visibility %d", id, visible); tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT); - if (statusbar_lock(50 / portTICK_PERIOD_MS)) { + if (statusbar_lock()) { StatusbarIcon* icon = &statusbar_data.icons[id]; tt_check(icon->claimed); icon->visible = visible; diff --git a/Tactility/Source/service/sdcard/Sdcard.cpp b/Tactility/Source/service/sdcard/Sdcard.cpp index 685a73d6..57502e0f 100644 --- a/Tactility/Source/service/sdcard/Sdcard.cpp +++ b/Tactility/Source/service/sdcard/Sdcard.cpp @@ -26,8 +26,11 @@ class SdCardService final : public Service { } void update() { - auto sdcard = hal::getConfiguration()->sdcard; - assert(sdcard); + // TODO: Support multiple SD cards + auto sdcard = hal::findFirstDevice(hal::Device::Type::SdCard); + if (sdcard == nullptr) { + return; + } if (lock(50)) { auto new_state = sdcard->getState(); @@ -50,16 +53,12 @@ class SdCardService final : public Service { public: void onStart(ServiceContext& serviceContext) override { - if (hal::getConfiguration()->sdcard != nullptr) { - auto service = findServiceById(manifest.id); - updateTimer = std::make_unique(Timer::Type::Periodic, [service]() { - service->update(); - }); - // We want to try and scan more often in case of startup or scan lock failure - updateTimer->start(1000); - } else { - TT_LOG_I(TAG, "Timer not started: no SD card config"); - } + auto service = findServiceById(manifest.id); + updateTimer = std::make_unique(Timer::Type::Periodic, [service]() { + service->update(); + }); + // We want to try and scan more often in case of startup or scan lock failure + updateTimer->start(1000); } void onStop(ServiceContext& serviceContext) override { diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 68a37e4a..4d9e8145 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -89,13 +89,20 @@ static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) { } static _Nullable const char* getPowerStatusIcon() { - auto get_power = getConfiguration()->hardware->power; - if (get_power == nullptr) { + // TODO: Support multiple power devices? + std::shared_ptr power; + hal::findDevices(hal::Device::Type::Power, [&power](const auto& device) { + if (device->supportsMetric(hal::power::PowerDevice::MetricType::ChargeLevel)) { + power = device; + return false; + } + return true; + }); + + if (power == nullptr) { return nullptr; } - auto power = get_power(); - hal::power::PowerDevice::MetricData charge_level; if (!power->getMetric(hal::power::PowerDevice::MetricType::ChargeLevel, charge_level)) { return nullptr; @@ -197,7 +204,9 @@ class StatusbarService final : public Service { } void updateSdCardIcon() { - auto sdcard = hal::getConfiguration()->sdcard; + auto sdcards = hal::findDevices(hal::Device::Type::SdCard); + // TODO: Support multiple SD cards + auto sdcard = sdcards.empty() ? nullptr : sdcards[0]; if (sdcard != nullptr) { auto state = sdcard->getState(50 / portTICK_PERIOD_MS); if (state != hal::sdcard::SdCardDevice::State::Timeout) { diff --git a/TactilityC/Source/tt_hal_device.cpp b/TactilityC/Source/tt_hal_device.cpp index 1ba090dc..dbd91730 100644 --- a/TactilityC/Source/tt_hal_device.cpp +++ b/TactilityC/Source/tt_hal_device.cpp @@ -33,7 +33,7 @@ bool tt_hal_device_find(DeviceType type, DeviceId* deviceIds, uint16_t* count, u int16_t currentIndex = -1; uint16_t maxIndex = maxCount - 1; - findDevices(toTactilityDeviceType(type), [&](const std::shared_ptr& device) { + findDevices(toTactilityDeviceType(type), [&](const auto& device) { currentIndex++; deviceIds[currentIndex] = device->getId(); // Continue if there is storage capacity left diff --git a/TactilityCore/Include/Tactility/MessageQueue.h b/TactilityCore/Include/Tactility/MessageQueue.h index b5a90ce9..f781ef51 100644 --- a/TactilityCore/Include/Tactility/MessageQueue.h +++ b/TactilityCore/Include/Tactility/MessageQueue.h @@ -24,7 +24,6 @@ namespace tt { * Calls can be done from ISR/IRQ mode unless otherwise specified. */ class MessageQueue { -private: struct QueueHandleDeleter { void operator()(QueueHandle_t handleToDelete) { diff --git a/Translations/Translations.ods b/Translations/Translations.ods index 3ce333cdb1c527796c70bc09d411fc7401decf78..44298ecf4a93479bf0ce92f79c0f6f6640808618 100644 GIT binary patch delta 4310 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 delta 4521 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 diff --git a/sdkconfig.board.lilygo-tlora-pager b/sdkconfig.board.lilygo-tlora-pager index f68156ed..d31a9405 100644 --- a/sdkconfig.board.lilygo-tlora-pager +++ b/sdkconfig.board.lilygo-tlora-pager @@ -59,3 +59,5 @@ CONFIG_LV_THEME_DEFAULT_DARK=y # USB CONFIG_TINYUSB_MSC_ENABLED=y CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard" +# Boot optimization +CONFIG_SPIRAM_MEMTEST=n