Merge remote-tracking branch 'ofw/dev' into mntm-dev

This commit is contained in:
Willy-JL
2025-02-28 00:56:55 +00:00
81 changed files with 1521 additions and 1815 deletions

View File

@@ -1,6 +1,8 @@
#include "expansion_worker.h"
#include <power/power_service/power.h>
#include <furi_hal_power.h>
#include <furi_hal_serial.h>
#include <furi_hal_serial_control.h>
@@ -251,9 +253,13 @@ static bool expansion_worker_handle_state_connected(
if(!expansion_worker_rpc_session_open(instance)) break;
instance->state = ExpansionWorkerStateRpcActive;
} else if(command == ExpansionFrameControlCommandEnableOtg) {
if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, true);
furi_record_close(RECORD_POWER);
} else if(command == ExpansionFrameControlCommandDisableOtg) {
if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, false);
furi_record_close(RECORD_POWER);
} else {
break;
}

View File

@@ -2,6 +2,7 @@
#include <gui/canvas.h>
#include <gui/elements.h>
#include <input/input.h>
#include <furi.h>
#include <furi_hal_resources.h>
@@ -46,6 +47,7 @@ ARRAY_DEF(ButtonMatrix, ButtonArray_t);
struct ButtonPanel {
View* view;
bool freeze_input;
};
typedef struct {
@@ -63,7 +65,7 @@ static void button_panel_process_up(ButtonPanel* button_panel);
static void button_panel_process_down(ButtonPanel* button_panel);
static void button_panel_process_left(ButtonPanel* button_panel);
static void button_panel_process_right(ButtonPanel* button_panel);
static void button_panel_process_ok(ButtonPanel* button_panel);
static void button_panel_process_ok(ButtonPanel* button_panel, InputType input);
static void button_panel_view_draw_callback(Canvas* canvas, void* _model);
static bool button_panel_view_input_callback(InputEvent* event, void* context);
@@ -347,7 +349,7 @@ static void button_panel_process_right(ButtonPanel* button_panel) {
true);
}
void button_panel_process_ok(ButtonPanel* button_panel) {
void button_panel_process_ok(ButtonPanel* button_panel, InputType type) {
ButtonItem* button_item = NULL;
with_view_model(
@@ -360,7 +362,7 @@ void button_panel_process_ok(ButtonPanel* button_panel) {
true);
if(button_item && button_item->callback) {
button_item->callback(button_item->callback_context, button_item->index);
button_item->callback(button_item->callback_context, button_item->index, type);
}
}
@@ -368,8 +370,15 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) {
ButtonPanel* button_panel = context;
furi_assert(button_panel);
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
if((event->type == InputTypePress) || (event->type == InputTypeRelease)) {
button_panel->freeze_input = (event->type == InputTypePress);
}
consumed = true;
button_panel_process_ok(button_panel, event->type);
}
if(!button_panel->freeze_input &&
(!(event->type == InputTypePress) && !(event->type == InputTypeRelease))) {
switch(event->key) {
case InputKeyUp:
consumed = true;
@@ -387,10 +396,6 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) {
consumed = true;
button_panel_process_right(button_panel);
break;
case InputKeyOk:
consumed = true;
button_panel_process_ok(button_panel);
break;
default:
break;
}

View File

@@ -15,7 +15,7 @@ extern "C" {
typedef struct ButtonPanel ButtonPanel;
/** Callback type to call for handling selecting button_panel items */
typedef void (*ButtonItemCallback)(void* context, uint32_t index);
typedef void (*ButtonItemCallback)(void* context, uint32_t index, InputType type);
/** Allocate new button_panel module.
*

View File

@@ -119,7 +119,7 @@ static void widget_add_element(Widget* widget, WidgetElement* element) {
true);
}
WidgetElement* widget_add_string_multiline_element(
void widget_add_string_multiline_element(
Widget* widget,
uint8_t x,
uint8_t y,
@@ -131,10 +131,9 @@ WidgetElement* widget_add_string_multiline_element(
WidgetElement* string_multiline_element =
widget_element_string_multiline_create(x, y, horizontal, vertical, font, text);
widget_add_element(widget, string_multiline_element);
return string_multiline_element;
}
WidgetElement* widget_add_string_element(
void widget_add_string_element(
Widget* widget,
uint8_t x,
uint8_t y,
@@ -146,7 +145,6 @@ WidgetElement* widget_add_string_element(
WidgetElement* string_element =
widget_element_string_create(x, y, horizontal, vertical, font, text);
widget_add_element(widget, string_element);
return string_element;
}
WidgetElement* widget_add_text_box_element(
@@ -166,7 +164,7 @@ WidgetElement* widget_add_text_box_element(
return text_box_element;
}
WidgetElement* widget_add_text_scroll_element(
void widget_add_text_scroll_element(
Widget* widget,
uint8_t x,
uint8_t y,
@@ -177,10 +175,9 @@ WidgetElement* widget_add_text_scroll_element(
WidgetElement* text_scroll_element =
widget_element_text_scroll_create(x, y, width, height, text);
widget_add_element(widget, text_scroll_element);
return text_scroll_element;
}
WidgetElement* widget_add_button_element(
void widget_add_button_element(
Widget* widget,
GuiButtonType button_type,
const char* text,
@@ -190,26 +187,36 @@ WidgetElement* widget_add_button_element(
WidgetElement* button_element =
widget_element_button_create(button_type, text, callback, context);
widget_add_element(widget, button_element);
return button_element;
}
WidgetElement* widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon) {
void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon) {
furi_check(widget);
furi_check(icon);
WidgetElement* icon_element = widget_element_icon_create(x, y, icon);
widget_add_element(widget, icon_element);
return icon_element;
}
WidgetElement* widget_add_frame_element(
void widget_add_rect_element(
Widget* widget,
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius) {
uint8_t radius,
bool fill) {
furi_check(widget);
WidgetElement* frame_element = widget_element_frame_create(x, y, width, height, radius);
widget_add_element(widget, frame_element);
return frame_element;
WidgetElement* rect_element = widget_element_rect_create(x, y, width, height, radius, fill);
widget_add_element(widget, rect_element);
}
void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill) {
furi_check(widget);
WidgetElement* circle_element = widget_element_circle_create(x, y, radius, fill);
widget_add_element(widget, circle_element);
}
void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
furi_check(widget);
WidgetElement* line_element = widget_element_line_create(x1, y1, x2, y2);
widget_add_element(widget, line_element);
}

View File

@@ -51,7 +51,7 @@ View* widget_get_view(Widget* widget);
* @param font Font instance
* @param[in] text The text
*/
WidgetElement* widget_add_string_multiline_element(
void widget_add_string_multiline_element(
Widget* widget,
uint8_t x,
uint8_t y,
@@ -70,7 +70,7 @@ WidgetElement* widget_add_string_multiline_element(
* @param font Font instance
* @param[in] text The text
*/
WidgetElement* widget_add_string_element(
void widget_add_string_element(
Widget* widget,
uint8_t x,
uint8_t y,
@@ -119,7 +119,7 @@ WidgetElement* widget_add_text_box_element(
* "\ecCenter-aligned text" - sets center horizontal align until the next '\n' symbol
* "\erRight-aligned text" - sets right horizontal align until the next '\n' symbol
*/
WidgetElement* widget_add_text_scroll_element(
void widget_add_text_scroll_element(
Widget* widget,
uint8_t x,
uint8_t y,
@@ -135,7 +135,7 @@ WidgetElement* widget_add_text_scroll_element(
* @param callback ButtonCallback instance
* @param context pointer to context
*/
WidgetElement* widget_add_button_element(
void widget_add_button_element(
Widget* widget,
GuiButtonType button_type,
const char* text,
@@ -149,24 +149,60 @@ WidgetElement* widget_add_button_element(
* @param y top left y coordinate
* @param icon Icon instance
*/
WidgetElement* widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon);
void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon);
/** Add Frame Element
*
* @param widget Widget instance
* @param x top left x coordinate
* @param y top left y coordinate
* @param width frame width
* @param height frame height
* @param radius frame radius
*
* @warning deprecated, use widget_add_rect_element instead
*/
#define widget_add_frame_element(widget, x, y, width, height, radius) \
widget_add_rect_element((widget), (x), (y), (width), (height), (radius), false)
/** Add Rect Element
*
* @param widget Widget instance
* @param x top left x coordinate
* @param y top left y coordinate
* @param width frame width
* @param height frame height
* @param radius frame radius
* @param width rect width
* @param height rect height
* @param radius corner radius
* @param fill whether to fill the box or not
*/
WidgetElement* widget_add_frame_element(
void widget_add_rect_element(
Widget* widget,
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius);
uint8_t radius,
bool fill);
/** Add Circle Element
*
* @param widget Widget instance
* @param x center x coordinate
* @param y center y coordinate
* @param radius circle radius
* @param fill whether to fill the circle or not
*/
void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill);
/** Add Line Element
*
* @param widget Widget instance
* @param x1 first x coordinate
* @param y1 first y coordinate
* @param x2 second x coordinate
* @param y2 second y coordinate
*/
void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
#ifdef __cplusplus
}

View File

@@ -5,6 +5,8 @@
#pragma once
#include <input/input.h>
#ifdef __cplusplus
extern "C" {
#endif

View File

@@ -0,0 +1,45 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x;
uint8_t y;
uint8_t radius;
bool fill;
} GuiCircleModel;
static void gui_circle_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiCircleModel* model = element->model;
if(model->fill) {
canvas_draw_disc(canvas, model->x, model->y, model->radius);
} else {
canvas_draw_circle(canvas, model->x, model->y, model->radius);
}
}
static void gui_circle_free(WidgetElement* gui_circle) {
furi_assert(gui_circle);
free(gui_circle->model);
free(gui_circle);
}
WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill) {
// Allocate and init model
GuiCircleModel* model = malloc(sizeof(GuiCircleModel));
model->x = x;
model->y = y;
model->radius = radius;
model->fill = fill;
// Allocate and init Element
WidgetElement* gui_circle = malloc(sizeof(WidgetElement));
gui_circle->parent = NULL;
gui_circle->input = NULL;
gui_circle->draw = gui_circle_draw;
gui_circle->free = gui_circle_free;
gui_circle->model = model;
return gui_circle;
}

View File

@@ -1,48 +0,0 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height;
uint8_t radius;
} GuiFrameModel;
static void gui_frame_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiFrameModel* model = element->model;
canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius);
}
static void gui_frame_free(WidgetElement* gui_frame) {
furi_assert(gui_frame);
free(gui_frame->model);
free(gui_frame);
}
WidgetElement* widget_element_frame_create(
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius) {
// Allocate and init model
GuiFrameModel* model = malloc(sizeof(GuiFrameModel));
model->x = x;
model->y = y;
model->width = width;
model->height = height;
model->radius = radius;
// Allocate and init Element
WidgetElement* gui_frame = malloc(sizeof(WidgetElement));
gui_frame->parent = NULL;
gui_frame->input = NULL;
gui_frame->draw = gui_frame_draw;
gui_frame->free = gui_frame_free;
gui_frame->model = model;
return gui_frame;
}

View File

@@ -76,14 +76,16 @@ WidgetElement* widget_element_button_create(
/** Create icon element */
WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon);
/** Create frame element */
WidgetElement* widget_element_frame_create(
/** Create rect element */
WidgetElement* widget_element_rect_create(
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius);
uint8_t radius,
bool fill);
/** Create text scroll element */
WidgetElement* widget_element_text_scroll_create(
uint8_t x,
uint8_t y,
@@ -91,6 +93,12 @@ WidgetElement* widget_element_text_scroll_create(
uint8_t height,
const char* text);
/** Create circle element */
WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill);
/** Create line element */
WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,41 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x1;
uint8_t y1;
uint8_t x2;
uint8_t y2;
} GuiLineModel;
static void gui_line_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiLineModel* model = element->model;
canvas_draw_line(canvas, model->x1, model->y1, model->x2, model->y2);
}
static void gui_line_free(WidgetElement* gui_line) {
furi_assert(gui_line);
free(gui_line->model);
free(gui_line);
}
WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
// Allocate and init model
GuiLineModel* model = malloc(sizeof(GuiLineModel));
model->x1 = x1;
model->y1 = y1;
model->x2 = x2;
model->y2 = y2;
// Allocate and init Element
WidgetElement* gui_line = malloc(sizeof(WidgetElement));
gui_line->parent = NULL;
gui_line->input = NULL;
gui_line->draw = gui_line_draw;
gui_line->free = gui_line_free;
gui_line->model = model;
return gui_line;
}

View File

@@ -0,0 +1,55 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height;
uint8_t radius;
bool fill;
} GuiRectModel;
static void gui_rect_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiRectModel* model = element->model;
if(model->fill) {
canvas_draw_rbox(canvas, model->x, model->y, model->width, model->height, model->radius);
} else {
canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius);
}
}
static void gui_rect_free(WidgetElement* gui_rect) {
furi_assert(gui_rect);
free(gui_rect->model);
free(gui_rect);
}
WidgetElement* widget_element_rect_create(
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius,
bool fill) {
// Allocate and init model
GuiRectModel* model = malloc(sizeof(GuiRectModel));
model->x = x;
model->y = y;
model->width = width;
model->height = height;
model->radius = radius;
model->fill = fill;
// Allocate and init Element
WidgetElement* gui_rect = malloc(sizeof(WidgetElement));
gui_rect->parent = NULL;
gui_rect->input = NULL;
gui_rect->draw = gui_rect_draw;
gui_rect->free = gui_rect_free;
gui_rect->model = model;
return gui_rect;
}

