[FL-1783] Power service refactoring (#718)

* settings power: introduce power settings app
* power: move power API to separate file
* settings power: implement reboot scene
* settings power: add power off scene
* assets: add crying dolphin, fix Subghz assets names
* settings power: fix power off scene GUI
* settings power: add battery info scene
* power: add cli to application on start hook
* power: remove power from main menu
* power: move to power service folder
* power service: rework power off logic
* power: add pubsub events
* bt: subscribe to battery level change, update characteristic
* application: change order of Settings applications
* gui: add bubble element
* power: gui improvements
* application: rename Notification -> LCD and notifications
* Applications: menu order according to documentation and add missing power cli init
* settings power: add disconnect USB scene
* power cli: notify user to disconnect USB after poweroff
* Power: update poweroff message in cli

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
gornekich 2021-09-24 19:28:02 +03:00 committed by GitHub
parent c64052b491
commit d3b58f732f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1298 additions and 798 deletions

30
applications/applications.c Normal file → Executable file
View file

@ -46,12 +46,14 @@ extern void lfrfid_cli_init();
extern void nfc_cli_init();
extern void storage_cli_init();
extern void subghz_cli_init();
extern void power_cli_init();
// Settings
extern int32_t notification_settings_app(void* p);
extern int32_t storage_settings_app(void* p);
extern int32_t bt_settings_app(void* p);
extern int32_t about_settings_app(void* p);
extern int32_t power_settings_app(void* p);
const FlipperApplication FLIPPER_SERVICES[] = {
/* Services */
@ -143,18 +145,14 @@ const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperA
// Main menu APP
const FlipperApplication FLIPPER_APPS[] = {
#ifdef APP_IBUTTON
{.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14},
#ifdef APP_SUBGHZ
{.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14},
#endif
#ifdef APP_NFC
{.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14},
#endif
#ifdef APP_SUBGHZ
{.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14},
#endif
#ifdef APP_LF_RFID
{.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14},
#endif
@ -163,6 +161,10 @@ const FlipperApplication FLIPPER_APPS[] = {
{.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14},
#endif
#ifdef APP_IBUTTON
{.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14},
#endif
#ifdef APP_GPIO_TEST
{.app = gpio_test_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14},
#endif
@ -192,6 +194,9 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = {
#ifdef SRV_BT
bt_cli_init,
#endif
#ifdef SRV_POWER
power_cli_init,
#endif
#ifdef SRV_STORAGE
storage_cli_init,
#endif
@ -258,16 +263,23 @@ const FlipperApplication FLIPPER_ARCHIVE =
// Settings menu
const FlipperApplication FLIPPER_SETTINGS_APPS[] = {
#ifdef SRV_BT
{.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL},
#endif
#ifdef SRV_NOTIFICATION
{.app = notification_settings_app, .name = "Notification", .stack_size = 1024, .icon = NULL},
{.app = notification_settings_app,
.name = "LCD and notifications",
.stack_size = 1024,
.icon = NULL},
#endif
#ifdef SRV_STORAGE
{.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL},
#endif
#ifdef SRV_BT
{.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL},
#ifdef SRV_POWER
{.app = power_settings_app, .name = "Power", .stack_size = 1024, .icon = NULL},
#endif
#ifdef APP_ABOUT

View file

@ -26,6 +26,17 @@ static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) {
string_clear(pin_str);
}
static void bt_battery_level_changed_callback(const void* _event, void* context) {
furi_assert(_event);
furi_assert(context);
Bt* bt = context;
const PowerEvent* event = _event;
if(event->type == PowerEventTypeBatteryLevelChanged) {
bt_update_battery_level(bt, event->data.battery_level);
}
}
Bt* bt_alloc() {
Bt* bt = furi_alloc(sizeof(Bt));
// Load settings
@ -45,6 +56,11 @@ Bt* bt_alloc() {
bt->dialogs = furi_record_open("dialogs");
bt->dialog_message = dialog_message_alloc();
// Power
bt->power = furi_record_open("power");
PubSub* power_pubsub = power_get_pubsub(bt->power);
subscribe_pubsub(power_pubsub, bt_battery_level_changed_callback, bt);
return bt;
}

View file

@ -9,7 +9,8 @@
#include <gui/view_port.h>
#include <gui/view.h>
#include <applications/dialogs/dialogs.h>
#include <dialogs/dialogs.h>
#include <power/power_service/power.h>
#include "../bt_settings.h"
@ -36,4 +37,5 @@ struct Bt {
ViewPort* statusbar_view_port;
DialogsApp* dialogs;
DialogMessage* dialog_message;
Power* power;
};

View file

@ -327,6 +327,17 @@ void elements_slightly_rounded_box(
canvas_draw_rbox(canvas, x, y, width, height, 1);
}
void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
furi_assert(canvas);
canvas_draw_rframe(canvas, x + 4, y, width, height, 3);
uint8_t y_corner = y + height * 2 / 3;
canvas_draw_line(canvas, x, y_corner, x + 4, y_corner - 4);
canvas_draw_line(canvas, x, y_corner, x + 4, y_corner + 4);
canvas_set_color(canvas, ColorWhite);
canvas_draw_line(canvas, x + 4, y_corner - 3, x + 4, y_corner + 3);
canvas_set_color(canvas, ColorBlack);
}
void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) {
furi_assert(canvas);
furi_assert(string);

View file

@ -8,8 +8,7 @@
extern "C" {
#endif
/*
* Draw progress bar.
/** Draw progress bar.
* @param x - progress bar position on X axis
* @param y - progress bar position on Y axis
* @param width - progress bar width
@ -24,8 +23,7 @@ void elements_progress_bar(
uint8_t progress,
uint8_t total);
/*
* Draw scrollbar on canvas at specific position.
/** Draw scrollbar on canvas at specific position.
* @param x - scrollbar position on X axis
* @param y - scrollbar position on Y axis
* @param height - scrollbar height
@ -40,41 +38,35 @@ void elements_scrollbar_pos(
uint16_t pos,
uint16_t total);
/*
* Draw scrollbar on canvas.
/** Draw scrollbar on canvas.
* width 3px, height equal to canvas height
* @param pos - current element of total elements
* @param total - total elements
*/
void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total);
/*
* Draw rounded frame
/** Draw rounded frame
* @param x, y - top left corner coordinates
* @param width, height - frame width and height
*/
void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
/*
* Draw button in left corner
/** Draw button in left corner
* @param str - button text
*/
void elements_button_left(Canvas* canvas, const char* str);
/*
* Draw button in right corner
/** Draw button in right corner
* @param str - button text
*/
void elements_button_right(Canvas* canvas, const char* str);
/*
* Draw button in center
/** Draw button in center
* @param str - button text
*/
void elements_button_center(Canvas* canvas, const char* str);
/*
* Draw aligned multiline text
/** Draw aligned multiline text
* @param x, y - coordinates based on align param
* @param horizontal, vertical - aligment of multiline text
* @param text - string (possible multiline)
@ -87,22 +79,19 @@ void elements_multiline_text_aligned(
Align vertical,
const char* text);
/*
* Draw multiline text
/** Draw multiline text
* @param x, y - top left corner coordinates
* @param text - string (possible multiline)
*/
void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text);
/*
* Draw framed multiline text
/** Draw framed multiline text
* @param x, y - top left corner coordinates
* @param text - string (possible multiline)
*/
void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text);
/*
* Draw slightly rounded frame
/** Draw slightly rounded frame
* @param x, y - top left corner coordinates
* @param width, height - size of frame
*/
@ -113,8 +102,7 @@ void elements_slightly_rounded_frame(
uint8_t width,
uint8_t height);
/*
* Draw slightly rounded box
/** Draw slightly rounded box
* @param x, y - top left corner coordinates
* @param width, height - size of box
*/
@ -125,8 +113,15 @@ void elements_slightly_rounded_box(
uint8_t width,
uint8_t height);
/*
* Trim string buffer to fit width in pixels
/** Draw bubble frame for text
* @param x - left x coordinates
* @param y - top y coordinate
* @param width - bubble width
* @param height - bubble height
*/
void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
/** Trim string buffer to fit width in pixels
* @param string - string to trim
* @param width - max width
*/

View file

@ -1,278 +0,0 @@
#include "power.h"
#include "power_cli.h"
#include "power_views.h"
#include <furi.h>
#include <furi-hal.h>
#include <menu/menu.h>
#include <menu/menu_item.h>
#include <gui/gui.h>
#include <gui/icon_animation.h>
#include <gui/view_port.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog.h>
#include <assets_icons.h>
#include <stm32wbxx.h>
#include <notification/notification-messages.h>
#include <applications/bt/bt_service/bt.h>
#define POWER_OFF_TIMEOUT 30
typedef enum {
PowerStateNotCharging,
PowerStateCharging,
PowerStateCharged,
} PowerState;
struct Power {
ViewDispatcher* view_dispatcher;
View* info_view;
View* off_view;
View* disconnect_view;
ViewPort* battery_view_port;
Dialog* dialog;
ValueMutex* menu_vm;
Cli* cli;
Bt* bt;
MenuItem* menu;
PowerState state;
};
void power_draw_battery_callback(Canvas* canvas, void* context) {
furi_assert(context);
Power* power = context;
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
with_view_model(
power->info_view, (PowerInfoModel * model) {
canvas_draw_box(canvas, 2, 2, (float)model->charge / 100 * 20, 4);
return false;
});
}
uint32_t power_info_back_callback(void* context) {
return VIEW_NONE;
}
void power_menu_off_callback(void* context) {
Power* power = context;
power_off(power);
}
void power_menu_reset_dialog_result(DialogResult result, void* context) {
Power* power = context;
if(result == DialogResultLeft) {
power_reboot(power, PowerBootModeDfu);
} else if(result == DialogResultRight) {
power_reboot(power, PowerBootModeNormal);
} else if(result == DialogResultBack) {
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
}
}
void power_menu_reset_callback(void* context) {
Power* power = context;
dialog_set_result_callback(power->dialog, power_menu_reset_dialog_result);
dialog_set_header_text(power->dialog, "Reboot type");
dialog_set_text(power->dialog, "Reboot where?");
dialog_set_left_button_text(power->dialog, "DFU");
dialog_set_right_button_text(power->dialog, "OS");
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewDialog);
}
void power_menu_enable_otg_callback(void* context) {
furi_hal_power_enable_otg();
}
void power_menu_disable_otg_callback(void* context) {
furi_hal_power_disable_otg();
}
void power_menu_info_callback(void* context) {
Power* power = context;
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewInfo);
}
Power* power_alloc() {
Power* power = furi_alloc(sizeof(Power));
power->state = PowerStateNotCharging;
power->menu_vm = furi_record_open("menu");
power->cli = furi_record_open("cli");
power_cli_init(power->cli, power);
power->bt = furi_record_open("bt");
power->menu = menu_item_alloc_menu("Power", icon_animation_alloc(&A_Power_14));
menu_item_subitem_add(
power->menu, menu_item_alloc_function("Off", NULL, power_menu_off_callback, power));
menu_item_subitem_add(
power->menu, menu_item_alloc_function("Reboot", NULL, power_menu_reset_callback, power));
menu_item_subitem_add(
power->menu,
menu_item_alloc_function("Enable OTG", NULL, power_menu_enable_otg_callback, power));
menu_item_subitem_add(
power->menu,
menu_item_alloc_function("Disable OTG", NULL, power_menu_disable_otg_callback, power));
menu_item_subitem_add(
power->menu, menu_item_alloc_function("Info", NULL, power_menu_info_callback, power));
power->view_dispatcher = view_dispatcher_alloc();
power->info_view = view_alloc();
view_allocate_model(power->info_view, ViewModelTypeLockFree, sizeof(PowerInfoModel));
view_set_draw_callback(power->info_view, power_info_draw_callback);
view_set_previous_callback(power->info_view, power_info_back_callback);
view_dispatcher_add_view(power->view_dispatcher, PowerViewInfo, power->info_view);
power->off_view = view_alloc();
view_allocate_model(power->off_view, ViewModelTypeLockFree, sizeof(PowerOffModel));
view_set_draw_callback(power->off_view, power_off_draw_callback);
view_dispatcher_add_view(power->view_dispatcher, PowerViewOff, power->off_view);
power->disconnect_view = view_alloc();
view_set_draw_callback(power->disconnect_view, power_disconnect_draw_callback);
view_dispatcher_add_view(power->view_dispatcher, PowerViewDisconnect, power->disconnect_view);
power->dialog = dialog_alloc();
dialog_set_context(power->dialog, power);
view_dispatcher_add_view(
power->view_dispatcher, PowerViewDialog, dialog_get_view(power->dialog));
power->battery_view_port = view_port_alloc();
view_port_set_width(power->battery_view_port, icon_get_width(&I_Battery_26x8));
view_port_draw_callback_set(power->battery_view_port, power_draw_battery_callback, power);
return power;
}
void power_free(Power* power) {
furi_assert(power);
free(power);
}
void power_off(Power* power) {
furi_assert(power);
furi_hal_power_off();
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewDisconnect);
}
void power_reboot(Power* power, PowerBootMode mode) {
if(mode == PowerBootModeNormal) {
furi_hal_boot_set_mode(FuriHalBootModeNormal);
} else if(mode == PowerBootModeDfu) {
furi_hal_boot_set_mode(FuriHalBootModeDFU);
}
furi_hal_power_reset();
}
static void power_charging_indication_handler(Power* power, NotificationApp* notifications) {
if(furi_hal_power_is_charging()) {
if(furi_hal_power_get_pct() == 100) {
if(power->state != PowerStateCharged) {
notification_internal_message(notifications, &sequence_charged);
power->state = PowerStateCharged;
}
} else {
if(power->state != PowerStateCharging) {
notification_internal_message(notifications, &sequence_charging);
power->state = PowerStateCharging;
}
}
}
if(!furi_hal_power_is_charging()) {
if(power->state != PowerStateNotCharging) {
notification_internal_message(notifications, &sequence_not_charging);
power->state = PowerStateNotCharging;
}
}
}
int32_t power_srv(void* p) {
(void)p;
Power* power = power_alloc();
NotificationApp* notifications = furi_record_open("notification");
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, power->battery_view_port, GuiLayerStatusBarRight);
view_dispatcher_attach_to_gui(power->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
with_value_mutex(
power->menu_vm, (Menu * menu) { menu_item_add(menu, power->menu); });
furi_record_create("power", power);
uint8_t battery_level = 0;
uint8_t battery_level_prev = 0;
while(1) {
bool battery_low = false;
with_view_model(
power->info_view, (PowerInfoModel * model) {
model->charge = furi_hal_power_get_pct();
battery_level = model->charge;
model->health = furi_hal_power_get_bat_health_pct();
model->capacity_remaining = furi_hal_power_get_battery_remaining_capacity();
model->capacity_full = furi_hal_power_get_battery_full_capacity();
model->current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
model->current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
model->voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
model->voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
model->voltage_vbus = furi_hal_power_get_usb_voltage();
model->temperature_charger =
furi_hal_power_get_battery_temperature(FuriHalPowerICCharger);
model->temperature_gauge =
furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge);
if(model->charge == 0 && model->voltage_vbus < 4.0f) {
battery_low = true;
}
return true;
});
with_view_model(
power->off_view, (PowerOffModel * model) {
if(battery_low) {
if(model->poweroff_tick == 0) {
model->poweroff_tick =
osKernelGetTickCount() + osKernelGetTickFreq() * POWER_OFF_TIMEOUT;
} else {
if(osKernelGetTickCount() > model->poweroff_tick) {
power_off(power);
}
}
} else {
model->poweroff_tick = 0;
}
if(model->battery_low != battery_low) {
model->battery_low = battery_low;
view_dispatcher_switch_to_view(
power->view_dispatcher, battery_low ? PowerViewOff : VIEW_NONE);
}
return true;
});
power_charging_indication_handler(power, notifications);
if(battery_level_prev != battery_level) {
battery_level_prev = battery_level;
bt_update_battery_level(power->bt, battery_level);
}
view_port_update(power->battery_view_port);
osDelay(1024);
}
return 0;
}

