Various fixes and improvements

This commit is contained in:
Ken Van Hoeylandt 2025-08-29 21:53:52 +02:00
parent e9f72490fc
commit 4579ef8648
26 changed files with 150 additions and 120 deletions

View File

@ -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"

View File

@ -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 PwmBacklight driver esp_adc
)

View File

@ -1,22 +1,23 @@
#include "PwmBacklight.h"
#include "Tactility/kernel/SystemEvents.h"
#include "Tactility/service/gps/GpsService.h"
#include <Tactility/TactilityCore.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/hal/gps/GpsConfiguration.h>
#include <driver/gpio.h>
#include <PwmBacklight.h>
#include <Bq25896.h>
#include <Bq27220.h>
#include <Tca8418.h>
#define TAG "tpager"
#define TAG "TLoraPager"
// Power on
#define TDECK_POWERON_GPIO GPIO_NUM_10
constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10;
std::shared_ptr<Bq27220> bq27220;
std::shared_ptr<Tca8418> tca8418;
std::shared_ptr<Bq25896> bq25896;
bool tpagerInit() {
ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START);
@ -35,6 +36,10 @@ bool tpagerInit() {
tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
tt::hal::registerDevice(tca8418);
bq25896 = std::make_shared<Bq25896>(I2C_NUM_0);
tt::hal::registerDevice(bq25896);
bq25896->powerOn();
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
bq27220->configureCapacity(1500, 1500);
@ -51,5 +56,6 @@ bool tpagerInit() {
}
}
});
return true;
}

View File

@ -1,5 +1,6 @@
#include "TpagerPower.h"
#include <Bq25896.h>
#include <Tactility/Log.h>
#define TAG "power"
@ -46,7 +47,6 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) {
return true;
}
return false;
break;
case Current:
if (gauge->getCurrent(s16)) {
data.valueAsInt32 = s16;
@ -54,7 +54,6 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) {
} else {
return false;
}
break;
case BatteryVoltage:
if (gauge->getVoltage(u16)) {
data.valueAsUint32 = u16;
@ -62,7 +61,6 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) {
} else {
return false;
}
break;
case ChargeLevel:
if (gauge->getStateOfCharge(u16)) {
data.valueAsUint8 = u16;
@ -70,13 +68,25 @@ bool TpagerPower::getMetric(MetricType type, MetricData& data) {
} else {
return false;
}
break;
default:
return false;
break;
}
}
void TpagerPower::powerOff() {
auto device = tt::hal::findDevice([](auto device) {
return device->getName() == "BQ25896";
});
if (device == nullptr) {
TT_LOG_E(TAG, "BQ25896 not found");
return;
}
return false; // Safety guard for when new enum values are introduced
auto bq25896 = std::reinterpret_pointer_cast<Bq25896>(device);
if (bq25896 != nullptr) {
bq25896->powerOff();
}
}
static std::shared_ptr<PowerDevice> power;

View File

@ -20,7 +20,8 @@ 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<PowerDevice> tpager_get_power();

View File

@ -1,2 +1,2 @@
language=en-US
language=nl-NL
timeFormat24h=true

View File

@ -1,3 +0,0 @@
Apps
Files
Settings

View File

@ -1,3 +0,0 @@
Apps
Files
Settings

View File

@ -1,3 +0,0 @@
Appli
Fichiers
Réglages

View File

@ -1,3 +0,0 @@
Apps
Bestanden
Instellingen

View File

@ -1,3 +0,0 @@
Apps
Bestanden
Instellingen

View File

@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility
)

View File

@ -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)

View File

@ -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));
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <Tactility/hal/i2c/I2cDevice.h>
constexpr auto BQ25896_ADDRESS = 0x6b;
class Bq25896 final : public tt::hal::i2c::I2cDevice {
public:
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();
};

View File

@ -1,3 +1,4 @@
# ST7796
A basic ESP32 LVGL driver for ST7796 displays.

View File

@ -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;

View File

@ -4,7 +4,6 @@
#include <Tactility/hal/spi/Spi.h>
#include <EspLcdDisplay.h>
#include <driver/gpio.h>
#include <functional>
class St7796Display final : public EspLcdDisplay {

View File

@ -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

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;

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,54 +6,49 @@
#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);
auto* appId = static_cast<const char*>(lv_event_get_user_data(e));
service::loader::startApp(appId);
}
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);
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);
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);
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
// 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, 64, 64);
lv_obj_set_size(button_image, BUTTON_SIZE, BUTTON_SIZE);
auto* label = lv_label_create(wrapper);
lv_label_set_text(label, title);
lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, 0);
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);
return wrapper;
return apps_button;
}
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;
if (loadBootProperties(boot_properties) && !boot_properties.autoStartAppId.empty()) {
@ -64,41 +58,42 @@ 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);
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);
}
};

View File

@ -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();

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 {

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;

Binary file not shown.

View File

@ -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