[FL-2627] Flipper applications: SDK, build and debug system (#1387)
* Added support for running applications from SD card (FAPs - Flipper Application Packages) * Added plugin_dist target for fbt to build FAPs * All apps of type FlipperAppType.EXTERNAL and FlipperAppType.PLUGIN are built as FAPs by default * Updated VSCode configuration for new fbt features - re-deploy stock configuration to use them * Added debugging support for FAPs with fbt debug & VSCode * Added public firmware API with automated versioning Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: SG <who.just.the.doctor@gmail.com> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
26
applications/services/power/application.fam
Normal file
26
applications/services/power/application.fam
Normal file
@@ -0,0 +1,26 @@
|
||||
App(
|
||||
appid="power",
|
||||
name="PowerSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="power_srv",
|
||||
cdefines=["SRV_POWER"],
|
||||
requires=[
|
||||
"gui",
|
||||
"cli",
|
||||
],
|
||||
provides=[
|
||||
"power_settings",
|
||||
"power_start",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=110,
|
||||
sdk_headers=["power_service/power.h"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="power_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="power_on_system_start",
|
||||
requires=["power"],
|
||||
order=80,
|
||||
)
|
149
applications/services/power/power_cli.c
Normal file
149
applications/services/power/power_cli.c
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "power_cli.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
void power_cli_off(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
printf("It's now safe to disconnect USB from your flipper\r\n");
|
||||
furi_delay_ms(666);
|
||||
power_off(power);
|
||||
}
|
||||
|
||||
void power_cli_reboot(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
power_reboot(PowerBootModeNormal);
|
||||
}
|
||||
|
||||
void power_cli_reboot2dfu(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
power_reboot(PowerBootModeDfu);
|
||||
}
|
||||
|
||||
static void power_cli_info_callback(const char* key, const char* value, bool last, void* context) {
|
||||
UNUSED(last);
|
||||
UNUSED(context);
|
||||
printf("%-24s: %s\r\n", key, value);
|
||||
}
|
||||
|
||||
void power_cli_info(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
furi_hal_power_info_get(power_cli_info_callback, NULL);
|
||||
}
|
||||
|
||||
void power_cli_debug(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
furi_hal_power_dump_state();
|
||||
}
|
||||
|
||||
void power_cli_5v(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
if(!string_cmp(args, "0")) {
|
||||
furi_hal_power_disable_otg();
|
||||
} else if(!string_cmp(args, "1")) {
|
||||
furi_hal_power_enable_otg();
|
||||
} else {
|
||||
cli_print_usage("power_otg", "<1|0>", string_get_cstr(args));
|
||||
}
|
||||
}
|
||||
|
||||
void power_cli_3v3(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
if(!string_cmp(args, "0")) {
|
||||
furi_hal_power_disable_external_3_3v();
|
||||
} else if(!string_cmp(args, "1")) {
|
||||
furi_hal_power_enable_external_3_3v();
|
||||
} else {
|
||||
cli_print_usage("power_ext", "<1|0>", string_get_cstr(args));
|
||||
}
|
||||
}
|
||||
|
||||
static void power_cli_command_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("power <cmd> <args>\r\n");
|
||||
printf("Cmd list:\r\n");
|
||||
|
||||
printf("\toff\t - shutdown power\r\n");
|
||||
printf("\treboot\t - reboot\r\n");
|
||||
printf("\treboot2dfu\t - reboot to dfu bootloader\r\n");
|
||||
printf("\tinfo\t - show power info\r\n");
|
||||
printf("\tdebug\t - show debug information\r\n");
|
||||
printf("\t5v <0 or 1>\t - enable or disable 5v ext\r\n");
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
printf("\t3v3 <0 or 1>\t - enable or disable 3v3 ext\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void power_cli(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
string_t cmd;
|
||||
string_init(cmd);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, cmd)) {
|
||||
power_cli_command_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "off") == 0) {
|
||||
power_cli_off(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "reboot") == 0) {
|
||||
power_cli_reboot(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "reboot2dfu") == 0) {
|
||||
power_cli_reboot2dfu(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "info") == 0) {
|
||||
power_cli_info(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "debug") == 0) {
|
||||
power_cli_debug(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "5v") == 0) {
|
||||
power_cli_5v(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
if(string_cmp_str(cmd, "3v3") == 0) {
|
||||
power_cli_3v3(cli, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
power_cli_command_print_usage();
|
||||
} while(false);
|
||||
|
||||
string_clear(cmd);
|
||||
}
|
||||
|
||||
void power_on_system_start() {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
|
||||
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL);
|
||||
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(power_cli);
|
||||
#endif
|
||||
}
|
11
applications/services/power/power_cli.h
Normal file
11
applications/services/power/power_cli.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void power_on_system_start();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
237
applications/services/power/power_service/power.c
Normal file
237
applications/services/power/power_service/power.c
Normal file
@@ -0,0 +1,237 @@
|
||||
#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);
|
||||
|
||||
if(power->info.gauge_is_ok) {
|
||||
canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
|
||||
if(power->state == PowerStateCharging) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
// TODO: replace -1 magic for uint8_t with re-framing
|
||||
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
}
|
||||
} else {
|
||||
canvas_draw_box(canvas, 8, 3, 8, 2);
|
||||
}
|
||||
}
|
||||
|
||||
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 = malloc(sizeof(Power));
|
||||
|
||||
// Records
|
||||
power->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
power->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
// Pubsub
|
||||
power->event_pubsub = furi_pubsub_alloc();
|
||||
|
||||
// State initialization
|
||||
power->state = PowerStateNotCharging;
|
||||
power->battery_low = false;
|
||||
power->power_off_timeout = POWER_OFF_TIMEOUT;
|
||||
power->api_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
// 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));
|
||||
power->power_unplug_usb = power_unplug_usb_alloc();
|
||||
view_dispatcher_add_view(
|
||||
power->view_dispatcher,
|
||||
PowerViewUnplugUsb,
|
||||
power_unplug_usb_get_view(power->power_unplug_usb));
|
||||
view_dispatcher_attach_to_gui(
|
||||
power->view_dispatcher, power->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Battery view port
|
||||
power->battery_view_port = power_battery_view_port_alloc(power);
|
||||
power->show_low_bat_level_message = true;
|
||||
|
||||
return power;
|
||||
}
|
||||
|
||||
void power_free(Power* power) {
|
||||
furi_assert(power);
|
||||
|
||||
// Gui
|
||||
view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff);
|
||||
power_off_free(power->power_off);
|
||||
view_dispatcher_remove_view(power->view_dispatcher, PowerViewUnplugUsb);
|
||||
power_unplug_usb_free(power->power_unplug_usb);
|
||||
|
||||
view_port_free(power->battery_view_port);
|
||||
|
||||
// State
|
||||
furi_mutex_free(power->api_mtx);
|
||||
|
||||
// FuriPubSub
|
||||
furi_pubsub_free(power->event_pubsub);
|
||||
|
||||
// Records
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
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;
|
||||
furi_pubsub_publish(power->event_pubsub, &power->event);
|
||||
}
|
||||
} else {
|
||||
if(power->state != PowerStateCharging) {
|
||||
notification_internal_message(power->notification, &sequence_charging);
|
||||
power->state = PowerStateCharging;
|
||||
power->event.type = PowerEventTypeStartCharging;
|
||||
furi_pubsub_publish(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;
|
||||
furi_pubsub_publish(power->event_pubsub, &power->event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool power_update_info(Power* power) {
|
||||
PowerInfo info;
|
||||
|
||||
info.gauge_is_ok = furi_hal_power_gauge_is_ok();
|
||||
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);
|
||||
|
||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
||||
bool need_refresh = power->info.charge != info.charge;
|
||||
power->info = info;
|
||||
furi_mutex_release(power->api_mtx);
|
||||
|
||||
return need_refresh;
|
||||
}
|
||||
|
||||
static void power_check_low_battery(Power* power) {
|
||||
if(!power->info.gauge_is_ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check battery charge and vbus voltage
|
||||
if((power->info.charge == 0) && (power->info.voltage_vbus < 4.0f) &&
|
||||
power->show_low_bat_level_message) {
|
||||
if(!power->battery_low) {
|
||||
view_dispatcher_send_to_front(power->view_dispatcher);
|
||||
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) {
|
||||
PowerOffResponse response = power_off_get_response(power->power_off);
|
||||
if(response == PowerOffResponseDefault) {
|
||||
if(power->power_off_timeout) {
|
||||
power_off_set_time_left(power->power_off, power->power_off_timeout--);
|
||||
} else {
|
||||
power_off(power);
|
||||
}
|
||||
} else if(response == PowerOffResponseOk) {
|
||||
power_off(power);
|
||||
} else if(response == PowerOffResponseHide) {
|
||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
||||
if(power->power_off_timeout) {
|
||||
power_off_set_time_left(power->power_off, power->power_off_timeout--);
|
||||
} else {
|
||||
power_off(power);
|
||||
}
|
||||
} else if(response == PowerOffResponseCancel) {
|
||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
furi_pubsub_publish(power->event_pubsub, &power->event);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t power_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Power* power = power_alloc();
|
||||
power_update_info(power);
|
||||
furi_record_create(RECORD_POWER, power);
|
||||
|
||||
while(1) {
|
||||
// Update data from gauge and charger
|
||||
bool need_refresh = 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
|
||||
if(need_refresh) view_port_update(power->battery_view_port);
|
||||
|
||||
// Check OTG status and disable it in case of fault
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_check_otg_status();
|
||||
}
|
||||
|
||||
furi_delay_ms(1000);
|
||||
}
|
||||
|
||||
power_free(power);
|
||||
|
||||
return 0;
|
||||
}
|
97
applications/services/power/power_service/power.h
Normal file
97
applications/services/power/power_service/power.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <core/pubsub.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RECORD_POWER "power"
|
||||
|
||||
typedef struct Power Power;
|
||||
|
||||
typedef enum {
|
||||
PowerBootModeNormal,
|
||||
PowerBootModeDfu,
|
||||
PowerBootModeUpdateStart,
|
||||
} PowerBootMode;
|
||||
|
||||
typedef enum {
|
||||
PowerEventTypeStopCharging,
|
||||
PowerEventTypeStartCharging,
|
||||
PowerEventTypeFullyCharged,
|
||||
PowerEventTypeBatteryLevelChanged,
|
||||
} PowerEventType;
|
||||
|
||||
typedef union {
|
||||
uint8_t battery_level;
|
||||
} PowerEventData;
|
||||
|
||||
typedef struct {
|
||||
PowerEventType type;
|
||||
PowerEventData data;
|
||||
} PowerEvent;
|
||||
|
||||
typedef struct {
|
||||
bool gauge_is_ok;
|
||||
|
||||
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(Power* power);
|
||||
|
||||
/** 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
|
||||
*
|
||||
* @return FuriPubSub instance
|
||||
*/
|
||||
FuriPubSub* power_get_pubsub(Power* power);
|
||||
|
||||
/** Check battery health
|
||||
*
|
||||
* @return true if battery is healthy
|
||||
*/
|
||||
bool power_is_battery_healthy(Power* power);
|
||||
|
||||
/** Enable or disable battery low level notification mesage
|
||||
*
|
||||
* @param power Power instance
|
||||
* @param enable true - enable, false - disable
|
||||
*/
|
||||
void power_enable_low_battery_level_notification(Power* power, bool enable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
55
applications/services/power/power_service/power_api.c
Normal file
55
applications/services/power/power_service/power_api.c
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "power_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <update_util/update_operation.h>
|
||||
|
||||
void power_off(Power* power) {
|
||||
furi_hal_power_off();
|
||||
// Notify user if USB is plugged
|
||||
view_dispatcher_send_to_front(power->view_dispatcher);
|
||||
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewUnplugUsb);
|
||||
furi_delay_ms(100);
|
||||
furi_halt("Disconnect USB for safe shutdown");
|
||||
}
|
||||
|
||||
void power_reboot(PowerBootMode mode) {
|
||||
if(mode == PowerBootModeNormal) {
|
||||
update_operation_disarm();
|
||||
} else if(mode == PowerBootModeDfu) {
|
||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
|
||||
} else if(mode == PowerBootModeUpdateStart) {
|
||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
|
||||
}
|
||||
furi_hal_power_reset();
|
||||
}
|
||||
|
||||
void power_get_info(Power* power, PowerInfo* info) {
|
||||
furi_assert(power);
|
||||
furi_assert(info);
|
||||
|
||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
||||
memcpy(info, &power->info, sizeof(power->info));
|
||||
furi_mutex_release(power->api_mtx);
|
||||
}
|
||||
|
||||
FuriPubSub* power_get_pubsub(Power* power) {
|
||||
furi_assert(power);
|
||||
return power->event_pubsub;
|
||||
}
|
||||
|
||||
bool power_is_battery_healthy(Power* power) {
|
||||
furi_assert(power);
|
||||
bool is_healthy = false;
|
||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
||||
is_healthy = power->info.health > POWER_BATTERY_HEALTHY_LEVEL;
|
||||
furi_mutex_release(power->api_mtx);
|
||||
return is_healthy;
|
||||
}
|
||||
|
||||
void power_enable_low_battery_level_notification(Power* power, bool enable) {
|
||||
furi_assert(power);
|
||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
||||
power->show_low_bat_level_message = enable;
|
||||
furi_mutex_release(power->api_mtx);
|
||||
}
|
48
applications/services/power/power_service/power_i.h
Normal file
48
applications/services/power/power_service/power_i.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "power.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#include <gui/modules/popup.h>
|
||||
#include "views/power_off.h"
|
||||
#include "views/power_unplug_usb.h"
|
||||
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define POWER_BATTERY_HEALTHY_LEVEL 70
|
||||
|
||||
typedef enum {
|
||||
PowerStateNotCharging,
|
||||
PowerStateCharging,
|
||||
PowerStateCharged,
|
||||
} PowerState;
|
||||
|
||||
struct Power {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
PowerOff* power_off;
|
||||
PowerUnplugUsb* power_unplug_usb;
|
||||
|
||||
ViewPort* battery_view_port;
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
FuriPubSub* event_pubsub;
|
||||
PowerEvent event;
|
||||
|
||||
PowerState state;
|
||||
PowerInfo info;
|
||||
|
||||
bool battery_low;
|
||||
bool show_low_bat_level_message;
|
||||
uint8_t battery_level;
|
||||
uint8_t power_off_timeout;
|
||||
|
||||
FuriMutex* api_mtx;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
PowerViewOff,
|
||||
PowerViewUnplugUsb,
|
||||
} PowerView;
|
105
applications/services/power/power_service/views/power_off.c
Normal file
105
applications/services/power/power_service/views/power_off.c
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "power_off.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct PowerOff {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
PowerOffResponse response;
|
||||
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);
|
||||
if(model->response == PowerOffResponseDefault) {
|
||||
snprintf(buff, sizeof(buff), "Charge me!\nOff in %lds!", 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");
|
||||
} else {
|
||||
snprintf(buff, sizeof(buff), "Charge me!\nDont't forget!");
|
||||
elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff);
|
||||
|
||||
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, "Hold a second...");
|
||||
}
|
||||
}
|
||||
|
||||
static bool power_off_input_callback(InputEvent* event, void* context) {
|
||||
PowerOff* power_off = context;
|
||||
|
||||
bool consumed = false;
|
||||
PowerOffModel* model = view_get_model(power_off->view);
|
||||
if(model->response == PowerOffResponseDefault && event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyOk) {
|
||||
model->response = PowerOffResponseOk;
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->response = PowerOffResponseCancel;
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->response = PowerOffResponseHide;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
view_commit_model(power_off->view, consumed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PowerOff* power_off_alloc() {
|
||||
PowerOff* power_off = malloc(sizeof(PowerOff));
|
||||
|
||||
power_off->view = view_alloc();
|
||||
view_allocate_model(power_off->view, ViewModelTypeLocking, sizeof(PowerOffModel));
|
||||
view_set_context(power_off->view, power_off);
|
||||
view_set_draw_callback(power_off->view, power_off_draw_callback);
|
||||
view_set_input_callback(power_off->view, power_off_input_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;
|
||||
});
|
||||
}
|
||||
|
||||
PowerOffResponse power_off_get_response(PowerOff* power_off) {
|
||||
furi_assert(power_off);
|
||||
PowerOffResponse response;
|
||||
with_view_model(
|
||||
power_off->view, (PowerOffModel * model) {
|
||||
response = model->response;
|
||||
return false;
|
||||
});
|
||||
return response;
|
||||
}
|
22
applications/services/power/power_service/views/power_off.h
Normal file
22
applications/services/power/power_service/views/power_off.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct PowerOff PowerOff;
|
||||
|
||||
typedef enum {
|
||||
PowerOffResponseDefault,
|
||||
PowerOffResponseOk,
|
||||
PowerOffResponseCancel,
|
||||
PowerOffResponseHide,
|
||||
} PowerOffResponse;
|
||||
|
||||
#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);
|
||||
|
||||
PowerOffResponse power_off_get_response(PowerOff* power_off);
|
@@ -0,0 +1,43 @@
|
||||
#include "power_unplug_usb.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct PowerUnplugUsb {
|
||||
View* view;
|
||||
};
|
||||
|
||||
static void power_unplug_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
UNUSED(_model);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Unplug_bg_top_128x14);
|
||||
canvas_draw_box(canvas, 0, 14, 128, (64 - 10 - 14));
|
||||
canvas_draw_icon(canvas, 0, (64 - 10), &I_Unplug_bg_bottom_128x10);
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 64, 32, AlignCenter, AlignCenter, "It's now safe to unplug\nthe USB cable");
|
||||
}
|
||||
|
||||
PowerUnplugUsb* power_unplug_usb_alloc() {
|
||||
PowerUnplugUsb* power_unplug_usb = malloc(sizeof(PowerUnplugUsb));
|
||||
|
||||
power_unplug_usb->view = view_alloc();
|
||||
view_set_context(power_unplug_usb->view, power_unplug_usb);
|
||||
view_set_draw_callback(power_unplug_usb->view, power_unplug_usb_draw_callback);
|
||||
view_set_input_callback(power_unplug_usb->view, NULL);
|
||||
|
||||
return power_unplug_usb;
|
||||
}
|
||||
|
||||
void power_unplug_usb_free(PowerUnplugUsb* power_unplug_usb) {
|
||||
furi_assert(power_unplug_usb);
|
||||
view_free(power_unplug_usb->view);
|
||||
free(power_unplug_usb);
|
||||
}
|
||||
|
||||
View* power_unplug_usb_get_view(PowerUnplugUsb* power_unplug_usb) {
|
||||
furi_assert(power_unplug_usb);
|
||||
return power_unplug_usb->view;
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct PowerUnplugUsb PowerUnplugUsb;
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
PowerUnplugUsb* power_unplug_usb_alloc();
|
||||
|
||||
void power_unplug_usb_free(PowerUnplugUsb* power_unplug_usb);
|
||||
|
||||
View* power_unplug_usb_get_view(PowerUnplugUsb* power_unplug_usb);
|
Reference in New Issue
Block a user