View file

@ -1,19 +0,0 @@
#pragma once
typedef struct Power Power;
typedef enum {
PowerBootModeNormal,
PowerBootModeDfu,
} PowerBootMode;
/** Power off device
* @param power - Power instance
*/
void power_off(Power* power);
/** Reboot device
* @param power - Power instance
* @param mode - PowerBootMode
*/
void power_reboot(Power* power, PowerBootMode mode);

View file

@ -1,29 +1,31 @@
#include "power_cli.h"
#include <power/power_service/power.h>
#include <cli/cli.h>
#include <furi-hal.h>
void power_cli_poweroff(Cli* cli, string_t args, void* context) {
Power* power = context;
power_off(power);
power_off();
printf("It's now safe to disconnect USB from your flipper\r\n");
while(cli_getc(cli)) {
}
}
void power_cli_reboot(Cli* cli, string_t args, void* context) {
Power* power = context;
power_reboot(power, PowerBootModeNormal);
power_reboot(PowerBootModeNormal);
}
void power_cli_dfu(Cli* cli, string_t args, void* context) {
Power* power = context;
power_reboot(power, PowerBootModeDfu);
power_reboot(PowerBootModeDfu);
}
void power_cli_factory_reset(Cli* cli, string_t args, void* context) {
Power* power = context;
printf("All data will be lost. Are you sure (y/n)?\r\n");
char c = cli_getc(cli);
if(c == 'y' || c == 'Y') {
printf("Data will be wiped after reboot.\r\n");
furi_hal_boot_set_flags(FuriHalBootFlagFactoryReset);
power_reboot(power, PowerBootModeNormal);
power_reboot(PowerBootModeNormal);
} else {
printf("Safe choice.\r\n");
}
@ -53,13 +55,17 @@ void power_cli_ext(Cli* cli, string_t args, void* context) {
}
}
void power_cli_init(Cli* cli, Power* power) {
cli_add_command(cli, "poweroff", CliCommandFlagParallelSafe, power_cli_poweroff, power);
cli_add_command(cli, "reboot", CliCommandFlagParallelSafe, power_cli_reboot, power);
void power_cli_init() {
Cli* cli = furi_record_open("cli");
cli_add_command(cli, "poweroff", CliCommandFlagParallelSafe, power_cli_poweroff, NULL);
cli_add_command(cli, "reboot", CliCommandFlagParallelSafe, power_cli_reboot, NULL);
cli_add_command(
cli, "factory_reset", CliCommandFlagParallelSafe, power_cli_factory_reset, power);
cli_add_command(cli, "dfu", CliCommandFlagParallelSafe, power_cli_dfu, power);
cli_add_command(cli, "power_info", CliCommandFlagParallelSafe, power_cli_info, power);
cli_add_command(cli, "power_otg", CliCommandFlagParallelSafe, power_cli_otg, power);
cli_add_command(cli, "power_ext", CliCommandFlagParallelSafe, power_cli_ext, power);
cli, "factory_reset", CliCommandFlagParallelSafe, power_cli_factory_reset, NULL);
cli_add_command(cli, "dfu", CliCommandFlagParallelSafe, power_cli_dfu, NULL);
cli_add_command(cli, "power_info", CliCommandFlagParallelSafe, power_cli_info, NULL);
cli_add_command(cli, "power_otg", CliCommandFlagParallelSafe, power_cli_otg, NULL);
cli_add_command(cli, "power_ext", CliCommandFlagParallelSafe, power_cli_ext, NULL);
furi_record_close("cli");
}

