Merge branch 'feat/autopoweroff-percent' into 956/prs

This commit is contained in:
956MB
2026-07-06 04:04:26 -05:00
7 changed files with 314 additions and 51 deletions
@@ -11,7 +11,6 @@
#define TAG "Power"
#define POWER_OFF_TIMEOUT_S (90U)
#define POWER_POLL_PERIOD_MS (1000UL)
#define POWER_VBUS_LOW_THRESHOLD (4.0f)
@@ -313,10 +312,15 @@ static void power_check_low_battery(Power* power) {
return;
}
const uint8_t timeout_s = power_off_timeout_seconds(power->settings.power_off_timeout);
// Check battery charge and vbus voltage
if((power->info.is_shutdown_requested) &&
(power->info.voltage_vbus < POWER_VBUS_LOW_THRESHOLD) && power->show_battery_low_warning) {
if(!power->battery_low) {
power->power_off_timeout = timeout_s;
power_off_set_variant(power->view_power_off, PowerOffVariantLowBattery);
power_off_set_time_left(power->view_power_off, power->power_off_timeout);
view_holder_send_to_front(power->view_holder);
view_holder_set_view(power->view_holder, power_off_get_view(power->view_power_off));
}
@@ -325,7 +329,7 @@ static void power_check_low_battery(Power* power) {
if(power->battery_low) {
// view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
view_holder_set_view(power->view_holder, NULL);
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
power->power_off_timeout = timeout_s;
}
power->battery_low = false;
}
@@ -362,6 +366,61 @@ static void power_check_battery_level_change(Power* power) {
}
}
static void power_check_auto_poweroff(Power* power) {
const uint8_t timeout_s = power_off_timeout_seconds(power->settings.power_off_timeout);
if(power->auto_poweroff_warning) {
if(power->info.is_charging) {
view_holder_set_view(power->view_holder, NULL);
power->auto_poweroff_warning = false;
power->power_off_timeout = timeout_s;
return;
}
PowerOffResponse response = power_off_get_response(power->view_power_off);
if(response == PowerOffResponseDefault) {
if(power->power_off_timeout) {
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
} else {
power_off(power);
}
} else if(response == PowerOffResponseOk) {
power_off(power);
} else if(response == PowerOffResponseCancel) {
view_holder_set_view(power->view_holder, NULL);
power->auto_poweroff_warning = false;
uint8_t next = (power->info.charge > POWER_AUTO_POWEROFF_PERCENT_STEP) ?
(uint8_t)(power->info.charge - POWER_AUTO_POWEROFF_PERCENT_STEP) :
0;
power->auto_poweroff_next_warning = next;
power->power_off_timeout = timeout_s;
}
return;
}
if(power->settings.auto_poweroff_mode != PowerAutoPoweroffModePercent) {
return;
}
if(!power->info.gauge_is_ok || power->settings.auto_poweroff_percent == 0) {
return;
}
if(power->info.is_charging) {
if(power->info.charge >= power->settings.auto_poweroff_percent) {
power->auto_poweroff_next_warning = power->settings.auto_poweroff_percent;
}
return;
}
if(power->info.charge <= power->auto_poweroff_next_warning) {
power->auto_poweroff_warning = true;
power->power_off_timeout = timeout_s;
power_off_set_variant(power->view_power_off, PowerOffVariantAutoPoweroff);
power_off_set_time_left(power->view_power_off, power->power_off_timeout);
view_holder_send_to_front(power->view_holder);
view_holder_set_view(power->view_holder, power_off_get_view(power->view_power_off));
}
}
static void power_handle_shutdown(Power* power) {
furi_hal_power_off();
// Notify user if USB is plugged
@@ -540,11 +599,15 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) {
case PowerMessageTypeSetSettings:
furi_assert(msg.lock);
power->settings = *msg.csettings;
power->auto_poweroff_next_warning = power->settings.auto_poweroff_percent;
power->auto_poweroff_warning = false;
power_settings_apply(power);
power_settings_save(&power->settings);
break;
case PowerMessageTypeReloadSettings:
power_settings_load(&power->settings);
power->auto_poweroff_next_warning = power->settings.auto_poweroff_percent;
power->auto_poweroff_warning = false;
power_settings_apply(power);
break;
default:
@@ -588,6 +651,8 @@ static void power_tick_callback(void* context) {
power_check_battery_level_change(power);
// charge supress arm/disarm
power_charge_supress(power);
// auto poweroff by battery percentage arm/disarm
power_check_auto_poweroff(power);
// Update battery view port
view_port_enabled_set(
power->battery_view_port, momentum_settings.battery_icon != BatteryIconOff);
@@ -635,6 +700,7 @@ static void power_init_settings(Power* power) {
}
power_settings_load(&power->settings);
power->auto_poweroff_next_warning = power->settings.auto_poweroff_percent;
power_settings_apply(power);
furi_record_close(RECORD_STORAGE);
power->charge_is_supressed = false;
@@ -645,8 +711,10 @@ static Power* power_alloc(void) {
// Pubsub
power->event_pubsub = furi_pubsub_alloc();
// State initialization
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
power->power_off_timeout = power_off_timeout_seconds(PowerOffTimeout90);
power->show_battery_low_warning = true;
power->auto_poweroff_next_warning = 0;
power->auto_poweroff_warning = false;
// Gui
Gui* gui = furi_record_open(RECORD_GUI);
@@ -19,6 +19,18 @@ typedef enum {
PowerStateCharged,
} PowerState;
static inline uint8_t power_off_timeout_seconds(PowerOffTimeout t) {
switch(t) {
case PowerOffTimeout30:
return 30;
case PowerOffTimeout60:
return 60;
case PowerOffTimeout90:
default:
return 90;
}
}
struct Power {
ViewHolder* view_holder;
FuriPubSub* event_pubsub;
@@ -35,6 +47,8 @@ struct Power {
bool battery_low;
bool show_battery_low_warning;
bool auto_poweroff_warning;
uint8_t auto_poweroff_next_warning;
bool is_otg_requested;
uint8_t battery_level;
uint8_t power_off_timeout;
@@ -65,6 +79,8 @@ typedef enum {
PowerMessageTypeGetSettings,
PowerMessageTypeSetSettings,
PowerMessageTypeReloadSettings,
PowerMessageTypeTestWarning,
} PowerMessageType;
typedef struct {
@@ -6,14 +6,20 @@
#define TAG "PowerSettings"
#define POWER_SETTINGS_VER_1 (1) // Previous version number
#define POWER_SETTINGS_VER (2) // New version number
#define POWER_SETTINGS_VER_1 (1) // Oldest version
#define POWER_SETTINGS_VER_2 (2) // Charge-supress added
#define POWER_SETTINGS_VER (3) // Percentage + warning timeout added
#define POWER_SETTINGS_MAGIC (0x21)
typedef struct {
uint32_t auto_poweroff_delay_ms;
} PowerSettingsPrevious;
} PowerSettingsV1;
typedef struct {
uint32_t auto_poweroff_delay_ms;
uint8_t charge_supress_percent;
} PowerSettingsV2;
void power_settings_load(PowerSettings* settings) {
furi_assert(settings);
@@ -24,7 +30,7 @@ void power_settings_load(PowerSettings* settings) {
uint8_t version;
if(!saved_struct_get_metadata(POWER_SETTINGS_PATH, NULL, &version, NULL)) break;
// if config actual version - load it directly
// v3 - load it directly
if(version == POWER_SETTINGS_VER) {
success = saved_struct_load(
POWER_SETTINGS_PATH,
@@ -33,23 +39,45 @@ void power_settings_load(PowerSettings* settings) {
POWER_SETTINGS_MAGIC,
POWER_SETTINGS_VER);
// if config previous version - load it and manual set new settings to inital value
} else if(version == POWER_SETTINGS_VER_1) {
PowerSettingsPrevious* settings_previous = malloc(sizeof(PowerSettingsPrevious));
// v2 -> v3
} else if(version == POWER_SETTINGS_VER_2) {
PowerSettingsV2* prev = malloc(sizeof(PowerSettingsV2));
success = saved_struct_load(
POWER_SETTINGS_PATH,
settings_previous,
sizeof(PowerSettingsPrevious),
prev,
sizeof(PowerSettingsV2),
POWER_SETTINGS_MAGIC,
POWER_SETTINGS_VER_2);
if(success) {
settings->auto_poweroff_mode = prev->auto_poweroff_delay_ms ?
PowerAutoPoweroffModeTimer :
PowerAutoPoweroffModeOff;
settings->auto_poweroff_delay_ms = prev->auto_poweroff_delay_ms;
settings->charge_supress_percent = prev->charge_supress_percent;
settings->auto_poweroff_percent = 0;
settings->power_off_timeout = PowerOffTimeout90;
}
free(prev);
// v1 -> v3
} else if(version == POWER_SETTINGS_VER_1) {
PowerSettingsV1* prev = malloc(sizeof(PowerSettingsV1));
success = saved_struct_load(
POWER_SETTINGS_PATH,
prev,
sizeof(PowerSettingsV1),
POWER_SETTINGS_MAGIC,
POWER_SETTINGS_VER_1);
// new settings initialization
if(success) {
settings->auto_poweroff_delay_ms = settings_previous->auto_poweroff_delay_ms;
settings->auto_poweroff_mode = prev->auto_poweroff_delay_ms ?
PowerAutoPoweroffModeTimer :
PowerAutoPoweroffModeOff;
settings->auto_poweroff_delay_ms = prev->auto_poweroff_delay_ms;
settings->charge_supress_percent = 0;
settings->auto_poweroff_percent = 0;
settings->power_off_timeout = PowerOffTimeout90;
}
free(settings_previous);
free(prev);
}
} while(false);
@@ -57,7 +85,7 @@ void power_settings_load(PowerSettings* settings) {
if(!success) {
FURI_LOG_W(TAG, "Failed to load file, using defaults");
memset(settings, 0, sizeof(PowerSettings));
// power_settings_save(settings);
settings->power_off_timeout = PowerOffTimeout90;
}
}
@@ -3,9 +3,26 @@
#include <stdint.h>
#include <stdbool.h>
typedef enum {
PowerAutoPoweroffModeOff,
PowerAutoPoweroffModeTimer,
PowerAutoPoweroffModePercent,
} PowerAutoPoweroffMode;
typedef enum {
PowerOffTimeout30,
PowerOffTimeout60,
PowerOffTimeout90,
} PowerOffTimeout;
#define POWER_AUTO_POWEROFF_PERCENT_STEP (5U)
typedef struct {
PowerAutoPoweroffMode auto_poweroff_mode;
uint32_t auto_poweroff_delay_ms;
uint8_t charge_supress_percent;
uint8_t auto_poweroff_percent;
PowerOffTimeout power_off_timeout;
} PowerSettings;
void power_settings_load(PowerSettings* settings);
@@ -9,34 +9,61 @@ struct PowerOff {
typedef struct {
PowerOffResponse response;
PowerOffVariant variant;
uint32_t time_left_sec;
} PowerOffModel;
static void power_off_draw_callback(Canvas* canvas, void* _model) {
furi_assert(_model);
PowerOffModel* model = _model;
static const struct {
const char* header;
const char* bubble_primary;
const char* bubble_secondary;
const char* bottom_hint;
} strings[2] = {
[PowerOffVariantLowBattery] =
{.header = "Battery low!",
.bubble_primary = "Charge me!",
.bubble_secondary = "Don't forget!",
.bottom_hint = "Hold a second..."},
[PowerOffVariantAutoPoweroff] =
{.header = "Auto PowerOff",
.bubble_primary = "Battery low!",
.bubble_secondary = "Cancelled",
.bottom_hint = "Charge to reset"},
};
const char* header = strings[model->variant].header;
const char* bubble_primary = strings[model->variant].bubble_primary;
const char* bubble_secondary = strings[model->variant].bubble_secondary;
const char* bottom_hint = strings[model->variant].bottom_hint;
char buff[32];
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 1, AlignCenter, AlignTop, "Battery low!");
canvas_draw_str_aligned(canvas, 64, 1, AlignCenter, AlignTop, header);
canvas_draw_icon(canvas, 0, 18, &I_BatteryBody_52x28);
canvas_draw_icon(canvas, 16, 25, &I_FaceNopower_29x14);
elements_bubble(canvas, 54, 17, 70, 30);
canvas_set_font(canvas, FontSecondary);
if(model->response == PowerOffResponseDefault) {
snprintf(buff, sizeof(buff), "Charge me!\nOff in %lus!", model->time_left_sec);
snprintf(buff, sizeof(buff), "%s\nOff in %lus", bubble_primary, model->time_left_sec);
elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff);
elements_button_left(canvas, "Cancel");
elements_button_center(canvas, "OK");
elements_button_right(canvas, "Hide");
if(model->variant == PowerOffVariantLowBattery) {
elements_button_right(canvas, "Hide");
}
} else {
snprintf(buff, sizeof(buff), "Charge me!\nDon't forget!");
snprintf(buff, sizeof(buff), "%s\n%s", bubble_primary, bubble_secondary);
elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff);
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, "Hold a second...");
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, bottom_hint);
}
}
@@ -71,6 +98,12 @@ PowerOff* power_off_alloc(void) {
view_set_draw_callback(power_off->view, power_off_draw_callback);
view_set_input_callback(power_off->view, power_off_input_callback);
with_view_model(
power_off->view,
PowerOffModel * model,
{ model->variant = PowerOffVariantLowBattery; },
false);
return power_off;
}
@@ -91,6 +124,19 @@ void power_off_set_time_left(PowerOff* power_off, uint8_t time_left) {
power_off->view, PowerOffModel * model, { model->time_left_sec = time_left; }, true);
}
void power_off_set_variant(PowerOff* power_off, PowerOffVariant variant) {
furi_assert(power_off);
with_view_model(
power_off->view,
PowerOffModel * model,
{
model->variant = variant;
// Reset response so the warning UI shows the countdown on each new trigger
model->response = PowerOffResponseDefault;
},
true);
}
PowerOffResponse power_off_get_response(PowerOff* power_off) {
furi_assert(power_off);
PowerOffResponse response;
@@ -9,6 +9,11 @@ typedef enum {
PowerOffResponseHide,
} PowerOffResponse;
typedef enum {
PowerOffVariantLowBattery,
PowerOffVariantAutoPoweroff,
} PowerOffVariant;
#include <gui/view.h>
PowerOff* power_off_alloc(void);
@@ -19,4 +24,6 @@ View* power_off_get_view(PowerOff* power_off);
void power_off_set_time_left(PowerOff* power_off, uint8_t time_left);
void power_off_set_variant(PowerOff* power_off, PowerOffVariant variant);
PowerOffResponse power_off_get_response(PowerOff* power_off);
@@ -9,24 +9,11 @@ enum PowerSettingsSubmenuIndex {
PowerSettingsSubmenuIndexAutoPowerOff,
};
#define AUTO_POWEROFF_DELAY_COUNT 13
const char* const auto_poweroff_delay_text[AUTO_POWEROFF_DELAY_COUNT] = {
"OFF",
"5min",
"10min",
"15min",
"30min",
"45min",
"60min",
"90min",
"2h",
"6h",
"12h",
"24h",
"48h"};
#define AUTO_POWEROFF_DELAY_COUNT 12
const char* const auto_poweroff_delay_text[AUTO_POWEROFF_DELAY_COUNT] =
{"5min", "10min", "15min", "30min", "45min", "60min", "90min", "2h", "6h", "12h", "24h", "48h"};
const uint32_t auto_poweroff_delay_value[AUTO_POWEROFF_DELAY_COUNT] = {
0,
300000,
600000,
900000,
@@ -40,7 +27,21 @@ const uint32_t auto_poweroff_delay_value[AUTO_POWEROFF_DELAY_COUNT] = {
86400000,
172800000};
#define CHARGE_SUPRESS_STEP 5
#define AUTO_POWEROFF_PERCENT_STEP 5
#define AUTO_POWEROFF_PERCENT_MAX 95
#define CHARGE_SUPRESS_STEP 5
static const char* const auto_poweroff_mode_text[3] = {
"OFF",
"Timer",
"%",
};
static const char* const power_off_timeout_text[3] = {
[PowerOffTimeout30] = "30s",
[PowerOffTimeout60] = "60s",
[PowerOffTimeout90] = "90s",
};
// change variable_item_list visible text and charge_supress_percent_settings when user change item in variable_item_list
static void power_settings_scene_start_charge_supress_percent_changed(VariableItem* item) {
@@ -53,6 +54,20 @@ static void power_settings_scene_start_charge_supress_percent_changed(VariableIt
app->settings.charge_supress_percent = value == 100 ? 0 : value;
}
// change variable_item_list visible text and auto_poweroff_mode_settings when user change item in variable_item_list
static void power_settings_scene_start_auto_poweroff_mode_changed(VariableItem* item) {
PowerSettingsApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->settings.auto_poweroff_mode = (PowerAutoPoweroffMode)index;
variable_item_set_current_value_text(item, auto_poweroff_mode_text[index]);
// Force a rebuild so the Duration / Percentage sub-item swaps in/out.
scene_manager_set_scene_state(
app->scene_manager, PowerSettingsAppSceneStart, PowerSettingsSubmenuIndexAutoPowerOff);
power_settings_scene_start_on_enter(app);
}
// change variable_item_list visible text and app_poweroff_delay_time_settings when user change item in variable_item_list
static void power_settings_scene_start_auto_poweroff_delay_changed(VariableItem* item) {
PowerSettingsApp* app = variable_item_get_context(item);
@@ -62,6 +77,26 @@ static void power_settings_scene_start_auto_poweroff_delay_changed(VariableItem*
app->settings.auto_poweroff_delay_ms = auto_poweroff_delay_value[index];
}
// change variable_item_list visible text and auto_poweroff_percent_settings when user change item in variable_item_list
static void power_settings_scene_start_auto_poweroff_percent_changed(VariableItem* item) {
PowerSettingsApp* app = variable_item_get_context(item);
uint8_t value = (variable_item_get_current_value_index(item) + 1) * AUTO_POWEROFF_PERCENT_STEP;
char buf[6];
snprintf(buf, sizeof(buf), "%u%%", value);
variable_item_set_current_value_text(item, buf);
app->settings.auto_poweroff_percent = value;
}
// change variable_item_list visible text and power_off_timeout_settings when user change item in variable_item_list
static void power_settings_scene_start_power_off_timeout_changed(VariableItem* item) {
PowerSettingsApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->settings.power_off_timeout = (PowerOffTimeout)index;
variable_item_set_current_value_text(item, power_off_timeout_text[index]);
}
static void power_settings_scene_start_submenu_callback(void* context, uint32_t index) {
//show selected menu screen by index
furi_assert(context);
@@ -76,6 +111,8 @@ void power_settings_scene_start_on_enter(void* context) {
VariableItem* item;
uint8_t value_index;
variable_item_list_reset(variable_item_list);
variable_item_list_add(variable_item_list, "Battery Info", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Reboot", 1, NULL, NULL);
@@ -85,16 +122,56 @@ void power_settings_scene_start_on_enter(void* context) {
item = variable_item_list_add(
variable_item_list,
"Auto PowerOff",
AUTO_POWEROFF_DELAY_COUNT,
power_settings_scene_start_auto_poweroff_delay_changed,
3,
power_settings_scene_start_auto_poweroff_mode_changed,
app);
value_index = value_index_uint32(
app->settings.auto_poweroff_delay_ms,
auto_poweroff_delay_value,
AUTO_POWEROFF_DELAY_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, auto_poweroff_delay_text[value_index]);
variable_item_set_current_value_index(item, app->settings.auto_poweroff_mode);
variable_item_set_current_value_text(
item, auto_poweroff_mode_text[app->settings.auto_poweroff_mode]);
if(app->settings.auto_poweroff_mode == PowerAutoPoweroffModeTimer) {
item = variable_item_list_add(
variable_item_list,
"Duration",
AUTO_POWEROFF_DELAY_COUNT,
power_settings_scene_start_auto_poweroff_delay_changed,
app);
value_index = value_index_uint32(
app->settings.auto_poweroff_delay_ms,
auto_poweroff_delay_value,
AUTO_POWEROFF_DELAY_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, auto_poweroff_delay_text[value_index]);
} else if(app->settings.auto_poweroff_mode == PowerAutoPoweroffModePercent) {
item = variable_item_list_add(
variable_item_list,
"Percentage",
AUTO_POWEROFF_PERCENT_MAX / AUTO_POWEROFF_PERCENT_STEP,
power_settings_scene_start_auto_poweroff_percent_changed,
app);
uint8_t pct = app->settings.auto_poweroff_percent;
if(pct < AUTO_POWEROFF_PERCENT_STEP) pct = AUTO_POWEROFF_PERCENT_STEP;
if(pct > AUTO_POWEROFF_PERCENT_MAX) pct = AUTO_POWEROFF_PERCENT_MAX;
value_index = (pct / AUTO_POWEROFF_PERCENT_STEP) - 1;
char buf[6];
snprintf(buf, sizeof(buf), "%u%%", pct);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, buf);
}
item = variable_item_list_add(
variable_item_list,
"Warning Timeout",
3,
power_settings_scene_start_power_off_timeout_changed,
app);
variable_item_set_current_value_index(item, app->settings.power_off_timeout);
variable_item_set_current_value_text(
item, power_off_timeout_text[app->settings.power_off_timeout]);
item = variable_item_list_add(
variable_item_list,
@@ -124,12 +201,16 @@ bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event)
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PowerSettingsSubmenuIndexBatteryInfo) {
switch(event.event) {
case PowerSettingsSubmenuIndexBatteryInfo:
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneBatteryInfo);
} else if(event.event == PowerSettingsSubmenuIndexReboot) {
break;
case PowerSettingsSubmenuIndexReboot:
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneReboot);
} else if(event.event == PowerSettingsSubmenuIndexOff) {
break;
case PowerSettingsSubmenuIndexOff:
scene_manager_next_scene(app->scene_manager, PowerSettingsAppScenePowerOff);
break;
}
scene_manager_set_scene_state(app->scene_manager, PowerSettingsAppSceneStart, event.event);
consumed = true;