From 97b8007aca2806251e79248289251cfc0c83e916 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 21 Jan 2025 19:53:00 +0100 Subject: [PATCH] Unphone battery status (#176) --- Boards/UnPhone/Source/UnPhone.cpp | 2 + Boards/UnPhone/Source/hal/UnPhonePower.cpp | 92 ++++++++++++++++++++++ Boards/UnPhone/Source/hal/UnPhonePower.h | 24 ++++++ Boards/UnPhone/Source/hal/UnPhoneTouch.cpp | 21 ++++- Boards/UnPhone/Source/hal/UnPhoneTouch.h | 11 +++ TactilityCore/Source/Log.h | 2 +- 6 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 Boards/UnPhone/Source/hal/UnPhonePower.cpp create mode 100644 Boards/UnPhone/Source/hal/UnPhonePower.h diff --git a/Boards/UnPhone/Source/UnPhone.cpp b/Boards/UnPhone/Source/UnPhone.cpp index eb9bf209..1f725e9e 100644 --- a/Boards/UnPhone/Source/UnPhone.cpp +++ b/Boards/UnPhone/Source/UnPhone.cpp @@ -1,6 +1,7 @@ #include "UnPhoneFeatures.h" #include "hal/Configuration.h" #include "hal/UnPhoneDisplay.h" +#include "hal/UnPhonePower.h" #include "hal/UnPhoneSdCard.h" bool unPhoneInitPower(); @@ -16,6 +17,7 @@ extern const tt::hal::Configuration unPhone = { .initLvgl = unPhoneInitLvgl, .createDisplay = createDisplay, .sdcard = createUnPhoneSdCard(), + .power = unPhoneGetPower, .i2c = { tt::hal::i2c::Configuration { .name = "Internal", diff --git a/Boards/UnPhone/Source/hal/UnPhonePower.cpp b/Boards/UnPhone/Source/hal/UnPhonePower.cpp new file mode 100644 index 00000000..fd104e95 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhonePower.cpp @@ -0,0 +1,92 @@ +#include "UnPhonePower.h" +#include "UnPhoneTouch.h" +#include "Log.h" + +#define TAG "unphone_power" + +#define BATTERY_VOLTAGE_MIN 3.2f +#define BATTERY_VOLTAGE_MAX 4.2f + +static uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) { + float volts = TT_MIN((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX); + float voltage_percentage = (volts - BATTERY_VOLTAGE_MIN) / (BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN); + float voltage_factor = TT_MIN(1.0f, voltage_percentage); + auto charge_level = (uint8_t) (voltage_factor * 100.f); + TT_LOG_V(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level); + return charge_level; +} + +bool UnPhonePower::supportsMetric(MetricType type) const { + switch (type) { + case MetricType::BatteryVoltage: + case MetricType::ChargeLevel: + return true; + default: + return false; + } +} + +bool UnPhonePower::getMetric(Power::MetricType type, Power::MetricData& data) { + switch (type) { + case MetricType::BatteryVoltage: + return readBatteryVoltageSampled(data.valueAsUint32); + case MetricType::ChargeLevel: { + uint32_t milli_volt; + if (readBatteryVoltageSampled(milli_volt)) { + data.valueAsUint8 = estimateChargeLevelFromVoltage(milli_volt); + return true; + } else { + return false; + } + } + default: + return false; + } +} + +bool UnPhonePower::readBatteryVoltageOnce(uint32_t& output) const { + auto* touch = UnPhoneTouch::getInstance(); + if (touch != nullptr) { + float vbat; + if (touch->getVBat(vbat)) { + // Convert to mV + output = (uint32_t)(vbat * 1000.f); + return true; + } + } + + return false; +} + + +#define MAX_VOLTAGE_SAMPLES 15 + +bool UnPhonePower::readBatteryVoltageSampled(uint32_t& output) const { + size_t samples_read = 0; + uint32_t sample_accumulator = 0; + uint32_t sample_read_buffer; + + for (size_t i = 0; i < MAX_VOLTAGE_SAMPLES; ++i) { + if (readBatteryVoltageOnce(sample_read_buffer)) { + sample_accumulator += sample_read_buffer; + samples_read++; + } + } + + if (samples_read > 0) { + output = sample_accumulator / samples_read; + return true; + } else { + return false; + } +} + +static std::shared_ptr power; + +std::shared_ptr unPhoneGetPower() { + if (power == nullptr) { + power = std::make_shared(); + } + return power; +} + diff --git a/Boards/UnPhone/Source/hal/UnPhonePower.h b/Boards/UnPhone/Source/hal/UnPhonePower.h new file mode 100644 index 00000000..040687e8 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhonePower.h @@ -0,0 +1,24 @@ +#pragma once + +#include "hal/Power.h" +#include + +using namespace tt::hal; + +class UnPhonePower : public Power { + +public: + + UnPhonePower() = default; + ~UnPhonePower() = default; + + bool supportsMetric(MetricType type) const override; + bool getMetric(Power::MetricType type, Power::MetricData& data) override; + +private: + + bool readBatteryVoltageOnce(uint32_t& output) const; + bool readBatteryVoltageSampled(uint32_t& output) const; +}; + +std::shared_ptr unPhoneGetPower(); diff --git a/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp b/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp index 3a6c1f54..e2a5a59b 100644 --- a/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp +++ b/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp @@ -1,15 +1,18 @@ #include "UnPhoneTouch.h" -#include "esp_err.h" #include "Log.h" -#include "esp_lvgl_port.h" +#include "esp_err.h" #include "esp_lcd_touch_xpt2046.h" +#include "esp_lvgl_port.h" +#include "lvgl/LvglSync.h" #define TAG "unphone_touch" #define UNPHONE_TOUCH_X_MAX 320 #define UNPHONE_TOUCH_Y_MAX 480 +UnPhoneTouch* UnPhoneTouch::instance = nullptr; + bool UnPhoneTouch::start(lv_display_t* display) { const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(GPIO_NUM_38); @@ -57,10 +60,12 @@ bool UnPhoneTouch::start(lv_display_t* display) { return false; } + instance = this; return true; } bool UnPhoneTouch::stop() { + instance = nullptr; cleanup(); return true; } @@ -81,3 +86,15 @@ void UnPhoneTouch::cleanup() { ioHandle = nullptr; } } + +bool UnPhoneTouch::getVBat(float& outputVbat) { + if (touchHandle != nullptr) { + // Shares the SPI bus with the display, so we have to sync/lock as this method might be called from anywhere + if (tt::lvgl::lock(50 / portTICK_PERIOD_MS)) { + esp_lcd_touch_xpt2046_read_battery_level(touchHandle, &outputVbat); + tt::lvgl::unlock(); + return true; + } + } + return false; +} diff --git a/Boards/UnPhone/Source/hal/UnPhoneTouch.h b/Boards/UnPhone/Source/hal/UnPhoneTouch.h index 3fa85d82..57afbf1b 100644 --- a/Boards/UnPhone/Source/hal/UnPhoneTouch.h +++ b/Boards/UnPhone/Source/hal/UnPhoneTouch.h @@ -6,13 +6,24 @@ #include "esp_lcd_touch.h" class UnPhoneTouch : public tt::hal::Touch { + private: + + static UnPhoneTouch* instance; + esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr; esp_lcd_touch_handle_t _Nullable touchHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr; void cleanup(); + public: + bool start(lv_display_t* display) override; bool stop() override; lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } + + bool getVBat(float& outputVbat); + + /** Used for accessing getVBat() in Power driver */ + static UnPhoneTouch* getInstance() { return instance; } }; diff --git a/TactilityCore/Source/Log.h b/TactilityCore/Source/Log.h index 19062ba3..4ee78c59 100644 --- a/TactilityCore/Source/Log.h +++ b/TactilityCore/Source/Log.h @@ -72,7 +72,7 @@ void log(LogLevel level, const char* tag, const char* format, ...); tt::log(tt::LogLevel::Info, tag, format, ##__VA_ARGS__) #define TT_LOG_D(tag, format, ...) \ tt::log(tt::LogLevel::Debug, tag, format, ##__VA_ARGS__) -#define TT_LOG_T(tag, format, ...) \ +#define TT_LOG_V(tag, format, ...) \ tt::log(tt::LogLevel::Trace, tag, format, ##__VA_ARGS__) #endif // ESP_TARGET