View file

@ -1,6 +1,3 @@
#pragma once
#include <cli/cli.h>
#include "power.h"
void power_cli_init(Cli* cli, Power* power);
void power_cli_init();

View file

@ -0,0 +1,179 @@
#include "power_i.h"
#include "views/power_off.h"
#include <furi.h>
#include <furi-hal.h>
#include <gui/view_port.h>
#include <gui/view.h>
#define POWER_OFF_TIMEOUT 90
void power_draw_battery_callback(Canvas* canvas, void* context) {
furi_assert(context);
Power* power = context;
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
canvas_draw_box(canvas, 2, 2, power->info.charge / 5, 4);
}
static ViewPort* power_battery_view_port_alloc(Power* power) {
ViewPort* battery_view_port = view_port_alloc();
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_26x8));
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
gui_add_view_port(power->gui, battery_view_port, GuiLayerStatusBarRight);
return battery_view_port;
}
Power* power_alloc() {
Power* power = furi_alloc(sizeof(Power));
// Records
power->notification = furi_record_open("notification");
power->gui = furi_record_open("gui");
// Pubsub
init_pubsub(&power->event_pubsub);
// State initialization
power->state = PowerStateNotCharging;
power->battery_low = false;
power->power_off_timeout = POWER_OFF_TIMEOUT;
power->info_mtx = osMutexNew(NULL);
// Gui
power->view_dispatcher = view_dispatcher_alloc();
power->power_off = power_off_alloc();
view_dispatcher_add_view(
power->view_dispatcher, PowerViewOff, power_off_get_view(power->power_off));
view_dispatcher_attach_to_gui(
power->view_dispatcher, power->gui, ViewDispatcherTypeFullscreen);
// Battery view port
power->battery_view_port = power_battery_view_port_alloc(power);
return power;
}
void power_free(Power* power) {
furi_assert(power);
// Records
furi_record_close("notification");
furi_record_close("gui");
// Gui
view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff);
power_off_free(power->power_off);
view_port_free(power->battery_view_port);
// State
osMutexDelete(power->info_mtx);
free(power);
}
static void power_check_charging_state(Power* power) {
if(furi_hal_power_is_charging()) {
if(power->info.charge == 100) {
if(power->state != PowerStateCharged) {
notification_internal_message(power->notification, &sequence_charged);
power->state = PowerStateCharged;
power->event.type = PowerEventTypeFullyCharged;
notify_pubsub(&power->event_pubsub, &power->event);
}
} else {
if(power->state != PowerStateCharging) {
notification_internal_message(power->notification, &sequence_charging);
power->state = PowerStateCharging;
power->event.type = PowerEventTypeStartCharging;
notify_pubsub(&power->event_pubsub, &power->event);
}
}
} else {
if(power->state != PowerStateNotCharging) {
notification_internal_message(power->notification, &sequence_not_charging);
power->state = PowerStateNotCharging;
power->event.type = PowerEventTypeStopCharging;
notify_pubsub(&power->event_pubsub, &power->event);
}
}
}
static void power_update_info(Power* power) {
osMutexAcquire(power->info_mtx, osWaitForever);
PowerInfo* info = &power->info;
info->charge = furi_hal_power_get_pct();
info->health = furi_hal_power_get_bat_health_pct();
info->capacity_remaining = furi_hal_power_get_battery_remaining_capacity();
info->capacity_full = furi_hal_power_get_battery_full_capacity();
info->current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
info->current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
info->voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
info->voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
info->voltage_vbus = furi_hal_power_get_usb_voltage();
info->temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger);
info->temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge);
osMutexRelease(power->info_mtx);
}
static void power_check_low_battery(Power* power) {
// Check battery charge and vbus voltage
if((power->info.charge == 0) && (power->info.voltage_vbus < 4.0f)) {
if(!power->battery_low) {
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewOff);
}
power->battery_low = true;
} else {
if(power->battery_low) {
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
power->power_off_timeout = POWER_OFF_TIMEOUT;
}
power->battery_low = false;
}
// If battery low, update view and switch off power after timeout
if(power->battery_low) {
if(power->power_off_timeout) {
power_off_set_time_left(power->power_off, power->power_off_timeout--);
} else {
power_off();
}
}
}
static void power_check_battery_level_change(Power* power) {
if(power->battery_level != power->info.charge) {
power->battery_level = power->info.charge;
power->event.type = PowerEventTypeBatteryLevelChanged;
power->event.data.battery_level = power->battery_level;
notify_pubsub(&power->event_pubsub, &power->event);
}
}
int32_t power_srv(void* p) {
(void)p;
Power* power = power_alloc();
furi_record_create("power", power);
while(1) {
// Update data from gauge and charger
power_update_info(power);
// Check low battery level
power_check_low_battery(power);
// Check and notify about charging state
power_check_charging_state(power);
// Check and notify about battery level change
power_check_battery_level_change(power);
// Update battery view port
view_port_update(power->battery_view_port);
osDelay(1000);
}
power_free(power);
return 0;
}

