Merge develop into main (#313)

- Add app path get() functions to `TactilityC`
- Improved `Dispatcher` and `DispatcherThread`
- Improved `PubSub` (type safety)
- Created test for `DispatcherThread` and `PubSub`
- Save properties files on app exit (various apps) by posting it to the main dispatcher (fixes UI hanging briefly on app exit)
- Fixed bug with `SystemSettings` being read from the wrong file path.
- `loadPropertiesFile()` now uses `file::readLines()` instead of doing that manually
- Increased timer task stack size (required due to issues when reading a properties file for the very first time)
- General cleanup
- Created `EstimatedPower` driver that uses an ADC pin to measure voltage and estimate the battery charge that is left.
- Cleanup of T-Deck board (updated to new style)
This commit is contained in:
Ken Van Hoeylandt
2025-09-01 23:07:00 +02:00
committed by GitHub
parent 5cc5b50694
commit 0f8380e8fe
96 changed files with 766 additions and 682 deletions
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_adc
)
+3
View File
@@ -0,0 +1,3 @@
# EstimatedPower
Use ADC measurements to read voltage and estimate the available power that is left in the battery.
@@ -0,0 +1,67 @@
#include "ChargeFromAdcVoltage.h"
#include <Tactility/Log.h>
#include <algorithm>
constexpr auto TAG = "EstimatePower";
constexpr auto MAX_VOLTAGE_SAMPLES = 15;
uint8_t ChargeFromAdcVoltage::estimateChargeLevelFromVoltage(uint32_t milliVolt) const {
const float volts = std::min((float)milliVolt / 1000.f, configuration.batteryVoltageMax);
const float voltage_percentage = (volts - configuration.batteryVoltageMin) / (configuration.batteryVoltageMax - configuration.batteryVoltageMin);
const float voltage_factor = std::min(1.0f, voltage_percentage);
const 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;
}
ChargeFromAdcVoltage::ChargeFromAdcVoltage(const Configuration& configuration) : configuration(configuration) {
if (adc_oneshot_new_unit(&configuration.adcConfig, &adcHandle) != ESP_OK) {
TT_LOG_E(TAG, "ADC config failed");
return;
}
if (adc_oneshot_config_channel(adcHandle, configuration.adcChannel, &configuration.adcChannelConfig) != ESP_OK) {
TT_LOG_E(TAG, "ADC channel config failed");
adc_oneshot_del_unit(adcHandle);
return;
}
}
ChargeFromAdcVoltage::~ChargeFromAdcVoltage() {
if (adcHandle) {
adc_oneshot_del_unit(adcHandle);
}
}
bool ChargeFromAdcVoltage::readBatteryVoltageOnce(uint32_t& output) const {
int raw;
if (adc_oneshot_read(adcHandle, configuration.adcChannel, &raw) == ESP_OK) {
output = configuration.adcMultiplier * ((1000.f * configuration.adcRefVoltage) / 4096.f) * (float)raw;
TT_LOG_V(TAG, "Raw = %d, voltage = %lu", raw, output);
return true;
} else {
TT_LOG_E(TAG, "Read failed");
return false;
}
}
bool ChargeFromAdcVoltage::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) {
return false;
}
output = sample_accumulator / samples_read;
return true;
}
@@ -0,0 +1,42 @@
#pragma once
#include <esp_adc/adc_oneshot.h>
class ChargeFromAdcVoltage {
public:
struct Configuration {
adc_channel_t adcChannel = ADC_CHANNEL_3;
float adcMultiplier = 1.0f;
float adcRefVoltage = 3.3f;
float batteryVoltageMin = 3.2f;
float batteryVoltageMax = 4.2f;
adc_oneshot_unit_init_cfg_t adcConfig = {
.unit_id = ADC_UNIT_1,
.clk_src = ADC_RTC_CLK_SRC_DEFAULT,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
adc_oneshot_chan_cfg_t adcChannelConfig = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
};
private:
adc_oneshot_unit_handle_t adcHandle = nullptr;
Configuration configuration;
public:
explicit ChargeFromAdcVoltage(const Configuration& configuration);
~ChargeFromAdcVoltage();
uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) const;
bool readBatteryVoltageSampled(uint32_t& output) const;
bool readBatteryVoltageOnce(uint32_t& output) const;
};
@@ -0,0 +1,30 @@
#include "EstimatedPower.h"
bool EstimatedPower::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool EstimatedPower::getMetric(MetricType type, MetricData& data) {
switch (type) {
using enum MetricType;
case BatteryVoltage:
return chargeFromAdcVoltage->readBatteryVoltageSampled(data.valueAsUint32);
case ChargeLevel:
if (chargeFromAdcVoltage->readBatteryVoltageSampled(data.valueAsUint32)) {
data.valueAsUint32 = chargeFromAdcVoltage->estimateChargeLevelFromVoltage(data.valueAsUint32);
return true;
} else {
return false;
}
default:
return false;
}
}
@@ -0,0 +1,27 @@
#pragma once
#include <ChargeFromAdcVoltage.h>
#include <Tactility/hal/power/PowerDevice.h>
using tt::hal::power::PowerDevice;
/**
* Uses Voltage measurements to estimate charge.
* Supports voltage and charge level metrics.
* Can be overridden to further extend supported metrics.
*/
class EstimatedPower final : public PowerDevice {
std::unique_ptr<ChargeFromAdcVoltage> chargeFromAdcVoltage;
public:
EstimatedPower(ChargeFromAdcVoltage::Configuration configuration) :
chargeFromAdcVoltage(std::make_unique<ChargeFromAdcVoltage>(std::move(configuration))) {}
std::string getName() const override { return "ADC Power Measurement"; }
std::string getDescription() const override { return "Power measurement interface via ADC pin"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
};