Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FL-3783] Asynchronous Infrared remote manipulation #3503

Merged
merged 13 commits into from Mar 11, 2024
36 changes: 20 additions & 16 deletions applications/main/infrared/infrared_app.c
Expand Up @@ -6,7 +6,8 @@

#define TAG "InfraredApp"

#define INFRARED_TX_MIN_INTERVAL_MS 50U
#define INFRARED_TX_MIN_INTERVAL_MS (50U)
#define INFRARED_TASK_STACK_SIZE (2048UL)

static const NotificationSequence*
infrared_notification_sequences[InfraredNotificationMessageCount] = {
Expand Down Expand Up @@ -128,6 +129,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path)
static InfraredApp* infrared_alloc() {
InfraredApp* infrared = malloc(sizeof(InfraredApp));

infrared->task_thread =
furi_thread_alloc_ex("InfraredTask", INFRARED_TASK_STACK_SIZE, NULL, infrared);
infrared->file_path = furi_string_alloc();
infrared->button_name = furi_string_alloc();

Expand Down Expand Up @@ -203,6 +206,10 @@ static InfraredApp* infrared_alloc() {

static void infrared_free(InfraredApp* infrared) {
furi_assert(infrared);

furi_thread_join(infrared->task_thread);
furi_thread_free(infrared->task_thread);

ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state;

Expand Down Expand Up @@ -377,6 +384,18 @@ void infrared_tx_stop(InfraredApp* infrared) {
infrared->app_state.last_transmit_time = furi_get_tick();
}

void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) {
view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading));
furi_thread_set_callback(infrared->task_thread, callback);
furi_thread_start(infrared->task_thread);
}

bool infrared_blocking_task_finalize(InfraredApp* infrared) {
furi_thread_join(infrared->task_thread);
view_stack_remove_view(infrared->view_stack, loading_get_view(infrared->loading));
return furi_thread_get_return_code(infrared->task_thread);
}

void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
Expand All @@ -397,21 +416,6 @@ void infrared_play_notification_message(
notification_message(infrared->notifications, infrared_notification_sequences[message]);
}

void infrared_show_loading_popup(const InfraredApp* infrared, bool show) {
ViewStack* view_stack = infrared->view_stack;
Loading* loading = infrared->loading;

if(show) {
// Raise timer priority so that animations can play
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
view_stack_add_view(view_stack, loading_get_view(loading));
} else {
view_stack_remove_view(view_stack, loading_get_view(loading));
// Restore default timer priority
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
}
}

void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
Expand Down
39 changes: 25 additions & 14 deletions applications/main/infrared/infrared_app_i.h
Expand Up @@ -19,12 +19,13 @@
#include <gui/modules/button_menu.h>
#include <gui/modules/button_panel.h>

#include <rpc/rpc_app.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>

#include <notification/notification_messages.h>

#include <infrared_worker.h>
#include <infrared/worker/infrared_worker.h>

#include "infrared_app.h"
#include "infrared_remote.h"
Expand All @@ -36,8 +37,6 @@
#include "views/infrared_debug_view.h"
#include "views/infrared_move_view.h"

#include "rpc/rpc_app.h"

#define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128
Expand Down Expand Up @@ -120,6 +119,7 @@ struct InfraredApp {
Loading* loading; /**< Standard view for informing about long operations. */
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */

FuriThread* task_thread; /**< Pointer to a FuriThread instance for concurrent tasks. */
FuriString* file_path; /**< Full path to the currently loaded file. */
FuriString* button_name; /**< Name of the button requested in RPC mode. */
/** Arbitrary text storage for various inputs. */
Expand Down Expand Up @@ -210,6 +210,28 @@ void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index);
*/
void infrared_tx_stop(InfraredApp* infrared);

/**
* @brief Start a blocking task in a separate thread.
*
* If a ViewStack is currently on screen, a busy "Hourglass" animation
* will be shown and no input will be accepted until completion.
*
* @param[in,out] infrared pointer to the application instance.
* @param[in] callback pointer to the function to be run in the thread.
*/
void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback);

/**
* @brief Wait for a blocking task to finish and receive the result.
*
* Upon the completion of a blocking task, the busy animation will be hidden
* and input will be accepted again.
*
* @param[in,out] infrared pointer to the application instance.
* @return true if the blocking task finished successfully, false otherwise.
*/
bool infrared_blocking_task_finalize(InfraredApp* infrared);

/**
* @brief Set the internal text store with formatted text.
*
Expand Down Expand Up @@ -239,17 +261,6 @@ void infrared_play_notification_message(
const InfraredApp* infrared,
InfraredNotificationMessage message);

/**
* @brief Show a loading pop-up screen.
*
* In order for this to work, a Stack view must be currently active and
* the main view must be added to it.
*
* @param[in] infrared pointer to the application instance.
* @param[in] show whether to show or hide the pop-up.
*/
void infrared_show_loading_popup(const InfraredApp* infrared, bool show);