View file

@ -0,0 +1,65 @@
#pragma once
#include <stdint.h>
#include <furi/pubsub.h>
typedef struct Power Power;
typedef enum {
PowerBootModeNormal,
PowerBootModeDfu,
} PowerBootMode;
typedef enum {
PowerEventTypeStopCharging,
PowerEventTypeStartCharging,
PowerEventTypeFullyCharged,
PowerEventTypeBatteryLevelChanged,
} PowerEventType;
typedef union {
uint8_t battery_level;
} PowerEventData;
typedef struct {
PowerEventType type;
PowerEventData data;
} PowerEvent;
typedef struct {
float current_charger;
float current_gauge;
float voltage_charger;
float voltage_gauge;
float voltage_vbus;
uint32_t capacity_remaining;
uint32_t capacity_full;
float temperature_charger;
float temperature_gauge;
uint8_t charge;
uint8_t health;
} PowerInfo;
/** Power off device
*/
void power_off();
/** Reboot device
* @param mode - PowerBootMode
*/
void power_reboot(PowerBootMode mode);
/** Get power info
* @param power - Power instance
* @param info - PowerInfo instance
*/
void power_get_info(Power* power, PowerInfo* info);
/** Get power event pubsub handler
* @param power - Power instance
*/
PubSub* power_get_pubsub(Power* power);

View file

@ -0,0 +1,31 @@
#include "power_i.h"
#include <furi.h>
#include "furi-hal-power.h"
#include "furi-hal-boot.h"
void power_off() {
furi_hal_power_off();
}
void power_reboot(PowerBootMode mode) {
if(mode == PowerBootModeNormal) {
furi_hal_boot_set_mode(FuriHalBootModeNormal);
} else if(mode == PowerBootModeDfu) {
furi_hal_boot_set_mode(FuriHalBootModeDFU);
}
furi_hal_power_reset();
}
void power_get_info(Power* power, PowerInfo* info) {
furi_assert(power);
furi_assert(info);
osMutexAcquire(power->info_mtx, osWaitForever);
memcpy(info, &power->info, sizeof(power->info));
osMutexRelease(power->info_mtx);
}
PubSub* power_get_pubsub(Power* power) {
furi_assert(power);
return &power->event_pubsub;
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "power.h"
#include <stdint.h>
#include <gui/view_dispatcher.h>
#include <gui/gui.h>
#include "views/power_off.h"
#include <notification/notification-messages.h>
typedef enum {
PowerStateNotCharging,
PowerStateCharging,
PowerStateCharged,
} PowerState;
struct Power {
ViewDispatcher* view_dispatcher;
PowerOff* power_off;
ViewPort* battery_view_port;
Gui* gui;
NotificationApp* notification;
PubSub event_pubsub;
PowerEvent event;
PowerState state;
PowerInfo info;
osMutexId_t info_mtx;
bool battery_low;
uint8_t battery_level;
uint8_t power_off_timeout;
};
typedef enum { PowerViewOff } PowerView;

View file

@ -0,0 +1,57 @@
#include "power_off.h"
#include <furi.h>
#include <gui/elements.h>
struct PowerOff {
View* view;
};
typedef struct {
uint32_t time_left_sec;
} PowerOffModel;
static void power_off_draw_callback(Canvas* canvas, void* _model) {
furi_assert(_model);
PowerOffModel* model = _model;
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_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);
elements_multiline_text_aligned(
canvas, 70, 23, AlignLeft, AlignTop, "Connect me\n to charger.");
snprintf(buff, sizeof(buff), "Poweroff in %lds.", model->time_left_sec);
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, buff);
}
PowerOff* power_off_alloc() {
PowerOff* power_off = furi_alloc(sizeof(PowerOff));
power_off->view = view_alloc();
view_allocate_model(power_off->view, ViewModelTypeLocking, sizeof(PowerOffModel));
view_set_draw_callback(power_off->view, power_off_draw_callback);
return power_off;
}
void power_off_free(PowerOff* power_off) {
furi_assert(power_off);
view_free(power_off->view);
free(power_off);
}
View* power_off_get_view(PowerOff* power_off) {
furi_assert(power_off);
return power_off->view;
}
void power_off_set_time_left(PowerOff* power_off, uint8_t time_left) {
furi_assert(power_off);
with_view_model(
power_off->view, (PowerOffModel * model) {
model->time_left_sec = time_left;
return true;
});
}

