mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-12 20:08:36 -07:00
[FL-3841] FuriEventLoop Pt.2 (#3703)
* Abstract primitive type from main logic in FuriEventLoop * Remove message_queue_i.h * Add stream buffer support for event loop * Add semaphore support for event loop * Add temporary unit test workaround * Make the linter happy * Add mutex support for event loop * Implement event subscription and unsubscription while the event loop is running * Implement edge events * Fix leftover logical errors * Add event loop timer example application * Implement flag-based edge trigger and one-shot mode * Add event loop mutex example application * Only notify the event loop if stream buffer is at or above its trigger level * Reformat comments * Add event loop stream buffer example application * Add event loop multiple elements example application * Improve event loop flag names * Remove redundant signal handler as it is already handled by the event loop * Refactor Power service, improve ViewHolder * Use ViewHolder instead of ViewDispatcher in About app * Enable ViewDispatcher queue on construction, deprecate view_dispatcher_enable_queue() * Remove all invocations of view_dispatcher_enable_queue() * Remove app-scened-template * Remove missing library from target.json * Port Accessor app to ViewHolder * Make the linter happy * Add example_view_holder application, update ViewHolder docs * Add example_view_dispatcher application, update ViewDispatcher docs * Replace FuriSemaphore with FuriApiLock, remove workaround delay * Fix logical error * Fix another logical error * Use the sources directive to speed up compilation * Use constant define macro * Improve FuriEventLoop documentation * Improve FuriEventLoop documentation once more * Bump API Version * Gui: remove redundant checks from ViewDispatcher * Gui: remove dead ifs from ViewDispatcher Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com> Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
@@ -265,7 +265,6 @@ static Desktop* desktop_alloc(void) {
|
||||
desktop->view_dispatcher = view_dispatcher_alloc();
|
||||
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
|
||||
|
||||
view_dispatcher_enable_queue(desktop->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(
|
||||
desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
|
||||
@@ -49,12 +49,11 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
|
||||
file_browser_start(file_browser, data->preselected_filename);
|
||||
|
||||
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
|
||||
view_holder_start(view_holder);
|
||||
api_lock_wait_unlock(file_browser_context->lock);
|
||||
|
||||
ret = file_browser_context->result;
|
||||
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
view_holder_free(view_holder);
|
||||
file_browser_stop(file_browser);
|
||||
file_browser_free(file_browser);
|
||||
|
||||
@@ -88,12 +88,11 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa
|
||||
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
|
||||
|
||||
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
|
||||
view_holder_start(view_holder);
|
||||
api_lock_wait_unlock(message_context->lock);
|
||||
|
||||
ret = message_context->result;
|
||||
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
view_holder_free(view_holder);
|
||||
dialog_ex_free(dialog_ex);
|
||||
api_lock_free(message_context->lock);
|
||||
|
||||
@@ -192,8 +192,8 @@ static void dolphin_update_clear_limits_timer_period(void* context) {
|
||||
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
|
||||
}
|
||||
|
||||
static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
|
||||
UNUSED(queue);
|
||||
static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
|
||||
UNUSED(object);
|
||||
|
||||
Dolphin* dolphin = context;
|
||||
DolphinEvent event;
|
||||
@@ -280,7 +280,7 @@ int32_t dolphin_srv(void* p) {
|
||||
|
||||
dolphin_init_state(dolphin);
|
||||
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
dolphin->event_loop,
|
||||
dolphin->event_queue,
|
||||
FuriEventLoopEventIn,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#define TAG "ViewDispatcher"
|
||||
|
||||
#define VIEW_DISPATCHER_QUEUE_LEN (16U)
|
||||
|
||||
ViewDispatcher* view_dispatcher_alloc(void) {
|
||||
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
|
||||
|
||||
@@ -14,6 +16,26 @@ ViewDispatcher* view_dispatcher_alloc(void) {
|
||||
|
||||
ViewDict_init(view_dispatcher->views);
|
||||
|
||||
view_dispatcher->event_loop = furi_event_loop_alloc();
|
||||
|
||||
view_dispatcher->input_queue =
|
||||
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent));
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
view_dispatcher->event_loop,
|
||||
view_dispatcher->input_queue,
|
||||
FuriEventLoopEventIn,
|
||||
view_dispatcher_run_input_callback,
|
||||
view_dispatcher);
|
||||
|
||||
view_dispatcher->event_queue =
|
||||
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(uint32_t));
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
view_dispatcher->event_loop,
|
||||
view_dispatcher->event_queue,
|
||||
FuriEventLoopEventIn,
|
||||
view_dispatcher_run_event_callback,
|
||||
view_dispatcher);
|
||||
|
||||
return view_dispatcher;
|
||||
}
|
||||
|
||||
@@ -29,44 +51,19 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) {
|
||||
// Free ViewPort
|
||||
view_port_free(view_dispatcher->view_port);
|
||||
// Free internal queue
|
||||
if(view_dispatcher->input_queue) {
|
||||
furi_event_loop_message_queue_unsubscribe(
|
||||
view_dispatcher->event_loop, view_dispatcher->input_queue);
|
||||
furi_message_queue_free(view_dispatcher->input_queue);
|
||||
}
|
||||
if(view_dispatcher->event_queue) {
|
||||
furi_event_loop_message_queue_unsubscribe(
|
||||
view_dispatcher->event_loop, view_dispatcher->event_queue);
|
||||
furi_message_queue_free(view_dispatcher->event_queue);
|
||||
}
|
||||
if(view_dispatcher->event_loop) {
|
||||
furi_event_loop_free(view_dispatcher->event_loop);
|
||||
}
|
||||
furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->input_queue);
|
||||
furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->event_queue);
|
||||
|
||||
furi_message_queue_free(view_dispatcher->input_queue);
|
||||
furi_message_queue_free(view_dispatcher->event_queue);
|
||||
|
||||
furi_event_loop_free(view_dispatcher->event_loop);
|
||||
// Free dispatcher
|
||||
free(view_dispatcher);
|
||||
}
|
||||
|
||||
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) {
|
||||
furi_check(view_dispatcher);
|
||||
furi_check(view_dispatcher->event_loop == NULL);
|
||||
|
||||
view_dispatcher->event_loop = furi_event_loop_alloc();
|
||||
|
||||
view_dispatcher->input_queue = furi_message_queue_alloc(16, sizeof(InputEvent));
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
view_dispatcher->event_loop,
|
||||
view_dispatcher->input_queue,
|
||||
FuriEventLoopEventIn,
|
||||
view_dispatcher_run_input_callback,
|
||||
view_dispatcher);
|
||||
|
||||
view_dispatcher->event_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
view_dispatcher->event_loop,
|
||||
view_dispatcher->event_queue,
|
||||
FuriEventLoopEventIn,
|
||||
view_dispatcher_run_event_callback,
|
||||
view_dispatcher);
|
||||
UNUSED(view_dispatcher);
|
||||
}
|
||||
|
||||
void view_dispatcher_set_navigation_event_callback(
|
||||
@@ -99,14 +96,12 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
|
||||
|
||||
FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher) {
|
||||
furi_check(view_dispatcher);
|
||||
furi_check(view_dispatcher->event_loop);
|
||||
|
||||
return view_dispatcher->event_loop;
|
||||
}
|
||||
|
||||
void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
||||
furi_check(view_dispatcher);
|
||||
furi_check(view_dispatcher->event_loop);
|
||||
|
||||
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
|
||||
view_dispatcher->tick_period;
|
||||
@@ -134,7 +129,6 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
||||
|
||||
void view_dispatcher_stop(ViewDispatcher* view_dispatcher) {
|
||||
furi_check(view_dispatcher);
|
||||
furi_check(view_dispatcher->event_loop);
|
||||
furi_event_loop_stop(view_dispatcher->event_loop);
|
||||
}
|
||||
|
||||
@@ -242,13 +236,9 @@ void view_dispatcher_draw_callback(Canvas* canvas, void* context) {
|
||||
|
||||
void view_dispatcher_input_callback(InputEvent* event, void* context) {
|
||||
ViewDispatcher* view_dispatcher = context;
|
||||
if(view_dispatcher->input_queue) {
|
||||
furi_check(
|
||||
furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
} else {
|
||||
view_dispatcher_handle_input(view_dispatcher, event);
|
||||
}
|
||||
furi_check(
|
||||
furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
}
|
||||
|
||||
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
|
||||
@@ -328,7 +318,6 @@ void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32
|
||||
|
||||
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
|
||||
furi_check(view_dispatcher);
|
||||
furi_check(view_dispatcher->event_loop);
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(view_dispatcher->event_queue, &event, FuriWaitForever) ==
|
||||
@@ -364,9 +353,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
|
||||
view_port_update(view_dispatcher->view_port);
|
||||
} else {
|
||||
view_port_enabled_set(view_dispatcher->view_port, false);
|
||||
if(view_dispatcher->event_loop) {
|
||||
view_dispatcher_stop(view_dispatcher);
|
||||
}
|
||||
view_dispatcher_stop(view_dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,10 +368,10 @@ void view_dispatcher_update(View* view, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context) {
|
||||
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
ViewDispatcher* instance = context;
|
||||
furi_assert(instance->event_queue == queue);
|
||||
furi_assert(instance->event_queue == object);
|
||||
|
||||
uint32_t event;
|
||||
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
|
||||
@@ -393,10 +380,10 @@ bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context) {
|
||||
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
ViewDispatcher* instance = context;
|
||||
furi_assert(instance->input_queue == queue);
|
||||
furi_assert(instance->input_queue == object);
|
||||
|
||||
InputEvent input;
|
||||
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
* @file view_dispatcher.h
|
||||
* @brief GUI: ViewDispatcher API
|
||||
*
|
||||
* ViewDispatcher is used to connect several Views to a Gui instance, switch between them and handle various events.
|
||||
* This is useful in applications featuring an advanced graphical user interface.
|
||||
*
|
||||
* Internally, ViewDispatcher employs a FuriEventLoop instance together with two separate
|
||||
* message queues for input and custom event handling. See FuriEventLoop for more information.
|
||||
*
|
||||
* If no multi-view or complex event handling capabilities are required, consider using ViewHolder instead.
|
||||
*
|
||||
* @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time.
|
||||
*/
|
||||
|
||||
@@ -40,6 +48,9 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context);
|
||||
ViewDispatcher* view_dispatcher_alloc(void);
|
||||
|
||||
/** Free ViewDispatcher instance
|
||||
*
|
||||
* @warning All added views MUST be removed using view_dispatcher_remove_view()
|
||||
* before calling this function.
|
||||
*
|
||||
* @param view_dispatcher pointer to ViewDispatcher
|
||||
*/
|
||||
@@ -47,12 +58,13 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Enable queue support
|
||||
*
|
||||
* Allocates event_loop, input and event message queues. Must be used with
|
||||
* `view_dispatcher_run`
|
||||
* @deprecated Do NOT use in new code and remove all calls to it from existing code.
|
||||
* The queue support is now always enabled during construction. If no queue support
|
||||
* is required, consider using ViewHolder instead.
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
|
||||
FURI_DEPRECATED void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Send custom event
|
||||
*
|
||||
@@ -103,11 +115,11 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
|
||||
|
||||
/** Get event_loop instance
|
||||
*
|
||||
* event_loop instance is allocated on `view_dispatcher_enable_queue` and used
|
||||
* in view_dispatcher_run.
|
||||
* Use the return value to connect additional supported primitives (message queues, timers, etc)
|
||||
* to this ViewDispatcher instance's event loop.
|
||||
*
|
||||
* You can add your objects into event_loop instance, but don't run the loop on
|
||||
* your side as it will cause issues with input processing on dispatcher stop.
|
||||
* @warning Do NOT call furi_event_loop_run() on the returned instance, it is done internally
|
||||
* in the view_dispatcher_run() call.
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*
|
||||
@@ -117,15 +129,14 @@ FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Run ViewDispatcher
|
||||
*
|
||||
* Use only after queue enabled
|
||||
* This function will start the event loop and block until view_dispatcher_stop() is called
|
||||
* or the current thread receives a FuriSignalExit signal.
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_run(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Stop ViewDispatcher
|
||||
*
|
||||
* Use only after queue enabled
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
|
||||
@@ -56,7 +56,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
|
||||
void view_dispatcher_update(View* view, void* context);
|
||||
|
||||
/** ViewDispatcher run event loop event callback */
|
||||
bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context);
|
||||
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
|
||||
|
||||
/** ViewDispatcher run event loop input callback */
|
||||
bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context);
|
||||
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
|
||||
|
||||
@@ -32,7 +32,8 @@ ViewHolder* view_holder_alloc(void) {
|
||||
}
|
||||
|
||||
void view_holder_free(ViewHolder* view_holder) {
|
||||
furi_assert(view_holder);
|
||||
furi_check(view_holder);
|
||||
furi_check(view_holder->view == NULL);
|
||||
|
||||
if(view_holder->gui) {
|
||||
gui_remove_view_port(view_holder->gui, view_holder->view_port);
|
||||
@@ -48,12 +49,14 @@ void view_holder_free(ViewHolder* view_holder) {
|
||||
}
|
||||
|
||||
void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||
furi_assert(view_holder);
|
||||
furi_check(view_holder);
|
||||
|
||||
if(view_holder->view) {
|
||||
if(view_holder->view->exit_callback) {
|
||||
view_holder->view->exit_callback(view_holder->view->context);
|
||||
while(view_holder->ongoing_input) {
|
||||
furi_delay_tick(1);
|
||||
}
|
||||
|
||||
view_exit(view_holder->view);
|
||||
view_set_update_callback(view_holder->view, NULL);
|
||||
view_set_update_callback_context(view_holder->view, NULL);
|
||||
}
|
||||
@@ -61,12 +64,23 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||
view_holder->view = view;
|
||||
|
||||
if(view_holder->view) {
|
||||
const ViewPortOrientation orientation = (ViewPortOrientation)view->orientation;
|
||||
furi_assert(orientation < ViewPortOrientationMAX);
|
||||
if(view_port_get_orientation(view_holder->view_port) != orientation) {
|
||||
view_port_set_orientation(view_holder->view_port, orientation);
|
||||
// we just rotated input keys, now it's time to sacrifice some input
|
||||
view_holder->ongoing_input = 0;
|
||||
}
|
||||
|
||||
view_set_update_callback(view_holder->view, view_holder_update);
|
||||
view_set_update_callback_context(view_holder->view, view_holder);
|
||||
|
||||
if(view_holder->view->enter_callback) {
|
||||
view_holder->view->enter_callback(view_holder->view->context);
|
||||
}
|
||||
view_enter(view_holder->view);
|
||||
view_port_enabled_set(view_holder->view_port, true);
|
||||
view_port_update(view_holder->view_port);
|
||||
|
||||
} else {
|
||||
view_port_enabled_set(view_holder->view_port, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +88,7 @@ void view_holder_set_free_callback(
|
||||
ViewHolder* view_holder,
|
||||
FreeCallback free_callback,
|
||||
void* free_context) {
|
||||
furi_assert(view_holder);
|
||||
furi_check(view_holder);
|
||||
view_holder->free_callback = free_callback;
|
||||
view_holder->free_context = free_context;
|
||||
}
|
||||
@@ -87,31 +101,22 @@ void view_holder_set_back_callback(
|
||||
ViewHolder* view_holder,
|
||||
BackCallback back_callback,
|
||||
void* back_context) {
|
||||
furi_assert(view_holder);
|
||||
furi_check(view_holder);
|
||||
view_holder->back_callback = back_callback;
|
||||
view_holder->back_context = back_context;
|
||||
}
|
||||
|
||||
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_holder);
|
||||
view_holder->gui = gui;
|
||||
furi_check(view_holder);
|
||||
furi_check(view_holder->gui == NULL);
|
||||
furi_check(gui);
|
||||
gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
|
||||
}
|
||||
|
||||
void view_holder_start(ViewHolder* view_holder) {
|
||||
view_port_enabled_set(view_holder->view_port, true);
|
||||
}
|
||||
|
||||
void view_holder_stop(ViewHolder* view_holder) {
|
||||
while(view_holder->ongoing_input)
|
||||
furi_delay_tick(1);
|
||||
view_port_enabled_set(view_holder->view_port, false);
|
||||
view_holder->gui = gui;
|
||||
}
|
||||
|
||||
void view_holder_update(View* view, void* context) {
|
||||
furi_assert(view);
|
||||
furi_assert(context);
|
||||
furi_check(view);
|
||||
furi_check(context);
|
||||
|
||||
ViewHolder* view_holder = context;
|
||||
if(view == view_holder->view) {
|
||||
@@ -119,6 +124,18 @@ void view_holder_update(View* view, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void view_holder_send_to_front(ViewHolder* view_holder) {
|
||||
furi_check(view_holder);
|
||||
furi_check(view_holder->gui);
|
||||
gui_view_port_send_to_front(view_holder->gui, view_holder->view_port);
|
||||
}
|
||||
|
||||
void view_holder_send_to_back(ViewHolder* view_holder) {
|
||||
furi_check(view_holder);
|
||||
furi_check(view_holder->gui);
|
||||
gui_view_port_send_to_back(view_holder->gui, view_holder->view_port);
|
||||
}
|
||||
|
||||
static void view_holder_draw_callback(Canvas* canvas, void* context) {
|
||||
ViewHolder* view_holder = context;
|
||||
if(view_holder->view) {
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
* @file view_holder.h
|
||||
* @brief GUI: ViewHolder API
|
||||
*
|
||||
* @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time.
|
||||
* ViewHolder is used to connect a single View to a Gui instance. This is useful in smaller applications
|
||||
* with a simple user interface. If advanced view switching capabilites are required, consider using ViewDispatcher instead.
|
||||
*
|
||||
* @warning Views added to a ViewHolder MUST NOT be in a ViewStack at the same time.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
@@ -22,7 +25,8 @@ typedef void (*FreeCallback)(void* free_context);
|
||||
|
||||
/**
|
||||
* @brief Back callback type
|
||||
* @warning comes from GUI thread
|
||||
*
|
||||
* @warning Will be called from the GUI thread
|
||||
*/
|
||||
typedef void (*BackCallback)(void* back_context);
|
||||
|
||||
@@ -34,12 +38,17 @@ ViewHolder* view_holder_alloc(void);
|
||||
|
||||
/**
|
||||
* @brief Free ViewHolder and call Free callback
|
||||
*
|
||||
* @warning The current view must be unset prior to freeing a ViewHolder instance.
|
||||
*
|
||||
* @param view_holder pointer to ViewHolder
|
||||
*/
|
||||
void view_holder_free(ViewHolder* view_holder);
|
||||
|
||||
/**
|
||||
* @brief Set view for ViewHolder
|
||||
*
|
||||
* Pass NULL as the view parameter to unset the current view.
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @param view View instance
|
||||
@@ -59,13 +68,25 @@ void view_holder_set_free_callback(
|
||||
void* free_context);
|
||||
|
||||
/**
|
||||
* @brief Free callback context getter. Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
|
||||
* @brief Free callback context getter.
|
||||
*
|
||||
* Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @return void* free callback context
|
||||
*/
|
||||
void* view_holder_get_free_context(ViewHolder* view_holder);
|
||||
|
||||
/**
|
||||
* @brief Set the back key callback.
|
||||
*
|
||||
* The callback function will be called if the user has pressed the Back key
|
||||
* and the current view did not handle this event.
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @param back_callback pointer to the callback function
|
||||
* @param back_context pointer to a user-specific object, can be NULL
|
||||
*/
|
||||
void view_holder_set_back_callback(
|
||||
ViewHolder* view_holder,
|
||||
BackCallback back_callback,
|
||||
@@ -80,26 +101,27 @@ void view_holder_set_back_callback(
|
||||
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
|
||||
|
||||
/**
|
||||
* @brief Enable view processing
|
||||
*
|
||||
* @param view_holder
|
||||
*/
|
||||
void view_holder_start(ViewHolder* view_holder);
|
||||
|
||||
/**
|
||||
* @brief Disable view processing
|
||||
*
|
||||
* @param view_holder
|
||||
*/
|
||||
void view_holder_stop(ViewHolder* view_holder);
|
||||
|
||||
/** View Update Handler
|
||||
* @brief View Update Handler
|
||||
*
|
||||
* @param view View Instance
|
||||
* @param context ViewHolder instance
|
||||
* @param view View Instance
|
||||
* @param context ViewHolder instance
|
||||
*/
|
||||
void view_holder_update(View* view, void* context);
|
||||
|
||||
/**
|
||||
* @brief Send ViewPort of this ViewHolder instance to front
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
*/
|
||||
void view_holder_send_to_front(ViewHolder* view_holder);
|
||||
|
||||
/**
|
||||
* @brief Send ViewPort of this ViewHolder instance to back
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
*/
|
||||
void view_holder_send_to_back(ViewHolder* view_holder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -61,7 +61,6 @@ static LoaderApplicationsApp* loader_applications_app_alloc(void) {
|
||||
app->loading = loading_alloc();
|
||||
|
||||
view_holder_attach_to_gui(app->view_holder, app->gui);
|
||||
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
|
||||
|
||||
return app;
|
||||
} //-V773
|
||||
@@ -149,7 +148,7 @@ static int32_t loader_applications_thread(void* p) {
|
||||
LoaderApplicationsApp* app = loader_applications_app_alloc();
|
||||
|
||||
// start loading animation
|
||||
view_holder_start(app->view_holder);
|
||||
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
|
||||
|
||||
while(loader_applications_select_app(app)) {
|
||||
if(!furi_string_end_with(app->file_path, ".js")) {
|
||||
@@ -161,7 +160,7 @@ static int32_t loader_applications_thread(void* p) {
|
||||
}
|
||||
|
||||
// stop loading animation
|
||||
view_holder_stop(app->view_holder);
|
||||
view_holder_set_view(app->view_holder, NULL);
|
||||
|
||||
loader_applications_app_free(app);
|
||||
|
||||
|
||||
@@ -160,8 +160,6 @@ static LoaderMenuApp* loader_menu_app_alloc(LoaderMenu* loader_menu) {
|
||||
view_set_context(settings_view, app->settings_menu);
|
||||
view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
|
||||
view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view);
|
||||
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary);
|
||||
|
||||
return app;
|
||||
|
||||
@@ -17,13 +17,15 @@ void power_cli_off(Cli* cli, FuriString* args) {
|
||||
void power_cli_reboot(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
power_reboot(PowerBootModeNormal);
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
}
|
||||
|
||||
void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
power_reboot(PowerBootModeDfu);
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeDfu);
|
||||
}
|
||||
|
||||
void power_cli_5v(Cli* cli, FuriString* args) {
|
||||
|
||||
@@ -4,10 +4,18 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define POWER_OFF_TIMEOUT 90
|
||||
#define TAG "Power"
|
||||
#include <update_util/update_operation.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
#define TAG "Power"
|
||||
|
||||
#define POWER_OFF_TIMEOUT_S (90U)
|
||||
#define POWER_POLL_PERIOD_MS (1000UL)
|
||||
|
||||
#define POWER_VBUS_LOW_THRESHOLD (4.0f)
|
||||
#define POWER_HEALTH_LOW_THRESHOLD (70U)
|
||||
|
||||
static void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
Power* power = context;
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
|
||||
@@ -219,6 +227,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
}
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
}
|
||||
|
||||
} else {
|
||||
canvas_draw_box(canvas, 8, 3, 8, 2);
|
||||
}
|
||||
@@ -228,99 +237,61 @@ 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(void) {
|
||||
Power* power = malloc(sizeof(Power));
|
||||
static bool power_update_info(Power* power) {
|
||||
const PowerInfo info = {
|
||||
.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(),
|
||||
.charge = furi_hal_power_get_pct(),
|
||||
.health = furi_hal_power_get_bat_health_pct(),
|
||||
.capacity_remaining = furi_hal_power_get_battery_remaining_capacity(),
|
||||
.capacity_full = furi_hal_power_get_battery_full_capacity(),
|
||||
.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger),
|
||||
.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge),
|
||||
.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit(),
|
||||
.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger),
|
||||
.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge),
|
||||
.voltage_vbus = furi_hal_power_get_usb_voltage(),
|
||||
.temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger),
|
||||
.temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge),
|
||||
};
|
||||
|
||||
// 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;
|
||||
const bool need_refresh = (power->info.charge != info.charge) ||
|
||||
(power->info.is_charging != info.is_charging);
|
||||
power->info = info;
|
||||
return need_refresh;
|
||||
}
|
||||
|
||||
static void power_check_charging_state(Power* power) {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
if(furi_hal_power_is_charging()) {
|
||||
if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) {
|
||||
if(power->state != PowerStateCharged) {
|
||||
notification_internal_message(power->notification, &sequence_charged);
|
||||
notification_internal_message(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;
|
||||
|
||||
} else if(power->state != PowerStateCharging) {
|
||||
notification_internal_message(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(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.is_charging = furi_hal_power_is_charging();
|
||||
info.gauge_is_ok = furi_hal_power_gauge_is_ok();
|
||||
info.is_shutdown_requested = furi_hal_power_is_shutdown_requested();
|
||||
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_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit();
|
||||
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;
|
||||
need_refresh |= power->info.is_charging != info.is_charging;
|
||||
power->info = info;
|
||||
furi_mutex_release(power->api_mtx);
|
||||
|
||||
return need_refresh;
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
static void power_check_low_battery(Power* power) {
|
||||
@@ -329,40 +300,41 @@ static void power_check_low_battery(Power* power) {
|
||||
}
|
||||
|
||||
// Check battery charge and vbus voltage
|
||||
if((power->info.is_shutdown_requested) && (power->info.voltage_vbus < 4.0f) &&
|
||||
power->show_low_bat_level_message) {
|
||||
if((power->info.is_shutdown_requested) &&
|
||||
(power->info.voltage_vbus < POWER_VBUS_LOW_THRESHOLD) && power->show_battery_low_warning) {
|
||||
if(!power->battery_low) {
|
||||
view_dispatcher_send_to_front(power->view_dispatcher);
|
||||
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewOff);
|
||||
view_holder_send_to_front(power->view_holder);
|
||||
view_holder_set_view(power->view_holder, power_off_get_view(power->view_power_off));
|
||||
}
|
||||
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;
|
||||
// view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
||||
view_holder_set_view(power->view_holder, NULL);
|
||||
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
|
||||
}
|
||||
power->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);
|
||||
PowerOffResponse response = power_off_get_response(power->view_power_off);
|
||||
if(response == PowerOffResponseDefault) {
|
||||
if(power->power_off_timeout) {
|
||||
power_off_set_time_left(power->power_off, power->power_off_timeout--);
|
||||
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
|
||||
} else {
|
||||
power_off(power);
|
||||
}
|
||||
} else if(response == PowerOffResponseOk) {
|
||||
power_off(power);
|
||||
} else if(response == PowerOffResponseHide) {
|
||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
||||
view_holder_set_view(power->view_holder, NULL);
|
||||
if(power->power_off_timeout) {
|
||||
power_off_set_time_left(power->power_off, power->power_off_timeout--);
|
||||
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
|
||||
} else {
|
||||
power_off(power);
|
||||
}
|
||||
} else if(response == PowerOffResponseCancel) {
|
||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
||||
view_holder_set_view(power->view_holder, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,6 +356,121 @@ void power_trigger_ui_update(Power* power) {
|
||||
view_port_update(power->battery_view_port);
|
||||
}
|
||||
|
||||
static void power_handle_shutdown(Power* power) {
|
||||
furi_hal_power_off();
|
||||
// Notify user if USB is plugged
|
||||
view_holder_send_to_front(power->view_holder);
|
||||
view_holder_set_view(
|
||||
power->view_holder, power_unplug_usb_get_view(power->view_power_unplug_usb));
|
||||
furi_delay_ms(100);
|
||||
furi_halt("Disconnect USB for safe shutdown");
|
||||
}
|
||||
|
||||
static void power_handle_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);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
furi_hal_power_reset();
|
||||
}
|
||||
|
||||
static bool power_message_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
Power* power = context;
|
||||
|
||||
furi_assert(object == power->message_queue);
|
||||
|
||||
PowerMessage msg;
|
||||
furi_check(furi_message_queue_get(power->message_queue, &msg, 0) == FuriStatusOk);
|
||||
|
||||
switch(msg.type) {
|
||||
case PowerMessageTypeShutdown:
|
||||
power_handle_shutdown(power);
|
||||
break;
|
||||
case PowerMessageTypeReboot:
|
||||
power_handle_reboot(msg.boot_mode);
|
||||
break;
|
||||
case PowerMessageTypeGetInfo:
|
||||
*msg.power_info = power->info;
|
||||
break;
|
||||
case PowerMessageTypeIsBatteryHealthy:
|
||||
*msg.bool_param = power->info.health > POWER_HEALTH_LOW_THRESHOLD;
|
||||
break;
|
||||
case PowerMessageTypeShowBatteryLowWarning:
|
||||
power->show_battery_low_warning = *msg.bool_param;
|
||||
break;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
if(msg.lock) {
|
||||
api_lock_unlock(msg.lock);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void power_tick_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Power* power = context;
|
||||
|
||||
// Update data from gauge and charger
|
||||
const 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();
|
||||
}
|
||||
}
|
||||
|
||||
static Power* power_alloc(void) {
|
||||
Power* power = malloc(sizeof(Power));
|
||||
// Pubsub
|
||||
power->event_pubsub = furi_pubsub_alloc();
|
||||
// State initialization
|
||||
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
|
||||
power->show_battery_low_warning = true;
|
||||
// Gui
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
power->view_holder = view_holder_alloc();
|
||||
power->view_power_off = power_off_alloc();
|
||||
power->view_power_unplug_usb = power_unplug_usb_alloc();
|
||||
|
||||
view_holder_attach_to_gui(power->view_holder, gui);
|
||||
// Battery view port
|
||||
power->battery_view_port = power_battery_view_port_alloc(power);
|
||||
gui_add_view_port(gui, power->battery_view_port, GuiLayerStatusBarRight);
|
||||
// Event loop
|
||||
power->event_loop = furi_event_loop_alloc();
|
||||
power->message_queue = furi_message_queue_alloc(4, sizeof(PowerMessage));
|
||||
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
power->event_loop,
|
||||
power->message_queue,
|
||||
FuriEventLoopEventIn,
|
||||
power_message_callback,
|
||||
power);
|
||||
furi_event_loop_tick_set(power->event_loop, 1000, power_tick_callback, power);
|
||||
|
||||
return power;
|
||||
}
|
||||
|
||||
int32_t power_srv(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
@@ -396,40 +483,9 @@ int32_t power_srv(void* p) {
|
||||
|
||||
Power* power = power_alloc();
|
||||
power_update_info(power);
|
||||
|
||||
furi_record_create(RECORD_POWER, power);
|
||||
|
||||
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
|
||||
desktop_settings_load(settings);
|
||||
power->displayBatteryPercentage = settings->displayBatteryPercentage;
|
||||
free(settings);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
furi_crash("That was unexpected");
|
||||
furi_event_loop_run(power->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <core/pubsub.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <core/pubsub.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -65,7 +66,7 @@ void power_off(Power* power);
|
||||
*
|
||||
* @param mode PowerBootMode
|
||||
*/
|
||||
void power_reboot(PowerBootMode mode);
|
||||
void power_reboot(Power* power, PowerBootMode mode);
|
||||
|
||||
/** Get power info
|
||||
*
|
||||
|
||||
@@ -1,41 +1,39 @@
|
||||
#include "power_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <update_util/update_operation.h>
|
||||
|
||||
void power_off(Power* power) {
|
||||
furi_check(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");
|
||||
PowerMessage msg = {
|
||||
.type = PowerMessageTypeShutdown,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
void power_reboot(Power* power, PowerBootMode mode) {
|
||||
PowerMessage msg = {
|
||||
.type = PowerMessageTypeReboot,
|
||||
.boot_mode = mode,
|
||||
};
|
||||
|
||||
furi_hal_power_reset();
|
||||
furi_check(
|
||||
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void power_get_info(Power* power, PowerInfo* info) {
|
||||
furi_check(power);
|
||||
furi_check(info);
|
||||
|
||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
||||
memcpy(info, &power->info, sizeof(power->info));
|
||||
furi_mutex_release(power->api_mtx);
|
||||
PowerMessage msg = {
|
||||
.type = PowerMessageTypeGetInfo,
|
||||
.power_info = info,
|
||||
.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);
|
||||
}
|
||||
|
||||
FuriPubSub* power_get_pubsub(Power* power) {
|
||||
@@ -45,16 +43,30 @@ FuriPubSub* power_get_pubsub(Power* power) {
|
||||
|
||||
bool power_is_battery_healthy(Power* power) {
|
||||
furi_check(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;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
PowerMessage msg = {
|
||||
.type = PowerMessageTypeIsBatteryHealthy,
|
||||
.lock = api_lock_alloc_locked(),
|
||||
.bool_param = &ret,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||
api_lock_wait_unlock_and_free(msg.lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void power_enable_low_battery_level_notification(Power* power, bool enable) {
|
||||
furi_check(power);
|
||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
||||
power->show_low_bat_level_message = enable;
|
||||
furi_mutex_release(power->api_mtx);
|
||||
|
||||
PowerMessage msg = {
|
||||
.type = PowerMessageTypeShowBatteryLowWarning,
|
||||
.bool_param = &enable,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
@@ -2,19 +2,15 @@
|
||||
|
||||
#include "power.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_holder.h>
|
||||
|
||||
#include <toolbox/api_lock.h>
|
||||
#include <assets_icons.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,
|
||||
@@ -22,29 +18,45 @@ typedef enum {
|
||||
} PowerState;
|
||||
|
||||
struct Power {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
PowerOff* power_off;
|
||||
PowerUnplugUsb* power_unplug_usb;
|
||||
ViewHolder* view_holder;
|
||||
FuriPubSub* event_pubsub;
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* message_queue;
|
||||
|
||||
ViewPort* battery_view_port;
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
FuriPubSub* event_pubsub;
|
||||
PowerEvent event;
|
||||
PowerOff* view_power_off;
|
||||
PowerUnplugUsb* view_power_unplug_usb;
|
||||
|
||||
PowerEvent event;
|
||||
PowerState state;
|
||||
PowerInfo info;
|
||||
|
||||
bool battery_low;
|
||||
bool show_low_bat_level_message;
|
||||
bool show_battery_low_warning;
|
||||
uint8_t displayBatteryPercentage;
|
||||
uint8_t battery_level;
|
||||
uint8_t power_off_timeout;
|
||||
|
||||
FuriMutex* api_mtx;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
PowerViewOff,
|
||||
PowerViewUnplugUsb,
|
||||
} PowerView;
|
||||
|
||||
typedef enum {
|
||||
PowerMessageTypeShutdown,
|
||||
PowerMessageTypeReboot,
|
||||
PowerMessageTypeGetInfo,
|
||||
PowerMessageTypeIsBatteryHealthy,
|
||||
PowerMessageTypeShowBatteryLowWarning,
|
||||
} PowerMessageType;
|
||||
|
||||
typedef struct {
|
||||
PowerMessageType type;
|
||||
union {
|
||||
PowerBootMode boot_mode;
|
||||
PowerInfo* power_info;
|
||||
bool* bool_param;
|
||||
};
|
||||
FuriApiLock lock;
|
||||
} PowerMessage;
|
||||
|
||||
@@ -54,18 +54,21 @@ static void rpc_system_system_reboot_process(const PB_Main* request, void* conte
|
||||
RpcSession* session = (RpcSession*)context;
|
||||
furi_assert(session);
|
||||
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
const int mode = request->content.system_reboot_request.mode;
|
||||
|
||||
if(mode == PB_System_RebootRequest_RebootMode_OS) {
|
||||
power_reboot(PowerBootModeNormal);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
} else if(mode == PB_System_RebootRequest_RebootMode_DFU) {
|
||||
power_reboot(PowerBootModeDfu);
|
||||
power_reboot(power, PowerBootModeDfu);
|
||||
} else if(mode == PB_System_RebootRequest_RebootMode_UPDATE) {
|
||||
power_reboot(PowerBootModeUpdateStart);
|
||||
power_reboot(power, PowerBootModeUpdateStart);
|
||||
} else {
|
||||
rpc_send_and_release_empty(
|
||||
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_POWER);
|
||||
}
|
||||
|
||||
static void rpc_system_system_device_info_callback(
|
||||
@@ -181,9 +184,9 @@ static void rpc_system_system_factory_reset_process(const PB_Main* request, void
|
||||
|
||||
furi_hal_rtc_reset_registers();
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||
power_reboot(PowerBootModeNormal);
|
||||
|
||||
(void)session;
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -675,9 +675,12 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context)
|
||||
char c = cli_getc(cli);
|
||||
if(c == 'y' || c == 'Y') {
|
||||
printf("Data will be wiped after reboot.\r\n");
|
||||
|
||||
furi_hal_rtc_reset_registers();
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||
power_reboot(PowerBootModeNormal);
|
||||
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
} else {
|
||||
printf("Safe choice.\r\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user