2dc216fb7b
- McpSettings was hardcoded to /data/service/mcp/settings.properties, but ES3C28P uses storage.userDataLocation=SD so user data lives on /sdcard/tactility/. Settings appeared to save but on reload returned default false -> "enable and when I go back shows disabled". Fixed to use getUserDataPath()+"/settings/mcp.properties" pattern consistent with DisplaySettings/WebServerSettings, with findOrCreateParentDirectory() and isFile() guard. - HttpServer ctrl_port collision: ESP-IDF default 32768 used by both WebServer (80) and DevelopmentService (6666). Second httpd_start() failed silently -> dashboard up but /api/mcp 404 and :6666 refused. Fixed with unique ctrl_port = 32768 + (port % 1000). Added CONFIG_HTTPD_MAX_URI_HANDLERS=20 (default 8 < 9 needed for 7 handlers + 2 internal). - DevService stack 5120 -> 8192 for /app/install handling. - McpSystem video stream task stack 4096 -> 8192 and idle yield 50ms (was 5ms always) to avoid CPU starvation blocking httpd tasks. On RLCD, timer must always run: DisplayIdle early return on !supportsBacklightDuty() broke MCP manual activation (cachedSettings not loaded, backlight duty 0). Fixed to always load settings + start timer, log RLCD detection, guard setBacklightDuty with supportsBacklightDuty() and fallback duty 255. - DisplaySettings: add disableScreensaverWhenCharging bool, persisted, with UI toggle in Display app (Settings->Display) using PowerDevice::IsCharging check via findDevices callback. When charging and flag enabled, screensaver activation skipped, and if already dimmed, wake on charging. - ES3C28P Power driver: GPIO9 ADC1_CH8 2x divider 2500-4200mV spec from ~/Downloads official docs, TP4054 CHRG not connected to GPIO, so IsCharging inferred via voltage thresholds (<500mV no battery USB or >5000mV wall powered, >=4150mV high) + rising trend. Verified: flashed es3c28p to /dev/cu.usbmodem101 (192.168.68.113), WiFi FamReynaMesh RSSI -59, dashboard 200, :6666/info now works, /api/mcp returns disabled correctly until enabled, persists to SD.
157 lines
5.6 KiB
C++
157 lines
5.6 KiB
C++
#include "Power.h"
|
|
#include <Tactility/Logger.h>
|
|
#include <esp_adc/adc_oneshot.h>
|
|
#include <esp_adc/adc_cali.h>
|
|
#include <esp_adc/adc_cali_scheme.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
|
|
static const auto* TAG = "ES3C28P_Power";
|
|
|
|
// HW: BAT+ -> R14 200K + R15 200K -> GND => BAT_ADC middle, 2x divider, to GPIO9 ADC1_CH8
|
|
// Charger TP4054 CHRG pin only drives LED, not connected to ESP32 GPIO.
|
|
// So we infer charging by voltage behavior: rising trend and high voltage.
|
|
|
|
class Es3c28pPower final : public tt::hal::power::PowerDevice {
|
|
public:
|
|
Es3c28pPower() {
|
|
adc_oneshot_unit_init_cfg_t init_cfg = {
|
|
.unit_id = ADC_UNIT_1,
|
|
.clk_src = ADC_RTC_CLK_SRC_DEFAULT,
|
|
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
|
};
|
|
if (adc_oneshot_new_unit(&init_cfg, &adc_handle) != ESP_OK) {
|
|
ESP_LOGE(TAG, "ADC unit init failed");
|
|
return;
|
|
}
|
|
adc_oneshot_chan_cfg_t chan_cfg = {
|
|
.atten = ADC_ATTEN_DB_12,
|
|
.bitwidth = ADC_BITWIDTH_12,
|
|
};
|
|
if (adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_8, &chan_cfg) != ESP_OK) {
|
|
ESP_LOGE(TAG, "ADC channel config failed");
|
|
adc_oneshot_del_unit(adc_handle);
|
|
adc_handle = nullptr;
|
|
return;
|
|
}
|
|
adc_cali_curve_fitting_config_t cali_cfg = {
|
|
.unit_id = ADC_UNIT_1,
|
|
.atten = ADC_ATTEN_DB_12,
|
|
.bitwidth = ADC_BITWIDTH_12,
|
|
};
|
|
if (adc_cali_create_scheme_curve_fitting(&cali_cfg, &cali_handle) != ESP_OK) {
|
|
ESP_LOGW(TAG, "ADC cali scheme failed");
|
|
cali_handle = nullptr;
|
|
}
|
|
ESP_LOGI(TAG, "Battery ADC GPIO9 ADC1_CH8, 2x divider, cali %s", cali_handle ? "OK" : "none");
|
|
}
|
|
|
|
~Es3c28pPower() override {
|
|
if (cali_handle) adc_cali_delete_scheme_curve_fitting(cali_handle);
|
|
if (adc_handle) adc_oneshot_del_unit(adc_handle);
|
|
}
|
|
|
|
std::string getName() const override { return "ES3C28P Power"; }
|
|
std::string getDescription() const override { return "Battery GPIO9 ADC1_CH8 + TP4054 inferred charging"; }
|
|
|
|
bool supportsMetric(MetricType type) const override {
|
|
switch (type) {
|
|
case MetricType::BatteryVoltage:
|
|
case MetricType::ChargeLevel:
|
|
case MetricType::IsCharging:
|
|
return adc_handle != nullptr;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool getMetric(MetricType type, MetricData& data) override {
|
|
if (!adc_handle) return false;
|
|
|
|
int raw = 0;
|
|
int mv = 0;
|
|
if (adc_oneshot_read(adc_handle, ADC_CHANNEL_8, &raw) != ESP_OK) return false;
|
|
if (cali_handle) {
|
|
if (adc_cali_raw_to_voltage(cali_handle, raw, &mv) != ESP_OK) return false;
|
|
} else {
|
|
mv = (raw * 3300) / 4095;
|
|
}
|
|
int vbat_mv = mv * 2; // 200K/200K divider
|
|
|
|
// Track voltage trend for charging detection
|
|
const int now_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
|
if (last_read_ms == 0) {
|
|
last_vbat_mv = vbat_mv;
|
|
last_read_ms = now_ms;
|
|
}
|
|
|
|
// Update trend every 2 seconds to avoid noise
|
|
if (now_ms - last_read_ms > 2000) {
|
|
// If voltage increased by at least 10mV in 2s, likely charging
|
|
if (vbat_mv > last_vbat_mv + 10) {
|
|
rising_count++;
|
|
falling_count = 0;
|
|
} else if (vbat_mv < last_vbat_mv - 10) {
|
|
falling_count++;
|
|
rising_count = 0;
|
|
if (falling_count > 3) is_charging_trend = false;
|
|
}
|
|
last_vbat_mv = vbat_mv;
|
|
last_read_ms = now_ms;
|
|
}
|
|
|
|
switch (type) {
|
|
case MetricType::BatteryVoltage:
|
|
data.valueAsUint32 = (uint32_t)vbat_mv;
|
|
return true;
|
|
case MetricType::ChargeLevel: {
|
|
uint8_t pct;
|
|
if (vbat_mv <= 2500) pct = 0;
|
|
else if (vbat_mv >= 4200) pct = 100;
|
|
else pct = (uint8_t)((vbat_mv - 2500) / 17);
|
|
data.valueAsUint8 = pct;
|
|
return true;
|
|
}
|
|
case MetricType::IsCharging: {
|
|
// Cases:
|
|
// 1. No battery / wall powered: vbat <500mV or >5000mV (VBUS via MOSFET) => charging true
|
|
if (vbat_mv < 500 || vbat_mv > 5000) {
|
|
data.valueAsBool = true;
|
|
is_charging_trend = true;
|
|
return true;
|
|
}
|
|
// 2. High voltage near full with charger attached: >=4150mV => charging (charger holds at 4.2V)
|
|
if (vbat_mv >= 4150) {
|
|
data.valueAsBool = true;
|
|
is_charging_trend = true;
|
|
return true;
|
|
}
|
|
// 3. Rising trend indicates charging
|
|
if (rising_count >= 2) {
|
|
is_charging_trend = true;
|
|
}
|
|
// 4. If previously charging and voltage still >=4000, keep charging until drop
|
|
if (is_charging_trend && vbat_mv >= 4000) {
|
|
data.valueAsBool = true;
|
|
return true;
|
|
}
|
|
data.valueAsBool = is_charging_trend;
|
|
return true;
|
|
}
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
private:
|
|
adc_oneshot_unit_handle_t adc_handle = nullptr;
|
|
adc_cali_handle_t cali_handle = nullptr;
|
|
int last_vbat_mv = 0;
|
|
int last_read_ms = 0;
|
|
int rising_count = 0;
|
|
int falling_count = 0;
|
|
bool is_charging_trend = false;
|
|
};
|
|
|
|
std::shared_ptr<tt::hal::power::PowerDevice> createPower() {
|
|
return std::make_shared<Es3c28pPower>();
|
|
}
|