View file

@ -0,0 +1,13 @@
#pragma once
typedef struct PowerOff PowerOff;
#include <gui/view.h>
PowerOff* power_off_alloc();
void power_off_free(PowerOff* power_off);
View* power_off_get_view(PowerOff* power_off);
void power_off_set_time_left(PowerOff* power_off, uint8_t time_left);

View file

@ -0,0 +1,82 @@
#include "power_settings_app.h"
static bool power_settings_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
PowerSettingsApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool power_settings_back_event_callback(void* context) {
furi_assert(context);
PowerSettingsApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void power_settings_tick_event_callback(void* context) {
furi_assert(context);
PowerSettingsApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
PowerSettingsApp* power_settings_app_alloc() {
PowerSettingsApp* app = furi_alloc(sizeof(PowerSettingsApp));
// Records
app->gui = furi_record_open("gui");
app->power = furi_record_open("power");
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&power_settings_scene_handlers, app);
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, power_settings_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, power_settings_back_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, power_settings_tick_event_callback, 2000);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Views
app->batery_info = battery_info_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
PowerSettingsAppViewBatteryInfo,
battery_info_get_view(app->batery_info));
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, PowerSettingsAppViewSubmenu, submenu_get_view(app->submenu));
app->dialog = dialog_ex_alloc();
view_dispatcher_add_view(
app->view_dispatcher, PowerSettingsAppViewDialog, dialog_ex_get_view(app->dialog));
// Set first scene
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneStart);
return app;
}
void power_settings_app_free(PowerSettingsApp* app) {
furi_assert(app);
// Views
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewBatteryInfo);
battery_info_free(app->batery_info);
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewSubmenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewDialog);
dialog_ex_free(app->dialog);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Records
furi_record_close("power");
furi_record_close("gui");
free(app);
}
extern int32_t power_settings_app(void* p) {
PowerSettingsApp* app = power_settings_app_alloc();
view_dispatcher_run(app->view_dispatcher);
power_settings_app_free(app);
return 0;
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <furi.h>
#include <power/power_service/power.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include "views/battery_info.h"
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include "scenes/power_settings_scene.h"
typedef struct {
Power* power;
Gui* gui;
SceneManager* scene_manager;
ViewDispatcher* view_dispatcher;
BatteryInfo* batery_info;
Submenu* submenu;
DialogEx* dialog;
PowerInfo info;
} PowerSettingsApp;
typedef enum {
PowerSettingsAppViewBatteryInfo,
PowerSettingsAppViewSubmenu,
PowerSettingsAppViewDialog,
} PowerSettingsAppView;

View file

@ -0,0 +1,17 @@
#include "../power_settings_app.h"
void power_settings_scene_usb_disconnect_on_enter(void* context) {
PowerSettingsApp* app = context;
dialog_ex_set_header(
app->dialog, "Disconnect USB for safe\nshutdown", 64, 26, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog);
}
bool power_settings_scene_usb_disconnect_on_event(void* context, SceneManagerEvent event) {
return true;
}
void power_settings_scene_usb_disconnect_on_exit(void* context) {
}

View file

@ -0,0 +1,30 @@
#include "power_settings_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const power_settings_on_enter_handlers[])(void*) = {
#include "power_settings_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const power_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "power_settings_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const power_settings_on_exit_handlers[])(void* context) = {
#include "power_settings_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers power_settings_scene_handlers = {
.on_enter_handlers = power_settings_on_enter_handlers,
.on_event_handlers = power_settings_on_event_handlers,
.on_exit_handlers = power_settings_on_exit_handlers,
.scene_num = PowerSettingsAppSceneNum,
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) PowerSettingsAppScene##id,
typedef enum {
#include "power_settings_scene_config.h"
PowerSettingsAppSceneNum,
} PowerSettingsAppScene;
#undef ADD_SCENE
extern const SceneManagerHandlers power_settings_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "power_settings_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "power_settings_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "power_settings_scene_config.h"
#undef ADD_SCENE

View file

@ -0,0 +1,34 @@
#include "../power_settings_app.h"
static void power_settings_scene_battery_info_update_model(PowerSettingsApp* app) {
power_get_info(app->power, &app->info);
BatteryInfoModel battery_info_data = {
.vbus_voltage = app->info.voltage_vbus,
.gauge_voltage = app->info.voltage_gauge,
.gauge_current = app->info.current_gauge,
.gauge_temperature = app->info.temperature_gauge,
.charge = app->info.charge,
.health = app->info.health,
};
battery_info_set_data(app->batery_info, &battery_info_data);
}
void power_settings_scene_battery_info_on_enter(void* context) {
PowerSettingsApp* app = context;
power_settings_scene_battery_info_update_model(app);
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewBatteryInfo);
}
bool power_settings_scene_battery_info_on_event(void* context, SceneManagerEvent event) {
PowerSettingsApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeTick) {
power_settings_scene_battery_info_update_model(app);
consumed = true;
}
return consumed;
}
void power_settings_scene_battery_info_on_exit(void* context) {
}

View file

@ -0,0 +1,5 @@
ADD_SCENE(power_settings, start, Start)
ADD_SCENE(power_settings, battery_info, BatteryInfo)
ADD_SCENE(power_settings, reboot, Reboot)
ADD_SCENE(power_settings, power_off, PowerOff)
ADD_SCENE(power_settings, usb_disconnect, UsbDisconnect)

View file

