From 612377974ada2142e2db8244ba856b6d25a1423d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 30 Aug 2025 12:16:36 +0200 Subject: [PATCH] WIP --- Boards/LilygoTLoraPager/CMakeLists.txt | 2 +- Boards/LilygoTLoraPager/Source/Init.cpp | 30 +--- .../Source/LilygoTloraPager.cpp | 59 +++++-- .../Source/hal/TpagerEncoder.cpp | 141 +++++++++++++++ .../Source/hal/TpagerEncoder.h | 29 +++ .../Source/hal/TpagerKeyboard.cpp | 166 ++---------------- .../Source/hal/TpagerKeyboard.h | 20 ++- .../Source/hal/TpagerPower.cpp | 25 +-- .../LilygoTLoraPager/Source/hal/TpagerPower.h | 4 +- .../Source/hal/TpagerSdCard.cpp | 2 - Drivers/BQ25896/Source/Bq25896.h | 6 +- Drivers/BQ27220/Source/Bq27220.h | 7 +- Drivers/DRV2605/CMakeLists.txt | 5 + Drivers/DRV2605/README.md | 5 + Drivers/DRV2605/Source/Drv2605.cpp | 61 +++++++ Drivers/DRV2605/Source/Drv2605.h | 79 +++++++++ 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 + Tactility/Source/app/launcher/Launcher.cpp | 77 +++++--- Tactility/Source/hal/Device.cpp | 9 - Tactility/Source/hal/Hal.cpp | 59 +++++-- Tactility/Source/lvgl/Lvgl.cpp | 124 +++++++------ Tactility/Source/service/sdcard/Sdcard.cpp | 23 ++- .../Source/service/statusbar/Statusbar.cpp | 4 +- TactilityC/Source/tt_hal_device.cpp | 2 +- .../Include/Tactility/MessageQueue.h | 1 - 29 files changed, 633 insertions(+), 356 deletions(-) create mode 100644 Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.cpp create mode 100644 Boards/LilygoTLoraPager/Source/hal/TpagerEncoder.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 diff --git a/Boards/LilygoTLoraPager/CMakeLists.txt b/Boards/LilygoTLoraPager/CMakeLists.txt index 58b0977e..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 BQ25896 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 429d007c..e6619d5e 100644 --- a/Boards/LilygoTLoraPager/Source/Init.cpp +++ b/Boards/LilygoTLoraPager/Source/Init.cpp @@ -6,18 +6,8 @@ #include #include -#include -#include -#include -#define TAG "TLoraPager" - -// Power on -constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10; - -std::shared_ptr bq27220; -std::shared_ptr tca8418; -std::shared_ptr bq25896; +constexpr auto* TAG = "TLoraPager"; bool tpagerInit() { ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START); @@ -30,25 +20,17 @@ bool tpagerInit() { return false; } - bq27220 = std::make_shared(I2C_NUM_0); - tt::hal::registerDevice(bq27220); - - tca8418 = std::make_shared(I2C_NUM_0); - tt::hal::registerDevice(tca8418); - - bq25896 = std::make_shared(I2C_NUM_0); - tt::hal::registerDevice(bq25896); - bq25896->powerOn(); - tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { - bq27220->configureCapacity(1500, 1500); - 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"); diff --git a/Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp b/Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp index f37e054c..f8cd5d2f 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,12 +16,30 @@ bool tpagerInit(); using namespace tt::hal; +DeviceVector createDevices() { + auto bq27220 = std::make_shared(I2C_NUM_0); + bq27220->configureCapacity(1500, 1500); + 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", @@ -41,24 +62,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..8e1e51bc 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,17 @@ 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) { + TT_LOG_I(TAG, "Keypress: %c", keypress); 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 +92,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 +119,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 +128,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 +154,6 @@ bool TpagerKeyboard::stopLvgl() { lv_indev_delete(kbHandle); kbHandle = nullptr; - lv_indev_delete(encHandle); - encHandle = nullptr; return true; } @@ -215,81 +161,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 +215,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 5eaee439..98dfa04e 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp @@ -3,14 +3,9 @@ #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() {} @@ -30,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) { @@ -88,13 +77,3 @@ void TpagerPower::powerOff() { bq25896->powerOff(); } } - -static std::shared_ptr power; -extern std::shared_ptr bq27220; - -std::shared_ptr tpager_get_power() { - if (power == nullptr) { - power = std::make_shared(bq27220); - } - return power; -} diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerPower.h b/Boards/LilygoTLoraPager/Source/hal/TpagerPower.h index 9ee3015f..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"; } @@ -23,5 +23,3 @@ public: 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..0a0e0fea 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp @@ -3,8 +3,6 @@ #include #include -#include - using tt::hal::sdcard::SpiSdCardDevice; #define TPAGER_SDCARD_PIN_CS GPIO_NUM_21 diff --git a/Drivers/BQ25896/Source/Bq25896.h b/Drivers/BQ25896/Source/Bq25896.h index 1d3c428e..38fd702d 100644 --- a/Drivers/BQ25896/Source/Bq25896.h +++ b/Drivers/BQ25896/Source/Bq25896.h @@ -8,12 +8,14 @@ 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"; } - explicit Bq25896(i2c_port_t port) : I2cDevice(port, BQ25896_ADDRESS) {} - void powerOff(); void powerOn(); 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..2613f09b --- /dev/null +++ b/Drivers/DRV2605/Source/Drv2605.cpp @@ -0,0 +1,61 @@ +#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 + + 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); + + // 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::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..0219d517 --- /dev/null +++ b/Drivers/DRV2605/Source/Drv2605.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +class Drv2605 : public tt::hal::i2c::I2cDevice { + + static constexpr auto* TAG = "DRV2605"; + static constexpr auto ADDRESS = 0x5A; + + // 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) : I2cDevice(port, ADDRESS) { + if (!init()) { + TT_LOG_E(TAG, "Failed to initialize DRV2605"); + } + } + + 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 setWaveForm(uint8_t slot, uint8_t waveform); + void selectLibrary(uint8_t library); + void startPlayback(); + void stopPlayback(); +}; 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/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 502eeb4e..a6706212 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -13,33 +13,45 @@ namespace tt::app::launcher { constexpr auto* TAG = "Launcher"; constexpr auto BUTTON_SIZE = 64; -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 final : public App { -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_border_width(apps_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(apps_button, 0, LV_PART_MAIN); + 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* 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 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); - lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); + lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); - return apps_button; -} + return apps_button; + } -class LauncherApp : public App { + 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; + } + + static void onAppPressed(TT_UNUSED lv_event_t* e) { + auto* appId = static_cast(lv_event_get_user_data(e)); + service::loader::startApp(appId); + } static void onPowerOffPressed(lv_event_t* e) { auto power = hal::findFirstDevice(hal::Device::Type::Power); @@ -49,6 +61,7 @@ class LauncherApp : public App { } public: + void onCreate(TT_UNUSED AppContext& app) override { BootProperties boot_properties; if (loadBootProperties(boot_properties) && !boot_properties.autoStartAppId.empty()) { @@ -61,7 +74,7 @@ public: auto* buttons_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_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); @@ -88,12 +101,18 @@ public: createAppButton(buttons_wrapper, files_icon_path.c_str(), "Files", margin); createAppButton(buttons_wrapper, settings_icon_path.c_str(), "Settings", margin); - 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); - auto* power_label = lv_label_create(power_button); - lv_label_set_text(power_label, LV_SYMBOL_POWER); + 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/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..1c4c781c 100644 --- a/Tactility/Source/hal/Hal.cpp +++ b/Tactility/Source/hal/Hal.cpp @@ -14,6 +14,52 @@ namespace tt::hal { +void initDevices(const Configuration& configuration) { + 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); + } + + // TODO: Move + auto sdcards = hal::findDevices(Device::Type::SdCard); + if (!sdcards.empty()) { + if (sdcards.size() == 1) { + // Fixed mount path name + auto sdcard = sdcards[0]; + TT_LOG_I(TAG, "Mounting sdcard at %s", TT_SDCARD_MOUNT_POINT); + if (!sdcard->mount(TT_SDCARD_MOUNT_POINT)) { + TT_LOG_W(TAG, "SD card mount failed (init can continue)"); + } + } else { + // Numbered mount path name + for (int i = 0; i < sdcards.size(); i++) { + auto sdcard = sdcards[i]; + std::string mount_path = TT_SDCARD_MOUNT_POINT + std::to_string(i); + TT_LOG_I(TAG, "Mounting sdcard at %d", mount_path.c_str()); + if (!sdcard->mount(mount_path)) { + TT_LOG_W(TAG, "SD card mount failed (init can continue)"); + } + } + } + } +} + void init(const Configuration& configuration) { kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalBegin); @@ -34,18 +80,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); - } + initDevices(configuration); kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalEnd); } diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index f6b73d41..ddfeaddb 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) { 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..e0bb1d48 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -197,7 +197,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) {