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)
This commit is contained in:
Ken Van Hoeylandt
2025-08-30 21:54:55 +02:00
committed by GitHub
parent e9f72490fc
commit 50007ea9ed
69 changed files with 957 additions and 577 deletions
@@ -17,6 +17,10 @@ typedef std::shared_ptr<display::DisplayDevice> (*CreateDisplay)();
typedef std::shared_ptr<keyboard::KeyboardDevice> (*CreateKeyboard)();
typedef std::shared_ptr<power::PowerDevice> (*CreatePower)();
typedef std::vector<std::shared_ptr<Device>> DeviceVector;
typedef std::shared_ptr<Device> (*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<sdcard::SdCardDevice> _Nullable sdcard = nullptr;
/** An optional power interface for battery or other power delivery. */
[[deprecated("use createDevices")]]
const CreatePower _Nullable power = nullptr;
std::function<DeviceVector()> createDevices = [] { return std::vector<std::shared_ptr<Device>>(); };
/** A list of I2C interface configurations */
const std::vector<i2c::Configuration> i2c = {};
+11 -1
View File
@@ -20,6 +20,7 @@ public:
Touch,
SdCard,
Keyboard,
Encoder,
Power,
Gps
};
@@ -95,7 +96,16 @@ std::vector<std::shared_ptr<DeviceType>> findDevices(Device::Type type) {
}
}
void findDevices(Device::Type type, std::function<bool(const std::shared_ptr<Device>&)> onDeviceFound);
template<class DeviceType>
void findDevices(Device::Type type, std::function<bool(const std::shared_ptr<DeviceType>&)> onDeviceFound) {
auto devices_view = findDevices(type);
for (auto& device : devices_view) {
auto typed_device = std::static_pointer_cast<DeviceType>(device);
if (!onDeviceFound(typed_device)) {
break;
}
}
}
/** Find the first device of the specified type and cast it to the specified class */
template<class DeviceType>
@@ -0,0 +1,23 @@
#pragma once
#include "../Device.h"
#include <lvgl.h>
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;
};
}
@@ -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;
@@ -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
@@ -0,0 +1,7 @@
#pragma once
namespace tt::hal::sdcard {
void mountAll();
}
+7 -7
View File
@@ -1,16 +1,16 @@
#include "Tactility/Tactility.h"
#include "Tactility/app/AppRegistration.h"
#include "Tactility/lvgl/LvglPrivate.h"
#include "Tactility/service/ServiceManifest.h"
#include <Tactility/Tactility.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/TactilityConfig.h>
#include <Tactility/TactilityHeadless.h>
#include <Tactility/lvgl/LvglPrivate.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/loader/Loader.h>
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);
}
}
+3 -2
View File
@@ -6,6 +6,7 @@
#include "Tactility/service/ServiceRegistration.h"
#include <Tactility/Dispatcher.h>
#include <Tactility/hal/sdcard/SdCardMounting.h>
#include <Tactility/settings/TimePrivate.h>
#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;
}
+4 -3
View File
@@ -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<hal::display::DisplayDevice> getHalDisplay() {
return hal::findFirstDevice<hal::display::DisplayDevice>(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();
+76 -62
View File
@@ -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 <lvgl.h>
#include <Tactility/BootProperties.h>
#include <Tactility/hal/power/PowerDevice.h>
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::power::PowerDevice>(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<const char*>(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::power::PowerDevice>(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<int32_t>(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);
}
}
};
@@ -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();
-9
View File
@@ -88,15 +88,6 @@ std::vector<std::shared_ptr<Device>> findDevices(Device::Type type) {
});
}
void findDevices(Device::Type type, std::function<bool(const std::shared_ptr<Device>&)> onDeviceFound) {
auto devices_view = findDevices(type);
for (auto& device : devices_view) {
if (!onDeviceFound(device)) {
break;
}
}
}
std::vector<std::shared_ptr<Device>> getDevices() {
return devices;
}
+28 -16
View File
@@ -8,12 +8,35 @@
#include <Tactility/kernel/SystemEvents.h>
#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::PowerDevice> power = configuration.power();
registerDevice(power);
}
if (configuration.createKeyboard) {
auto keyboard = configuration.createKeyboard();
if (keyboard != nullptr) {
registerDevice(std::reinterpret_pointer_cast<Device>(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::PowerDevice> power = configuration.power();
registerDevice(power);
}
registerDevices(configuration);
kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalEnd);
}
+7 -7
View File
@@ -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, &reg, 1, &result, 1, DEFAULT_TIMEOUT);
return masterWriteRead(port, address, &reg, 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 {
@@ -0,0 +1,40 @@
#include <Tactility/hal/sdcard/SdCardMounting.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
namespace tt::hal::sdcard {
constexpr auto* TAG = "SdCardMounting";
constexpr auto* TT_SDCARD_MOUNT_POINT = "/sdcard";
static void mount(const std::shared_ptr<SdCardDevice>& 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<SdCardDevice>(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);
}
}
}
}
}
}
+16 -17
View File
@@ -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<sdcard::SpiSdCardDevice>(Device::Type::SdCard);
std::shared_ptr<sdcard::SpiSdCardDevice> usable_sdcard;
for (auto& sdcard : sdcards) {
auto sdcard_candidate = std::static_pointer_cast<sdcard::SpiSdCardDevice>(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::SpiSdCardDevice>(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() {
+88 -53
View File
@@ -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 <Tactility/app/display/DisplaySettings.h>
#include <Tactility/hal/Configuration.h>
#include <Tactility/hal/encoder/EncoderDevice.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/lvgl/Keyboard.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/TactilityHeadless.h>
#ifdef ESP_PLATFORM
#include "Tactility/lvgl/EspLvglPort.h"
#include <Tactility/lvgl/EspLvglPort.h>
#endif
#include <lvgl.h>
#include <Tactility/TactilityHeadless.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/service/ServiceRegistration.h>
namespace tt::lvgl {
#define TAG "Lvgl"
constexpr auto* TAG = "Lvgl";
static bool started = false;
static std::shared_ptr<hal::display::DisplayDevice> 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::display::DisplayDevice>(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::display::DisplayDevice>(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::touch::TouchDevice>(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::keyboard::KeyboardDevice>(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::encoder::EncoderDevice>(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::keyboard::KeyboardDevice>(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::touch::TouchDevice>(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::encoder::EncoderDevice>(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::display::DisplayDevice>(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
+6 -6
View File
@@ -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<Statusbar*>(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;
+11 -12
View File
@@ -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::sdcard::SdCardDevice>(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<SdCardService>(manifest.id);
updateTimer = std::make_unique<Timer>(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<SdCardService>(manifest.id);
updateTimer = std::make_unique<Timer>(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 {
@@ -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<hal::power::PowerDevice> power;
hal::findDevices<hal::power::PowerDevice>(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::sdcard::SdCardDevice>(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) {