@ -0,0 +1,48 @@
#include "../power_settings_app.h"
void power_settings_scene_power_off_dialog_callback(DialogExResult result, void* context) {
furi_assert(context);
PowerSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
void power_settings_scene_power_off_on_enter(void* context) {
PowerSettingsApp* app = context;
DialogEx* dialog = app->dialog;
dialog_ex_set_header(dialog, "Turn off Device?", 64, 2, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog, " I will be\nwaiting for\n you here...", 78, 16, AlignLeft, AlignTop);
dialog_ex_set_icon(dialog, 21, 13, &I_Cry_dolph_55x52);
dialog_ex_set_left_button_text(dialog, "Back");
dialog_ex_set_right_button_text(dialog, "OFF");
dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback);
dialog_ex_set_context(dialog, app);
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog);
}
bool power_settings_scene_power_off_on_event(void* context, SceneManagerEvent event) {
PowerSettingsApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(app->scene_manager);
} else if(event.event == DialogExResultRight) {
power_off();
// Check if USB is connected
power_get_info(app->power, &app->info);
if(app->info.voltage_vbus > 4.0f) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneUsbDisconnect);
}
}
consumed = true;
}
return consumed;
}
void power_settings_scene_power_off_on_exit(void* context) {
PowerSettingsApp* app = context;
dialog_ex_clean(app->dialog);
}

View file

@ -0,0 +1,52 @@
#include "../power_settings_app.h"
enum PowerSettingsRebootSubmenuIndex {
PowerSettingsRebootSubmenuIndexDfu,
PowerSettingsRebootSubmenuIndexOs,
};
void power_settings_scene_reboot_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
PowerSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void power_settings_scene_reboot_on_enter(void* context) {
PowerSettingsApp* app = context;
Submenu* submenu = app->submenu;
submenu_set_header(submenu, "Reboot type");
submenu_add_item(
submenu,
"Firmware upgrade",
PowerSettingsRebootSubmenuIndexDfu,
power_settings_scene_reboot_submenu_callback,
app);
submenu_add_item(
submenu,
"Flipper OS",
PowerSettingsRebootSubmenuIndexOs,
power_settings_scene_reboot_submenu_callback,
app);
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewSubmenu);
}
bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PowerSettingsRebootSubmenuIndexDfu) {
power_reboot(PowerBootModeDfu);
} else if(event.event == PowerSettingsRebootSubmenuIndexOs) {
power_reboot(PowerBootModeNormal);
}
consumed = true;
}
return consumed;
}
void power_settings_scene_reboot_on_exit(void* context) {
PowerSettingsApp* app = context;
submenu_clean(app->submenu);
}

View file

@ -0,0 +1,64 @@
#include "../power_settings_app.h"
enum PowerSettingsSubmenuIndex {
PowerSettingsSubmenuIndexBatteryInfo,
PowerSettingsSubmenuIndexReboot,
PowerSettingsSubmenuIndexOff,
};
static void power_settings_scene_start_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
PowerSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void power_settings_scene_start_on_enter(void* context) {
PowerSettingsApp* app = context;
Submenu* submenu = app->submenu;
submenu_add_item(
submenu,
"Battery info",
PowerSettingsSubmenuIndexBatteryInfo,
power_settings_scene_start_submenu_callback,
app);
submenu_add_item(
submenu,
"Reboot",
PowerSettingsSubmenuIndexReboot,
power_settings_scene_start_submenu_callback,
app);
submenu_add_item(
submenu,
"Power OFF",
PowerSettingsSubmenuIndexOff,
power_settings_scene_start_submenu_callback,
app);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneStart));
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewSubmenu);
}
bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
PowerSettingsApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PowerSettingsSubmenuIndexBatteryInfo) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneBatteryInfo);
} else if(event.event == PowerSettingsSubmenuIndexReboot) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneReboot);
} else if(event.event == PowerSettingsSubmenuIndexOff) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppScenePowerOff);
}
scene_manager_set_scene_state(app->scene_manager, PowerSettingsAppSceneStart, event.event);
consumed = true;
}
return consumed;
}
void power_settings_scene_start_on_exit(void* context) {
PowerSettingsApp* app = context;
submenu_clean(app->submenu);
}

View file

@ -0,0 +1,126 @@
#include "battery_info.h"
#include <furi.h>
#include <gui/elements.h>
struct BatteryInfo {
View* view;
};
static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val) {
canvas_draw_frame(canvas, x - 7, y + 7, 30, 13);
canvas_draw_icon(canvas, x, y, icon);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x - 4, y + 16, 24, 6);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val);
};
static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
char emote[20] = {};
char header[20] = {};
char value[20] = {};
int32_t drain_current = data->gauge_current * (-1000);
uint32_t charge_current = data->gauge_current * 1000;
// Draw battery
canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28);
if(charge_current > 0) {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14);
} else if(drain_current > 100) {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14);
} else if(data->charge < 10) {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14);
} else {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14);
}
// Draw bubble
elements_bubble(canvas, 53, 0, 71, 39);
// Set text
if(charge_current > 0) {
snprintf(emote, sizeof(emote), "%s", "Yummy!");
snprintf(header, sizeof(header), "%s", "Charging at");
snprintf(
value,
sizeof(value),
"%ld.%ldV %ldmA",
(uint32_t)(data->vbus_voltage),
(uint32_t)(data->vbus_voltage * 10) % 10,
charge_current);
} else if(drain_current > 0) {
snprintf(emote, sizeof(emote), "%s", drain_current > 100 ? "Oh no!" : "Om-nom-nom!");
snprintf(header, sizeof(header), "%s", "Consumption is");
snprintf(
value, sizeof(value), "%ld %s", drain_current, drain_current > 100 ? "mA!" : "mA");
} else if(charge_current != 0 || drain_current != 0) {
snprintf(header, 20, "...");
} else {
snprintf(header, sizeof(header), "Charged!");
}
canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote);
canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header);
canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value);
};
static void battery_info_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BatteryInfoModel* model = context;
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
draw_battery(canvas, model, 0, 5);
char batt_level[10];
char temperature[10];
char voltage[10];
char health[10];
snprintf(batt_level, sizeof(batt_level), "%ld%%", (uint32_t)model->charge);
snprintf(temperature, sizeof(temperature), "%ld C", (uint32_t)model->gauge_temperature);
snprintf(
voltage,
sizeof(voltage),
"%ld.%01ld V",
(uint32_t)model->gauge_voltage,
(uint32_t)(model->gauge_voltage * 10) % 10);
snprintf(health, sizeof(health), "%d%%", model->health);
draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level);
draw_stat(canvas, 40, 42, &I_Temperature_16x16, temperature);
draw_stat(canvas, 72, 42, &I_Voltage_16x16, voltage);
draw_stat(canvas, 104, 42, &I_Health_16x16, health);
}
BatteryInfo* battery_info_alloc() {
BatteryInfo* battery_info = furi_alloc(sizeof(BatteryInfo));
battery_info->view = view_alloc();
view_set_context(battery_info->view, battery_info);
view_allocate_model(battery_info->view, ViewModelTypeLocking, sizeof(BatteryInfoModel));
view_set_draw_callback(battery_info->view, battery_info_draw_callback);
return battery_info;
}
void battery_info_free(BatteryInfo* battery_info) {
furi_assert(battery_info);
view_free(battery_info->view);
free(battery_info);
}
View* battery_info_get_view(BatteryInfo* battery_info) {
furi_assert(battery_info);
return battery_info->view;
}
void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data) {
furi_assert(battery_info);
furi_assert(data);
with_view_model(
battery_info->view, (BatteryInfoModel * model) {
memcpy(model, data, sizeof(BatteryInfoModel));
return true;
});
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <gui/view.h>
typedef struct BatteryInfo BatteryInfo;
typedef struct {
float vbus_voltage;
float gauge_voltage;
float gauge_current;
float gauge_temperature;
uint8_t charge;
uint8_t health;
} BatteryInfoModel;
BatteryInfo* battery_info_alloc();
void battery_info_free(BatteryInfo* battery_info);
View* battery_info_get_view(BatteryInfo* battery_info);
void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data);

