fix: MCP settings persistence + WebServer/DevServer coexistence + screensaver charging toggle
- 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.
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
|
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
|
||||||
# Ensure large enough stack for network operations
|
# Ensure large enough stack for network operations
|
||||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
|
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
|
||||||
|
# HTTP server: need enough URI handler slots for WebServer (7 + internal) + DevServer
|
||||||
|
CONFIG_HTTPD_MAX_URI_HANDLERS=20
|
||||||
# Fixes static assertion: FLASH and PSRAM Mode configuration are not supported
|
# Fixes static assertion: FLASH and PSRAM Mode configuration are not supported
|
||||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||||
# Free up IRAM
|
# Free up IRAM
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${SOURCE_FILES}
|
SRCS ${SOURCE_FILES}
|
||||||
INCLUDE_DIRS "Source"
|
INCLUDE_DIRS "Source"
|
||||||
REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 PwmBacklight driver vfs fatfs
|
REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 PwmBacklight driver vfs fatfs esp_adc
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "PwmBacklight.h"
|
#include "PwmBacklight.h"
|
||||||
#include "devices/Display.h"
|
#include "devices/Display.h"
|
||||||
|
#include "devices/Power.h"
|
||||||
#include <driver/gpio.h>
|
#include <driver/gpio.h>
|
||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
@@ -82,7 +83,8 @@ static bool initBoot() {
|
|||||||
|
|
||||||
static DeviceVector createDevices() {
|
static DeviceVector createDevices() {
|
||||||
return {
|
return {
|
||||||
createDisplay()
|
createDisplay(),
|
||||||
|
createPower()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
#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>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Tactility/hal/power/PowerDevice.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
|
||||||
@@ -29,6 +29,7 @@ struct DisplaySettings {
|
|||||||
bool backlightTimeoutEnabled;
|
bool backlightTimeoutEnabled;
|
||||||
uint32_t backlightTimeoutMs; // 0 = Never
|
uint32_t backlightTimeoutMs; // 0 = Never
|
||||||
ScreensaverType screensaverType = ScreensaverType::BouncingBalls;
|
ScreensaverType screensaverType = ScreensaverType::BouncingBalls;
|
||||||
|
bool disableScreensaverWhenCharging = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Compares default settings with the function parameter to return the difference */
|
/** Compares default settings with the function parameter to return the difference */
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ class DevelopmentService final : public Service {
|
|||||||
.handler = handleAppUninstall,
|
.handler = handleAppUninstall,
|
||||||
.user_ctx = this
|
.user_ctx = this
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
8192
|
||||||
);
|
);
|
||||||
|
|
||||||
void startServer();
|
void startServer();
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ class DisplayApp final : public App {
|
|||||||
lv_obj_t* timeoutSwitch = nullptr;
|
lv_obj_t* timeoutSwitch = nullptr;
|
||||||
lv_obj_t* timeoutDropdown = nullptr;
|
lv_obj_t* timeoutDropdown = nullptr;
|
||||||
lv_obj_t* screensaverDropdown = nullptr;
|
lv_obj_t* screensaverDropdown = nullptr;
|
||||||
|
lv_obj_t* disableWhenChargingWrapper = nullptr;
|
||||||
|
lv_obj_t* disableWhenChargingSwitch = nullptr;
|
||||||
|
|
||||||
static void onBacklightSliderEvent(lv_event_t* event) {
|
static void onBacklightSliderEvent(lv_event_t* event) {
|
||||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||||
@@ -95,15 +97,29 @@ class DisplayApp final : public App {
|
|||||||
if (app->screensaverDropdown) {
|
if (app->screensaverDropdown) {
|
||||||
lv_obj_clear_state(app->screensaverDropdown, LV_STATE_DISABLED);
|
lv_obj_clear_state(app->screensaverDropdown, LV_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
|
if (app->disableWhenChargingWrapper) {
|
||||||
|
lv_obj_clear_state(app->disableWhenChargingWrapper, LV_STATE_DISABLED);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lv_obj_add_state(app->timeoutDropdown, LV_STATE_DISABLED);
|
lv_obj_add_state(app->timeoutDropdown, LV_STATE_DISABLED);
|
||||||
if (app->screensaverDropdown) {
|
if (app->screensaverDropdown) {
|
||||||
lv_obj_add_state(app->screensaverDropdown, LV_STATE_DISABLED);
|
lv_obj_add_state(app->screensaverDropdown, LV_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
|
if (app->disableWhenChargingWrapper) {
|
||||||
|
lv_obj_add_state(app->disableWhenChargingWrapper, LV_STATE_DISABLED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void onDisableWhenChargingChanged(lv_event_t* event) {
|
||||||
|
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
|
||||||
|
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||||
|
bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED);
|
||||||
|
app->displaySettings.disableScreensaverWhenCharging = enabled;
|
||||||
|
app->displaySettingsUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
static void onTimeoutChanged(lv_event_t* event) {
|
static void onTimeoutChanged(lv_event_t* event) {
|
||||||
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
|
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
|
||||||
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||||
@@ -294,6 +310,26 @@ public:
|
|||||||
if (!displaySettings.backlightTimeoutEnabled) {
|
if (!displaySettings.backlightTimeoutEnabled) {
|
||||||
lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED);
|
lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable screensaver when charging toggle
|
||||||
|
disableWhenChargingWrapper = lv_obj_create(main_wrapper);
|
||||||
|
lv_obj_set_size(disableWhenChargingWrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_style_pad_all(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_border_width(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
auto* charging_label = lv_label_create(disableWhenChargingWrapper);
|
||||||
|
lv_label_set_text(charging_label, "Disable on charging");
|
||||||
|
lv_obj_align(charging_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||||
|
|
||||||
|
disableWhenChargingSwitch = lv_switch_create(disableWhenChargingWrapper);
|
||||||
|
if (displaySettings.disableScreensaverWhenCharging) {
|
||||||
|
lv_obj_add_state(disableWhenChargingSwitch, LV_STATE_CHECKED);
|
||||||
|
}
|
||||||
|
lv_obj_align(disableWhenChargingSwitch, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||||
|
lv_obj_add_event_cb(disableWhenChargingSwitch, onDisableWhenChargingChanged, LV_EVENT_VALUE_CHANGED, this);
|
||||||
|
if (!displaySettings.backlightTimeoutEnabled) {
|
||||||
|
lv_obj_add_state(disableWhenChargingWrapper, LV_STATE_DISABLED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCalibratableTouchDevice()) {
|
if (hasCalibratableTouchDevice()) {
|
||||||
|
|||||||
@@ -1700,7 +1700,12 @@ static void mcp_video_stream_task(void* pvParameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(5));
|
// Yield: longer delay when no client connected to save CPU
|
||||||
|
if (mono_client_fd < 0 && color_client_fd < 0) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
} else {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mono_client_fd >= 0) close(mono_client_fd);
|
if (mono_client_fd >= 0) close(mono_client_fd);
|
||||||
@@ -1730,7 +1735,7 @@ bool startVideoStreamServer() {
|
|||||||
BaseType_t ret = xTaskCreatePinnedToCore(
|
BaseType_t ret = xTaskCreatePinnedToCore(
|
||||||
mcp_video_stream_task,
|
mcp_video_stream_task,
|
||||||
"mcp_video_stream",
|
"mcp_video_stream",
|
||||||
4096,
|
8192,
|
||||||
nullptr,
|
nullptr,
|
||||||
5,
|
5,
|
||||||
(TaskHandle_t*)&state.streamTaskHandle,
|
(TaskHandle_t*)&state.streamTaskHandle,
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ bool HttpServer::startInternal() {
|
|||||||
config.server_port = port;
|
config.server_port = port;
|
||||||
config.uri_match_fn = matchUri;
|
config.uri_match_fn = matchUri;
|
||||||
config.max_uri_handlers = handlers.size() + INTERNAL_URI_HANDLER_COUNT;
|
config.max_uri_handlers = handlers.size() + INTERNAL_URI_HANDLER_COUNT;
|
||||||
|
// Assign unique ctrl port based on server port to avoid collision when
|
||||||
|
// multiple HTTP servers (e.g. WebServer 80 + DevServer 6666) start simultaneously.
|
||||||
|
// ESP-IDF default ctrl_port is 32768; we offset by server port.
|
||||||
|
config.ctrl_port = static_cast<uint16_t>(32768 + (port % 1000));
|
||||||
|
|
||||||
if (httpd_start(&server, &config) != ESP_OK) {
|
if (httpd_start(&server, &config) != ESP_OK) {
|
||||||
LOGGER.error("Failed to start http server on port {}", port);
|
LOGGER.error("Failed to start http server on port {} (ctrl_port {})", port, config.ctrl_port);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/CoreDefines.h>
|
#include <Tactility/CoreDefines.h>
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <Tactility/hal/power/PowerDevice.h>
|
||||||
#include <Tactility/lvgl/LvglSync.h>
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
#include <Tactility/service/ServiceContext.h>
|
#include <Tactility/service/ServiceContext.h>
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
@@ -30,6 +31,22 @@ static std::shared_ptr<hal::display::DisplayDevice> getDisplay() {
|
|||||||
return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isDeviceCharging() {
|
||||||
|
bool charging = false;
|
||||||
|
hal::findDevices<hal::power::PowerDevice>(hal::Device::Type::Power, [&charging](const auto& power) {
|
||||||
|
if (!power->supportsMetric(hal::power::PowerDevice::MetricType::IsCharging)) {
|
||||||
|
return true; // continue
|
||||||
|
}
|
||||||
|
hal::power::PowerDevice::MetricData data;
|
||||||
|
if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, data) && data.valueAsBool) {
|
||||||
|
charging = true;
|
||||||
|
return false; // stop iter
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return charging;
|
||||||
|
}
|
||||||
|
|
||||||
void DisplayIdleService::stopScreensaverCb(lv_event_t* e) {
|
void DisplayIdleService::stopScreensaverCb(lv_event_t* e) {
|
||||||
auto* self = static_cast<DisplayIdleService*>(lv_event_get_user_data(e));
|
auto* self = static_cast<DisplayIdleService*>(lv_event_get_user_data(e));
|
||||||
self->stopScreensaverLocked();
|
self->stopScreensaverLocked();
|
||||||
@@ -178,43 +195,53 @@ void DisplayIdleService::tick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto display = getDisplay();
|
auto display = getDisplay();
|
||||||
if (display != nullptr && display->supportsBacklightDuty()) {
|
bool supportsBacklight = display != nullptr && display->supportsBacklightDuty();
|
||||||
|
|
||||||
|
if (supportsBacklight) {
|
||||||
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) {
|
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) {
|
||||||
if (displayDimmed) {
|
if (displayDimmed) {
|
||||||
display->setBacklightDuty(cachedDisplaySettings.backlightDuty);
|
display->setBacklightDuty(cachedDisplaySettings.backlightDuty);
|
||||||
displayDimmed = false;
|
displayDimmed = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
bool charging_blocks = cachedDisplaySettings.disableScreensaverWhenCharging && isDeviceCharging();
|
||||||
|
|
||||||
if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) {
|
if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) {
|
||||||
if (!lvgl::lock(100)) {
|
if (charging_blocks) {
|
||||||
return; // Retry on next tick
|
// Skip screensaver while charging
|
||||||
|
} else {
|
||||||
|
if (!lvgl::lock(100)) {
|
||||||
|
return; // Retry on next tick
|
||||||
|
}
|
||||||
|
activateScreensaver();
|
||||||
|
lvgl::unlock();
|
||||||
|
if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
|
||||||
|
display->setBacklightDuty(0);
|
||||||
|
}
|
||||||
|
displayDimmed = true;
|
||||||
}
|
}
|
||||||
activateScreensaver();
|
} else if (displayDimmed) {
|
||||||
lvgl::unlock();
|
if (inactive_ms < kWakeActivityThresholdMs) {
|
||||||
// Turn off backlight for "None" screensaver (just black screen)
|
stopScreensaver();
|
||||||
if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
|
} else if (charging_blocks) {
|
||||||
display->setBacklightDuty(0);
|
stopScreensaver();
|
||||||
}
|
}
|
||||||
displayDimmed = true;
|
|
||||||
} else if (displayDimmed && (inactive_ms < kWakeActivityThresholdMs)) {
|
|
||||||
stopScreensaver();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DisplayIdleService::onStart(ServiceContext& service) {
|
bool DisplayIdleService::onStart(ServiceContext& service) {
|
||||||
auto display = getDisplay();
|
|
||||||
if (display != nullptr && !display->supportsBacklightDuty()) {
|
|
||||||
LOGGER.info("Monochrome/RLCD display detected (no backlight control): disabling screensaver tick timer");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seed random number generator for varied screensaver patterns
|
// Seed random number generator for varied screensaver patterns
|
||||||
srand(static_cast<unsigned int>(time(nullptr)));
|
srand(static_cast<unsigned int>(time(nullptr)));
|
||||||
|
|
||||||
cachedDisplaySettings = settings::display::loadOrGetDefault();
|
cachedDisplaySettings = settings::display::loadOrGetDefault();
|
||||||
|
|
||||||
|
auto display = getDisplay();
|
||||||
|
if (display != nullptr && !display->supportsBacklightDuty()) {
|
||||||
|
LOGGER.info("Monochrome/RLCD display detected (no backlight control): idle timer will run but auto-backlight off is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
timer = std::make_unique<Timer>(Timer::Type::Periodic, kernel::millisToTicks(TICK_INTERVAL_MS), [this]{ this->tick(); });
|
timer = std::make_unique<Timer>(Timer::Type::Periodic, kernel::millisToTicks(TICK_INTERVAL_MS), [this]{ this->tick(); });
|
||||||
timer->setCallbackPriority(Thread::Priority::Lower);
|
timer->setCallbackPriority(Thread::Priority::Lower);
|
||||||
timer->start();
|
timer->start();
|
||||||
@@ -297,10 +324,12 @@ void DisplayIdleService::startMcpScreensaver() {
|
|||||||
screensaverActiveCounter = 0;
|
screensaverActiveCounter = 0;
|
||||||
backlightOff = false;
|
backlightOff = false;
|
||||||
|
|
||||||
// Ensure backlight is active and set to configured duty cycle
|
// Ensure backlight is active if the display supports it
|
||||||
auto display = getDisplay();
|
auto display = getDisplay();
|
||||||
if (display) {
|
if (display != nullptr && display->supportsBacklightDuty()) {
|
||||||
display->setBacklightDuty(cachedDisplaySettings.backlightDuty);
|
uint8_t duty = cachedDisplaySettings.backlightDuty;
|
||||||
|
if (duty == 0) duty = 255; // ensure visible if settings not loaded / default
|
||||||
|
display->setBacklightDuty(duty);
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr);
|
lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ constexpr auto* SETTINGS_KEY_BACKLIGHT_DUTY = "backlightDuty";
|
|||||||
constexpr auto* SETTINGS_KEY_TIMEOUT_ENABLED = "backlightTimeoutEnabled";
|
constexpr auto* SETTINGS_KEY_TIMEOUT_ENABLED = "backlightTimeoutEnabled";
|
||||||
constexpr auto* SETTINGS_KEY_TIMEOUT_MS = "backlightTimeoutMs";
|
constexpr auto* SETTINGS_KEY_TIMEOUT_MS = "backlightTimeoutMs";
|
||||||
constexpr auto* SETTINGS_KEY_SCREENSAVER_TYPE = "screensaverType";
|
constexpr auto* SETTINGS_KEY_SCREENSAVER_TYPE = "screensaverType";
|
||||||
|
constexpr auto* SETTINGS_KEY_DISABLE_WHEN_CHARGING = "disableScreensaverWhenCharging";
|
||||||
|
|
||||||
static Orientation getDefaultOrientation() {
|
static Orientation getDefaultOrientation() {
|
||||||
auto* display = lv_display_get_default();
|
auto* display = lv_display_get_default();
|
||||||
@@ -164,12 +165,19 @@ bool load(DisplaySettings& settings) {
|
|||||||
fromString(screensaver_entry->second, screensaver_type);
|
fromString(screensaver_entry->second, screensaver_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool disable_when_charging = false;
|
||||||
|
auto disable_charging_entry = map.find(SETTINGS_KEY_DISABLE_WHEN_CHARGING);
|
||||||
|
if (disable_charging_entry != map.end()) {
|
||||||
|
disable_when_charging = (disable_charging_entry->second == "1" || disable_charging_entry->second == "true" || disable_charging_entry->second == "True");
|
||||||
|
}
|
||||||
|
|
||||||
settings.orientation = orientation;
|
settings.orientation = orientation;
|
||||||
settings.gammaCurve = gamma_curve;
|
settings.gammaCurve = gamma_curve;
|
||||||
settings.backlightDuty = backlight_duty;
|
settings.backlightDuty = backlight_duty;
|
||||||
settings.backlightTimeoutEnabled = timeout_enabled;
|
settings.backlightTimeoutEnabled = timeout_enabled;
|
||||||
settings.backlightTimeoutMs = timeout_ms;
|
settings.backlightTimeoutMs = timeout_ms;
|
||||||
settings.screensaverType = screensaver_type;
|
settings.screensaverType = screensaver_type;
|
||||||
|
settings.disableScreensaverWhenCharging = disable_when_charging;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -181,7 +189,8 @@ DisplaySettings getDefault() {
|
|||||||
.backlightDuty = 200,
|
.backlightDuty = 200,
|
||||||
.backlightTimeoutEnabled = false,
|
.backlightTimeoutEnabled = false,
|
||||||
.backlightTimeoutMs = 60000,
|
.backlightTimeoutMs = 60000,
|
||||||
.screensaverType = ScreensaverType::BouncingBalls
|
.screensaverType = ScreensaverType::BouncingBalls,
|
||||||
|
.disableScreensaverWhenCharging = false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +210,7 @@ bool save(const DisplaySettings& settings) {
|
|||||||
map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
|
map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
|
||||||
map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
|
map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
|
||||||
map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType);
|
map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType);
|
||||||
|
map[SETTINGS_KEY_DISABLE_WHEN_CHARGING] = settings.disableScreensaverWhenCharging ? "1" : "0";
|
||||||
auto settings_path = getSettingsFilePath();
|
auto settings_path = getSettingsFilePath();
|
||||||
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2,19 +2,27 @@
|
|||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace tt::settings::mcp {
|
namespace tt::settings::mcp {
|
||||||
|
|
||||||
static const auto LOGGER = Logger("McpSettings");
|
static const auto LOGGER = Logger("McpSettings");
|
||||||
constexpr auto* SETTINGS_FILE = "/data/service/mcp/settings.properties";
|
|
||||||
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/mcp.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* KEY_MCP_ENABLED = "mcpEnabled";
|
constexpr auto* KEY_MCP_ENABLED = "mcpEnabled";
|
||||||
|
|
||||||
bool load(McpSettings& settings) {
|
bool load(McpSettings& settings) {
|
||||||
|
auto settings_path = getSettingsFilePath();
|
||||||
|
if (!file::isFile(settings_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(settings_path, map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,20 +44,25 @@ McpSettings loadOrGetDefault() {
|
|||||||
McpSettings settings;
|
McpSettings settings;
|
||||||
if (!load(settings)) {
|
if (!load(settings)) {
|
||||||
settings = getDefault();
|
settings = getDefault();
|
||||||
file::findOrCreateDirectory("/data/service/mcp", 0755);
|
if (!save(settings)) {
|
||||||
save(settings);
|
LOGGER.warn("Failed to save default MCP settings");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool save(const McpSettings& settings) {
|
bool save(const McpSettings& settings) {
|
||||||
file::findOrCreateDirectory("/data/service/mcp", 0755);
|
|
||||||
|
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
map[KEY_MCP_ENABLED] = settings.mcpEnabled ? "true" : "false";
|
map[KEY_MCP_ENABLED] = settings.mcpEnabled ? "true" : "false";
|
||||||
|
|
||||||
if (!file::savePropertiesFile(SETTINGS_FILE, map)) {
|
auto settings_path = getSettingsFilePath();
|
||||||
LOGGER.error("Failed to save MCP settings to {}", SETTINGS_FILE);
|
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
||||||
|
LOGGER.error("Failed to create parent dir for {}", settings_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file::savePropertiesFile(settings_path, map)) {
|
||||||
|
LOGGER.error("Failed to save MCP settings to {}", settings_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user