/**
* @brief Show a formatted error messsage.
*
Expand Down
1 change: 1 addition & 0 deletions applications/main/infrared/infrared_custom_event.h
Expand Up @@ -14,6 +14,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypePopupClosed,
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypeTaskFinished,

InfraredCustomEventTypeRpcLoadFile,
InfraredCustomEventTypeRpcExit,
Expand Down
Expand Up @@ -32,9 +32,24 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
}

static int32_t infrared_scene_universal_common_task_callback(void* context) {
InfraredApp* infrared = context;
const bool success = infrared_brute_force_calculate_messages(infrared->brute_force);
view_dispatcher_send_custom_event(
infrared->view_dispatcher,
infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0));

return success;
}

void infrared_scene_universal_common_on_enter(void* context) {
InfraredApp* infrared = context;
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);

// Load universal remote data in background
infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback);
}

bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
Expand All @@ -58,26 +73,34 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
consumed = true;
}
consumed = true;
}
} else {
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) ==
InfraredCustomEventTypeButtonSelected) {
uint16_t event_type;
int16_t event_value;
infrared_custom_event_unpack(event.event, &event_type, &event_value);

if(event_type == InfraredCustomEventTypeButtonSelected) {
uint32_t record_count;
if(infrared_brute_force_start(
brute_force, infrared_custom_event_get_value(event.event), &record_count)) {
if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
dolphin_deed(DolphinDeedIrSend);
infrared_scene_universal_common_show_popup(infrared, record_count);
} else {
scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases);
}
consumed = true;
} else if(event_type == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);

if(!task_success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
}
consumed = true;
}
}

Expand Down
60 changes: 36 additions & 24 deletions applications/main/infrared/scenes/infrared_scene_edit_delete.c
Expand Up @@ -6,12 +6,33 @@ static void
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}

static int32_t infrared_scene_edit_delete_task_callback(void* context) {
InfraredApp* infrared = context;
InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;

bool success;
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index);
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_remote_remove(infrared->remote);
} else {
furi_crash();
}

view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);

return success;
}

void infrared_scene_edit_delete_on_enter(void* context) {
InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredRemote* remote = infrared->remote;

const InfraredEditTarget edit_target = infrared->app_state.edit_target;

if(edit_target == InfraredEditTargetButton) {
dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop);

Expand Down Expand Up @@ -84,39 +105,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.event == DialogExResultRight) {
bool success = false;
InfraredRemote* remote = infrared->remote;
// Delete a button or a remote in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_edit_delete_task_callback);

} else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);

InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;

if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_delete_signal(remote, app_state->current_button_index);
infrared_show_loading_popup(infrared, false);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_remote_remove(remote);
app_state->current_button_index = InfraredButtonIndexNone;
} else {
furi_crash();
}

if(success) {
if(task_success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
} else {
infrared_show_error_message(
infrared,
"Failed to\ndelete %s",
edit_target == InfraredEditTargetButton ? "button" : "file");
const char* edit_target_text =
app_state->edit_target == InfraredEditTargetButton ? "button" : "file";
infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text);

const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes));
}
consumed = true;

app_state->current_button_index = InfraredButtonIndexNone;
}
consumed = true;
}

return consumed;
Expand Down
38 changes: 23 additions & 15 deletions applications/main/infrared/scenes/infrared_scene_edit_move.c
@@ -1,5 +1,17 @@
#include "../infrared_app_i.h"

static int32_t infrared_scene_edit_move_task_callback(void* context) {
InfraredApp* infrared = context;
const bool success = infrared_remote_move_signal(
infrared->remote,
infrared->app_state.prev_button_index,
infrared->app_state.current_button_index);
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);

return success;
}

static void infrared_scene_edit_move_button_callback(
uint32_t index_old,
uint32_t index_new,
Expand Down Expand Up @@ -38,25 +50,21 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {

if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeButtonSelected) {
infrared_show_loading_popup(infrared, true);
const bool button_moved = infrared_remote_move_signal(
infrared->remote,
infrared->app_state.prev_button_index,
infrared->app_state.current_button_index);
infrared_show_loading_popup(infrared, false);

if(!button_moved) {
infrared_show_error_message(
infrared,
"Failed to move\n\"%s\"",
infrared_remote_get_signal_name(
infrared->remote, infrared->app_state.current_button_index));
// Move the button in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback);

} else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);

if(!task_success) {
const char* signal_name = infrared_remote_get_signal_name(
infrared->remote, infrared->app_state.current_button_index);
infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name);
scene_manager_search_and_switch_to_previous_scene(
infrared->scene_manager, InfraredSceneRemoteList);
}

consumed = true;
}
consumed = true;
}

return consumed;
Expand Down