View file

@ -1,131 +0,0 @@
#include "power_views.h"
#include <gui/elements.h>
static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val) {
canvas_draw_frame(canvas, x - 7, y + 7, 30, 13);
canvas_draw_icon(canvas, x, y, icon);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x - 4, y + 16, 24, 6);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val);
};
static void draw_battery(Canvas* canvas, PowerInfoModel* data, int x, int y) {
char emote[20];
char header[20];
char value[20];
int32_t drain_current = -data->current_gauge * 1000;
uint32_t charge_current = data->current_gauge * 1000;
// battery
canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28);
if(charge_current > 0) {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14);
} else if(drain_current > 100) {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14);
} else if(data->charge < 10) {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14);
} else {
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14);
}
//bubble
canvas_draw_frame(canvas, 57, 0, 71, 39);
canvas_draw_line(canvas, 53, 23, 57, 19);
canvas_draw_line(canvas, 53, 23, 57, 27);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 57, 0, 2, 2);
canvas_draw_box(canvas, 57, 37, 2, 2);
canvas_draw_box(canvas, 126, 0, 2, 2);
canvas_draw_box(canvas, 126, 37, 2, 2);
canvas_draw_line(canvas, 57, 20, 57, 26);
canvas_set_color(canvas, ColorBlack);
canvas_draw_dot(canvas, 58, 1);
canvas_draw_dot(canvas, 58, 37);
canvas_draw_dot(canvas, 126, 1);
canvas_draw_dot(canvas, 126, 37);
// text
if(charge_current > 0) {
snprintf(emote, sizeof(emote), "%s", "Yummy!");
snprintf(header, sizeof(header), "%s", "Charging at");
snprintf(
value,
sizeof(value),
"%ld.%ldV %ldmA",
(uint32_t)(data->voltage_vbus),
(uint32_t)(data->voltage_vbus * 10) % 10,
charge_current);
} else if(drain_current > 0) {
snprintf(emote, sizeof(emote), "%s", drain_current > 100 ? "Oh no!" : "Om-nom-nom!");
snprintf(header, sizeof(header), "%s", "Consumption is");
snprintf(
value, sizeof(value), "%ld %s", drain_current, drain_current > 100 ? "mA!" : "mA");
} else if(charge_current != 0 || drain_current != 0) {
snprintf(header, 20, "%s", "...");
memset(value, 0, sizeof(value));
memset(emote, 0, sizeof(emote));
} else {
snprintf(header, sizeof(header), "%s", "Charged!");
memset(value, 0, sizeof(value));
memset(emote, 0, sizeof(emote));
}
canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote);
canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header);
canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value);
};
void power_info_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
PowerInfoModel* data = context;
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
draw_battery(canvas, data, 0, 5);
char batt_level[10];
char temperature[10];
char voltage[10];
char health[10];
snprintf(batt_level, sizeof(batt_level), "%ld%s", (uint32_t)data->charge, "%");
snprintf(temperature, sizeof(temperature), "%ld %s", (uint32_t)data->temperature_gauge, "C");
snprintf(
voltage,
sizeof(voltage),
"%ld.%01ld V",
(uint32_t)data->voltage_gauge,
(uint32_t)(data->voltage_gauge * 10) % 10);
snprintf(health, sizeof(health), "%d%s", data->health, "%");
draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level);
draw_stat(canvas, 40, 42, &I_Temperature_16x16, temperature);
draw_stat(canvas, 72, 42, &I_Voltage_16x16, voltage);
draw_stat(canvas, 104, 42, &I_Health_16x16, health);
}
void power_off_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
PowerOffModel* model = context;
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 15, "!!! Low Battery !!!");
char buffer[64];
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 5, 30, "Connect to charger");
snprintf(
buffer,
64,
"Or poweroff in %lds",
(model->poweroff_tick - osKernelGetTickCount()) / osKernelGetTickFreq());
canvas_draw_str(canvas, 5, 42, buffer);
}
void power_disconnect_draw_callback(Canvas* canvas, void* context) {
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(
canvas, 64, 32, AlignCenter, AlignCenter, "It's now safe to turn off\nyour flipper");
}

View file

@ -1,38 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <furi.h>
#include <gui/canvas.h>
#include <gui/view.h>
typedef enum { PowerViewInfo, PowerViewDialog, PowerViewOff, PowerViewDisconnect } PowerView;
typedef struct {
float current_charger;
float current_gauge;
float voltage_charger;
float voltage_gauge;
float voltage_vbus;
uint32_t capacity_remaining;
uint32_t capacity_full;
float temperature_charger;
float temperature_gauge;
uint8_t charge;
uint8_t health;
} PowerInfoModel;
typedef struct {
uint32_t poweroff_tick;
bool battery_low;
} PowerOffModel;
void power_info_draw_callback(Canvas* canvas, void* context);
void power_off_draw_callback(Canvas* canvas, void* context);
void power_disconnect_draw_callback(Canvas* canvas, void* context);

File diff suppressed because one or more lines are too long

View file