View File

@@ -30,13 +30,16 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
void power_cli_5v(Cli* cli, FuriString* args) {
UNUSED(cli);
Power* power = furi_record_open(RECORD_POWER);
if(!furi_string_cmp(args, "0")) {
furi_hal_power_disable_otg();
power_enable_otg(power, false);
} else if(!furi_string_cmp(args, "1")) {
furi_hal_power_enable_otg();
power_enable_otg(power, true);
} else {
cli_print_usage("power_otg", "<1|0>", furi_string_get_cstr(args));
}
furi_record_close(RECORD_POWER);
}
void power_cli_3v3(Cli* cli, FuriString* args) {

View File

@@ -258,6 +258,7 @@ static bool power_update_info(Power* power) {
.is_charging = furi_hal_power_is_charging(),
.gauge_is_ok = furi_hal_power_gauge_is_ok(),
.is_shutdown_requested = furi_hal_power_is_shutdown_requested(),
.is_otg_enabled = furi_hal_power_is_otg_enabled(),
.charge = furi_hal_power_get_pct(),
.health = furi_hal_power_get_bat_health_pct(),
.capacity_remaining = furi_hal_power_get_battery_remaining_capacity(),
@@ -520,6 +521,30 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) {
case PowerMessageTypeShowBatteryLowWarning:
power->show_battery_low_warning = *msg.bool_param;
break;
case PowerMessageTypeSwitchOTG:
power->is_otg_requested = *msg.bool_param;
if(power->is_otg_requested) {
// Only try to enable if VBUS voltage is low, otherwise charger will refuse
if(power->info.voltage_vbus < 4.5f) {
size_t retries = 5;
while(retries-- > 0) {
if(furi_hal_power_enable_otg()) {
break;
}
}
if(!retries) {
FURI_LOG_W(TAG, "Failed to enable OTG, will try later");
}
} else {
FURI_LOG_W(
TAG,
"Postponing OTG enable: VBUS(%0.1f) >= 4.5v",
(double)power->info.voltage_vbus);
}
} else {
furi_hal_power_disable_otg();
}
break;
case PowerMessageTypeGetSettings:
furi_assert(msg.lock);
*msg.settings = power->settings;
@@ -563,9 +588,18 @@ static void power_tick_callback(void* context) {
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();
// Check OTG status, disable in case of a fault
if(furi_hal_power_check_otg_fault()) {
FURI_LOG_E(TAG, "OTG fault detected, disabling OTG");
furi_hal_power_disable_otg();
power->is_otg_requested = false;
}
// Change OTG state if needed (i.e. after disconnecting USB power)
if(power->is_otg_requested &&
(!power->info.is_otg_enabled && power->info.voltage_vbus < 4.5f)) {
FURI_LOG_D(TAG, "OTG requested but not enabled, enabling OTG");
furi_hal_power_enable_otg();
}
}

View File

@@ -39,6 +39,7 @@ typedef struct {
bool gauge_is_ok;
bool is_charging;
bool is_shutdown_requested;
bool is_otg_enabled;
float current_charger;
float current_gauge;
@@ -96,6 +97,19 @@ bool power_is_battery_healthy(Power* power);
*/
void power_enable_low_battery_level_notification(Power* power, bool enable);
/** Enable or disable OTG
*
* @param power Power instance
* @param enable true - enable, false - disable
*/
void power_enable_otg(Power* power, bool enable);
/** Check OTG status
*
* @return true if OTG is requested
*/
bool power_is_otg_enabled(Power* power);
#ifdef __cplusplus
}
#endif

View File

@@ -71,6 +71,25 @@ void power_enable_low_battery_level_notification(Power* power, bool enable) {
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
void power_enable_otg(Power* power, bool enable) {
furi_check(power);
PowerMessage msg = {
.type = PowerMessageTypeSwitchOTG,
.bool_param = &enable,
.lock = api_lock_alloc_locked(),
};
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(msg.lock);
}
bool power_is_otg_enabled(Power* power) {
furi_check(power);
return power->is_otg_requested;
}
/*
* Private API for the Settings app
*/

View File

@@ -35,6 +35,7 @@ struct Power {
bool battery_low;
bool show_battery_low_warning;
bool is_otg_requested;
uint8_t battery_level;
uint8_t power_off_timeout;
@@ -59,6 +60,7 @@ typedef enum {
PowerMessageTypeGetInfo,
PowerMessageTypeIsBatteryHealthy,
PowerMessageTypeShowBatteryLowWarning,
PowerMessageTypeSwitchOTG,
PowerMessageTypeGetSettings,
PowerMessageTypeSetSettings,

View File

@@ -4,6 +4,7 @@
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <furi_hal_resources.h>
#include <power/power_service/power.h>
static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
switch(rpc_pin) {
@@ -218,12 +219,16 @@ void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) {
const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode;
Power* power = furi_record_open(RECORD_POWER);
if(mode == PB_Gpio_GpioOtgMode_OFF) {
if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
power_enable_otg(power, false);
} else {
if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg();
power_enable_otg(power, true);
}
furi_record_close(RECORD_POWER);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}