@ -5,64 +5,64 @@ extern const Icon I_Certification1_103x23;
extern const Icon I_Certification2_119x30;
extern const Icon A_WatchingTV_128x64;
extern const Icon A_Wink_128x64;
extern const Icon I_125_10px;
extern const Icon I_ble_10px;
extern const Icon I_dir_10px;
extern const Icon I_ibutt_10px;
extern const Icon I_ir_10px;
extern const Icon I_Nfc_10px;
extern const Icon I_sub1_10px;
extern const Icon I_ir_10px;
extern const Icon I_ibutt_10px;
extern const Icon I_unknown_10px;
extern const Icon I_ButtonCenter_7x7;
extern const Icon I_ButtonLeftSmall_3x5;
extern const Icon I_ButtonLeft_4x7;
extern const Icon I_ble_10px;
extern const Icon I_125_10px;
extern const Icon I_ButtonRightSmall_3x5;
extern const Icon I_ButtonRight_4x7;
extern const Icon I_ButtonLeft_4x7;
extern const Icon I_ButtonLeftSmall_3x5;
extern const Icon I_DFU_128x50;
extern const Icon I_Warning_30x23;
extern const Icon I_DolphinFirstStart0_70x53;
extern const Icon I_DolphinFirstStart1_59x53;
extern const Icon I_DolphinFirstStart2_59x51;
extern const Icon I_DolphinFirstStart3_57x48;
extern const Icon I_DolphinFirstStart4_67x53;
extern const Icon I_DolphinFirstStart5_54x49;
extern const Icon I_DolphinFirstStart6_58x54;
extern const Icon I_DolphinFirstStart7_61x51;
extern const Icon I_DolphinFirstStart8_56x51;
extern const Icon I_ButtonRight_4x7;
extern const Icon I_ButtonCenter_7x7;
extern const Icon I_DolphinOkay_41x43;
extern const Icon I_DolphinFirstStart4_67x53;
extern const Icon I_DolphinFirstStart2_59x51;
extern const Icon I_DolphinFirstStart5_54x49;
extern const Icon I_DolphinFirstStart0_70x53;
extern const Icon I_DolphinFirstStart6_58x54;
extern const Icon I_DolphinFirstStart1_59x53;
extern const Icon I_DolphinFirstStart8_56x51;
extern const Icon I_DolphinFirstStart7_61x51;
extern const Icon I_Flipper_young_80x60;
extern const Icon I_DoorLeft_70x55;
extern const Icon I_DolphinFirstStart3_57x48;
extern const Icon I_PassportBottom_128x17;
extern const Icon I_DoorLeft_8x56;
extern const Icon I_DoorLocked_10x56;
extern const Icon I_DoorRight_70x55;
extern const Icon I_DoorRight_8x56;
extern const Icon I_LockPopup_100x49;
extern const Icon I_PassportBottom_128x17;
extern const Icon I_DoorLeft_70x55;
extern const Icon I_PassportLeft_6x47;
extern const Icon I_Back_15x10;
extern const Icon I_DoorRight_70x55;
extern const Icon I_LockPopup_100x49;
extern const Icon I_Mute_25x27;
extern const Icon I_IrdaArrowUp_4x8;
extern const Icon I_Up_hvr_25x27;
extern const Icon I_Mute_hvr_25x27;
extern const Icon I_Vol_down_25x27;
extern const Icon I_Down_25x27;
extern const Icon I_Power_hvr_25x27;
extern const Icon I_IrdaLearnShort_128x31;
extern const Icon I_IrdaArrowDown_4x8;
extern const Icon I_Vol_down_hvr_25x27;
extern const Icon I_IrdaLearn_128x64;
extern const Icon I_Down_hvr_25x27;
extern const Icon I_Fill_marker_7x7;
extern const Icon I_IrdaArrowDown_4x8;
extern const Icon I_IrdaArrowUp_4x8;
extern const Icon I_IrdaLearnShort_128x31;
extern const Icon I_IrdaLearn_128x64;
extern const Icon I_IrdaSendShort_128x34;
extern const Icon I_IrdaSend_128x64;
extern const Icon I_Mute_25x27;
extern const Icon I_Mute_hvr_25x27;
extern const Icon I_Power_25x27;
extern const Icon I_Power_hvr_25x27;
extern const Icon I_Up_25x27;
extern const Icon I_Up_hvr_25x27;
extern const Icon I_Vol_down_25x27;
extern const Icon I_Vol_down_hvr_25x27;
extern const Icon I_Vol_up_25x27;
extern const Icon I_Up_25x27;
extern const Icon I_Back_15x10;
extern const Icon I_IrdaSend_128x64;
extern const Icon I_IrdaSendShort_128x34;
extern const Icon I_Vol_up_hvr_25x27;
extern const Icon I_KeyBackspaceSelected_16x9;
extern const Icon I_KeyBackspace_16x9;
extern const Icon I_KeySaveSelected_24x11;
extern const Icon I_KeySave_24x11;
extern const Icon I_KeyBackspaceSelected_16x9;
extern const Icon I_KeySaveSelected_24x11;
extern const Icon I_KeyBackspace_16x9;
extern const Icon A_125khz_14;
extern const Icon A_Bluetooth_14;
extern const Icon A_Debug_14;
@ -81,41 +81,42 @@ extern const Icon A_U2F_14;
extern const Icon A_iButton_14;
extern const Icon I_Detailed_chip_17x13;
extern const Icon I_Medium_chip_22x21;
extern const Icon I_BatteryBody_52x28;
extern const Icon I_Battery_16x16;
extern const Icon I_Health_16x16;
extern const Icon I_FaceCharging_29x14;
extern const Icon I_FaceConfused_29x14;
extern const Icon I_BatteryBody_52x28;
extern const Icon I_Voltage_16x16;
extern const Icon I_Temperature_16x16;
extern const Icon I_FaceNopower_29x14;
extern const Icon I_FaceNormal_29x14;
extern const Icon I_Health_16x16;
extern const Icon I_Temperature_16x16;
extern const Icon I_Voltage_16x16;
extern const Icon I_RFIDBigChip_37x36;
extern const Icon I_RFIDDolphinReceive_97x61;
extern const Icon I_RFIDDolphinSend_97x61;
extern const Icon I_Battery_16x16;
extern const Icon I_FaceConfused_29x14;
extern const Icon I_RFIDDolphinSuccess_108x57;
extern const Icon I_SDError_43x35;
extern const Icon I_RFIDBigChip_37x36;
extern const Icon I_RFIDDolphinSend_97x61;
extern const Icon I_RFIDDolphinReceive_97x61;
extern const Icon I_SDQuestion_35x43;
extern const Icon I_Background_128x11;
extern const Icon I_Background_128x8;
extern const Icon I_SDError_43x35;
extern const Icon I_Cry_dolph_55x52;
extern const Icon I_BadUsb_9x8;
extern const Icon I_Battery_19x8;
extern const Icon I_Battery_26x8;
extern const Icon I_Bluetooth_5x8;
extern const Icon I_Lock_8x8;
extern const Icon I_PlaceholderL_11x13;
extern const Icon I_PlaceholderR_30x13;
extern const Icon I_SDcardFail_11x8;
extern const Icon I_Background_128x8;
extern const Icon I_Lock_8x8;
extern const Icon I_Battery_26x8;
extern const Icon I_PlaceholderL_11x13;
extern const Icon I_Battery_19x8;
extern const Icon I_SDcardMounted_11x8;
extern const Icon I_SDcardFail_11x8;
extern const Icon I_USBConnected_15x8;
extern const Icon I_Lock_7x8;
extern const Icon I_Quest_7x8;
extern const Icon I_Bluetooth_5x8;
extern const Icon I_Background_128x11;
extern const Icon I_Scanning_123x52;
extern const Icon I_Quest_7x8;
extern const Icon I_Unlock_7x8;
extern const Icon I_DolphinExcited_64x63;
extern const Icon I_Lock_7x8;
extern const Icon I_DolphinMafia_115x62;
extern const Icon I_DolphinNice_96x59;
extern const Icon I_DolphinWait_61x59;
extern const Icon I_DolphinExcited_64x63;
extern const Icon I_iButtonDolphinSuccess_109x60;
extern const Icon I_iButtonDolphinVerySuccess_108x52;
extern const Icon I_iButtonKey_49x44;
extern const Icon I_DolphinNice_96x59;
extern const Icon I_DolphinWait_61x59;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB