Merge branch 'ofw_dev' into dev

This commit is contained in:
MX 2024-03-11 21:10:01 +03:00
commit 9050a242e6
No known key found for this signature in database
GPG Key ID: 7CCC66B7DBDD1C83
15 changed files with 288 additions and 202 deletions

View File

@ -112,7 +112,7 @@ MU_TEST_SUITE(test_datetime_validate_datetime) {
MU_TEST(test_datetime_timestamp_to_datetime_min) { MU_TEST(test_datetime_timestamp_to_datetime_min) {
uint32_t test_value = 0; uint32_t test_value = 0;
DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 4};
DateTime result = {0}; DateTime result = {0};
datetime_timestamp_to_datetime(test_value, &result); datetime_timestamp_to_datetime(test_value, &result);
@ -122,7 +122,7 @@ MU_TEST(test_datetime_timestamp_to_datetime_min) {
MU_TEST(test_datetime_timestamp_to_datetime_max) { MU_TEST(test_datetime_timestamp_to_datetime_max) {
uint32_t test_value = UINT32_MAX; uint32_t test_value = UINT32_MAX;
DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 7};
DateTime result = {0}; DateTime result = {0};
datetime_timestamp_to_datetime(test_value, &result); datetime_timestamp_to_datetime(test_value, &result);
@ -141,10 +141,26 @@ MU_TEST(test_datetime_timestamp_to_datetime_to_timestamp) {
mu_assert_int_eq(test_value, result); mu_assert_int_eq(test_value, result);
} }
MU_TEST(test_datetime_timestamp_to_datetime_weekday) {
uint32_t test_value = 1709748421; // Wed Mar 06 18:07:01 2024 UTC
DateTime datetime = {0};
datetime_timestamp_to_datetime(test_value, &datetime);
mu_assert_int_eq(datetime.hour, 18);
mu_assert_int_eq(datetime.minute, 7);
mu_assert_int_eq(datetime.second, 1);
mu_assert_int_eq(datetime.day, 6);
mu_assert_int_eq(datetime.month, 3);
mu_assert_int_eq(datetime.weekday, 3);
mu_assert_int_eq(datetime.year, 2024);
}
MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) { MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) {
MU_RUN_TEST(test_datetime_timestamp_to_datetime_min); MU_RUN_TEST(test_datetime_timestamp_to_datetime_min);
MU_RUN_TEST(test_datetime_timestamp_to_datetime_max); MU_RUN_TEST(test_datetime_timestamp_to_datetime_max);
MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp); MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp);
MU_RUN_TEST(test_datetime_timestamp_to_datetime_weekday);
} }
MU_TEST(test_datetime_datetime_to_timestamp_min) { MU_TEST(test_datetime_datetime_to_timestamp_min) {

View File

@ -6,7 +6,8 @@
#define TAG "InfraredApp" #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* static const NotificationSequence*
infrared_notification_sequences[InfraredNotificationMessageCount] = { infrared_notification_sequences[InfraredNotificationMessageCount] = {
@ -128,6 +129,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path)
static InfraredApp* infrared_alloc() { static InfraredApp* infrared_alloc() {
InfraredApp* infrared = malloc(sizeof(InfraredApp)); 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->file_path = furi_string_alloc();
infrared->button_name = furi_string_alloc(); infrared->button_name = furi_string_alloc();
@ -209,6 +212,10 @@ static InfraredApp* infrared_alloc() {
static void infrared_free(InfraredApp* infrared) { static void infrared_free(InfraredApp* infrared) {
furi_assert(infrared); furi_assert(infrared);
furi_thread_join(infrared->task_thread);
furi_thread_free(infrared->task_thread);
ViewDispatcher* view_dispatcher = infrared->view_dispatcher; ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state; InfraredAppState* app_state = &infrared->app_state;
@ -386,6 +393,18 @@ void infrared_tx_stop(InfraredApp* infrared) {
infrared->app_state.last_transmit_time = furi_get_tick(); 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, ...) { void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@ -406,21 +425,6 @@ void infrared_play_notification_message(
notification_message(infrared->notifications, infrared_notification_sequences[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, ...) { void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);

View File

@ -20,12 +20,13 @@
#include <gui/modules/button_panel.h> #include <gui/modules/button_panel.h>
#include <gui/modules/variable_item_list.h> #include <gui/modules/variable_item_list.h>
#include <rpc/rpc_app.h>
#include <storage/storage.h> #include <storage/storage.h>
#include <dialogs/dialogs.h> #include <dialogs/dialogs.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <infrared_worker.h> #include <infrared/worker/infrared_worker.h>
#include "infrared_app.h" #include "infrared_app.h"
#include "infrared_remote.h" #include "infrared_remote.h"
@ -37,8 +38,6 @@
#include "views/infrared_debug_view.h" #include "views/infrared_debug_view.h"
#include "views/infrared_move_view.h" #include "views/infrared_move_view.h"
#include "rpc/rpc_app.h"
#define INFRARED_FILE_NAME_SIZE 100 #define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2 #define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128 #define INFRARED_TEXT_STORE_SIZE 128
@ -122,6 +121,7 @@ struct InfraredApp {
Loading* loading; /**< Standard view for informing about long operations. */ Loading* loading; /**< Standard view for informing about long operations. */
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ 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* file_path; /**< Full path to the currently loaded file. */
FuriString* button_name; /**< Name of the button requested in RPC mode. */ FuriString* button_name; /**< Name of the button requested in RPC mode. */
/** Arbitrary text storage for various inputs. */ /** Arbitrary text storage for various inputs. */
@ -213,6 +213,28 @@ void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index);
*/ */
void infrared_tx_stop(InfraredApp* infrared); 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. * @brief Set the internal text store with formatted text.
* *
@ -242,17 +264,6 @@ void infrared_play_notification_message(
const InfraredApp* infrared, const InfraredApp* infrared,
InfraredNotificationMessage message); 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. * @brief Show a formatted error messsage.
* *

View File

@ -14,6 +14,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypePopupClosed, InfraredCustomEventTypePopupClosed,
InfraredCustomEventTypeButtonSelected, InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed, InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypeTaskFinished,
InfraredCustomEventTypeRpcLoadFile, InfraredCustomEventTypeRpcLoadFile,
InfraredCustomEventTypeRpcExit, InfraredCustomEventTypeRpcExit,

View File

@ -32,9 +32,24 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); 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) { void infrared_scene_universal_common_on_enter(void* context) {
InfraredApp* infrared = 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_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) { bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
@ -58,26 +73,34 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force); infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared); infrared_scene_universal_common_hide_popup(infrared);
consumed = true;
} }
consumed = true;
} }
} else { } else {
if(event.type == SceneManagerEventTypeBack) { if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(scene_manager); scene_manager_previous_scene(scene_manager);
consumed = true; consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) { } else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) == uint16_t event_type;
InfraredCustomEventTypeButtonSelected) { int16_t event_value;
infrared_custom_event_unpack(event.event, &event_type, &event_value);
if(event_type == InfraredCustomEventTypeButtonSelected) {
uint32_t record_count; uint32_t record_count;
if(infrared_brute_force_start( if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
brute_force, infrared_custom_event_get_value(event.event), &record_count)) {
dolphin_deed(DolphinDeedIrSend); dolphin_deed(DolphinDeedIrSend);
infrared_scene_universal_common_show_popup(infrared, record_count); infrared_scene_universal_common_show_popup(infrared, record_count);
} else { } else {
scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); 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;
} }
} }

View File

@ -6,12 +6,33 @@ static void
view_dispatcher_send_custom_event(infrared->view_dispatcher, result); 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) { void infrared_scene_edit_delete_on_enter(void* context) {
InfraredApp* infrared = context; InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex; DialogEx* dialog_ex = infrared->dialog_ex;
InfraredRemote* remote = infrared->remote; InfraredRemote* remote = infrared->remote;
const InfraredEditTarget edit_target = infrared->app_state.edit_target; const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) { if(edit_target == InfraredEditTargetButton) {
dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop);
@ -84,39 +105,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) { if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager); scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.event == DialogExResultRight) { } else if(event.event == DialogExResultRight) {
bool success = false; // Delete a button or a remote in a separate thread
InfraredRemote* remote = infrared->remote; 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; InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;
if(edit_target == InfraredEditTargetButton) { if(task_success) {
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) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
} else { } else {
infrared_show_error_message( const char* edit_target_text =
infrared, app_state->edit_target == InfraredEditTargetButton ? "button" : "file";
"Failed to\ndelete %s", infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text);
edit_target == InfraredEditTargetButton ? "button" : "file");
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes)); scene_manager, possible_scenes, COUNT_OF(possible_scenes));
} }
consumed = true;
app_state->current_button_index = InfraredButtonIndexNone;
} }
consumed = true;
} }
return consumed; return consumed;

View File

@ -1,5 +1,17 @@
#include "../infrared_app_i.h" #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( static void infrared_scene_edit_move_button_callback(
uint32_t index_old, uint32_t index_old,
uint32_t index_new, uint32_t index_new,
@ -38,25 +50,21 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeButtonSelected) { if(event.event == InfraredCustomEventTypeButtonSelected) {
infrared_show_loading_popup(infrared, true); // Move the button in a separate thread
const bool button_moved = infrared_remote_move_signal( infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback);
infrared->remote,
infrared->app_state.prev_button_index,
infrared->app_state.current_button_index);
infrared_show_loading_popup(infrared, false);
if(!button_moved) { } else if(event.event == InfraredCustomEventTypeTaskFinished) {
infrared_show_error_message( const bool task_success = infrared_blocking_task_finalize(infrared);
infrared,
"Failed to move\n\"%s\"", if(!task_success) {
infrared_remote_get_signal_name( const char* signal_name = infrared_remote_get_signal_name(
infrared->remote, infrared->app_state.current_button_index)); 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( scene_manager_search_and_switch_to_previous_scene(
infrared->scene_manager, InfraredSceneRemoteList); infrared->scene_manager, InfraredSceneRemoteList);
} }
consumed = true;
} }
consumed = true;
} }
return consumed; return consumed;

View File

@ -3,6 +3,28 @@
#include <string.h> #include <string.h>
#include <toolbox/path.h> #include <toolbox/path.h>
static int32_t infrared_scene_edit_rename_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_rename_signal(
infrared->remote, app_state->current_button_index, infrared->text_store[0]);
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
} else {
furi_crash();
}
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
return success;
}
void infrared_scene_edit_rename_on_enter(void* context) { void infrared_scene_edit_rename_on_enter(void* context) {
InfraredApp* infrared = context; InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote; InfraredRemote* remote = infrared->remote;
@ -61,41 +83,31 @@ void infrared_scene_edit_rename_on_enter(void* context) {
bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) {
InfraredApp* infrared = context; InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
SceneManager* scene_manager = infrared->scene_manager; SceneManager* scene_manager = infrared->scene_manager;
InfraredAppState* app_state = &infrared->app_state;
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTextEditDone) { if(event.event == InfraredCustomEventTypeTextEditDone) {
bool success = false; // Rename a button or a remote in a separate thread
const InfraredEditTarget edit_target = app_state->edit_target; infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback);
if(edit_target == InfraredEditTargetButton) {
const int32_t current_button_index = app_state->current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_rename_signal(
remote, current_button_index, infrared->text_store[0]);
infrared_show_loading_popup(infrared, false);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
} else {
furi_crash();
}
if(success) { } else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
InfraredAppState* app_state = &infrared->app_state;
if(task_success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone);
} else { } else {
infrared_show_error_message( const char* edit_target_text =
infrared, app_state->edit_target == InfraredEditTargetButton ? "button" : "file";
"Failed to\nrename %s", infrared_show_error_message(infrared, "Failed to\nrename %s", edit_target_text);
edit_target == InfraredEditTargetButton ? "button" : "file");
scene_manager_search_and_switch_to_previous_scene( scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemoteList); scene_manager, InfraredSceneRemoteList);
} }
consumed = true;
app_state->current_button_index = InfraredButtonIndexNone;
} }
consumed = true;
} }
return consumed; return consumed;

View File

@ -1,41 +1,59 @@
#include "../infrared_app_i.h" #include "../infrared_app_i.h"
void infrared_scene_remote_list_on_enter(void* context) { static int32_t infrared_scene_remote_list_task_callback(void* context) {
InfraredApp* infrared = context; InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager; const bool success =
ViewDispatcher* view_dispatcher = infrared->view_dispatcher; infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path));
view_dispatcher_send_custom_event(
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); return success;
}
static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) {
DialogsFileBrowserOptions browser_options; DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px);
browser_options.base_path = INFRARED_APP_FOLDER; browser_options.base_path = INFRARED_APP_FOLDER;
while(dialog_file_browser_show( const bool file_selected = dialog_file_browser_show(
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) { infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options);
const char* file_path = furi_string_get_cstr(infrared->file_path);
infrared_show_loading_popup(infrared, true); if(file_selected) {
const bool remote_loaded = infrared_remote_load(infrared->remote, file_path); view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
infrared_show_loading_popup(infrared, false); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
if(remote_loaded) { // Load the remote in a separate thread
scene_manager_next_scene(scene_manager, InfraredSceneRemote); infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback);
return;
} else { } else {
infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); scene_manager_previous_scene(infrared->scene_manager);
}
} }
}
scene_manager_previous_scene(scene_manager); void infrared_scene_remote_list_on_enter(void* context) {
InfraredApp* infrared = context;
infrared_scene_remote_list_select_and_load(infrared);
} }
bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) {
UNUSED(context); InfraredApp* infrared = context;
UNUSED(event);
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
if(task_success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
} else {
infrared_show_error_message(
infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path));
infrared_scene_remote_list_select_and_load(infrared);
}
}
consumed = true;
}
return consumed; return consumed;
} }

View File

@ -9,6 +9,15 @@ typedef enum {
InfraredRpcStateSending, InfraredRpcStateSending,
} InfraredRpcState; } InfraredRpcState;
static int32_t infrared_scene_rpc_task_callback(void* context) {
InfraredApp* infrared = context;
const bool success =
infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path));
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
return success;
}
void infrared_scene_rpc_on_enter(void* context) { void infrared_scene_rpc_on_enter(void* context) {
InfraredApp* infrared = context; InfraredApp* infrared = context;
Popup* popup = infrared->popup; Popup* popup = infrared->popup;
@ -21,7 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) {
popup_set_context(popup, context); popup_set_context(popup, context);
popup_set_callback(popup, infrared_popup_closed_callback); popup_set_callback(popup, infrared_popup_closed_callback);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle);
@ -33,76 +43,88 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
consumed = true; InfraredAppState* app_state = &infrared->app_state;
InfraredRpcState state = InfraredRpcState rpc_state =
scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc); scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc);
if(event.event == InfraredCustomEventTypeBackPressed) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypePopupClosed) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypeRpcLoadFile) {
bool result = false;
if(state == InfraredRpcStateIdle) {
result = infrared_remote_load(
infrared->remote, furi_string_get_cstr(infrared->file_path));
if(result) {
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
}
}
const char* remote_name = infrared_remote_get_name(infrared->remote);
infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); if(event.event == InfraredCustomEventTypeRpcLoadFile) {
if(rpc_state == InfraredRpcStateIdle) {
// Load the remote in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_rpc_task_callback);
}
} else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
if(task_success) {
const char* remote_name = infrared_remote_get_name(infrared->remote);
infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name);
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
} else {
infrared_text_store_set(
infrared, 0, "failed to load\n%s", furi_string_get_cstr(infrared->file_path));
}
popup_set_text( popup_set_text(
infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop);
rpc_system_app_confirm(infrared->rpc_ctx, result); rpc_system_app_confirm(infrared->rpc_ctx, task_success);
} else if( } else if(
event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressName ||
event.event == InfraredCustomEventTypeRpcButtonPressIndex) { event.event == InfraredCustomEventTypeRpcButtonPressIndex) {
bool result = false; bool result = false;
if(state == InfraredRpcStateLoaded) { if(rpc_state == InfraredRpcStateLoaded) {
if(event.event == InfraredCustomEventTypeRpcButtonPressName) { if(event.event == InfraredCustomEventTypeRpcButtonPressName) {
const char* button_name = furi_string_get_cstr(infrared->button_name); const char* button_name = furi_string_get_cstr(infrared->button_name);
size_t index; size_t index;
const bool index_found = const bool index_found =
infrared_remote_get_signal_index(infrared->remote, button_name, &index); infrared_remote_get_signal_index(infrared->remote, button_name, &index);
infrared->app_state.current_button_index = app_state->current_button_index = index_found ? (signed)index :
index_found ? (signed)index : InfraredButtonIndexNone; InfraredButtonIndexNone;
FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name);
} else { } else {
FURI_LOG_D( FURI_LOG_D(
TAG, TAG, "Sending signal with index \"%ld\"", app_state->current_button_index);
"Sending signal with index \"%ld\"",
infrared->app_state.current_button_index);
} }
if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { if(infrared->app_state.current_button_index != InfraredButtonIndexNone) {
infrared_tx_start_button_index( infrared_tx_start_button_index(infrared, app_state->current_button_index);
infrared, infrared->app_state.current_button_index);
scene_manager_set_scene_state( scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending);
result = true; result = true;
} }
} }
rpc_system_app_confirm(infrared->rpc_ctx, result); rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) {
bool result = false; bool result = false;
if(state == InfraredRpcStateSending) {
if(rpc_state == InfraredRpcStateSending) {
infrared_tx_stop(infrared); infrared_tx_stop(infrared);
result = true; result = true;
scene_manager_set_scene_state( scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
} }
rpc_system_app_confirm(infrared->rpc_ctx, result); rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(event.event == InfraredCustomEventTypeRpcExit) {
scene_manager_stop(infrared->scene_manager); } else if(
view_dispatcher_stop(infrared->view_dispatcher); event.event == InfraredCustomEventTypeRpcExit ||
rpc_system_app_confirm(infrared->rpc_ctx, true); event.event == InfraredCustomEventTypeRpcSessionClose ||
} else if(event.event == InfraredCustomEventTypeRpcSessionClose) { event.event == InfraredCustomEventTypePopupClosed) {
scene_manager_stop(infrared->scene_manager); scene_manager_stop(infrared->scene_manager);
view_dispatcher_stop(infrared->view_dispatcher); view_dispatcher_stop(infrared->view_dispatcher);
if(event.event == InfraredCustomEventTypeRpcExit) {
rpc_system_app_confirm(infrared->rpc_ctx, true);
}
} }
consumed = true;
} }
return consumed; return consumed;
} }
@ -112,5 +134,7 @@ void infrared_scene_rpc_on_exit(void* context) {
InfraredRpcStateSending) { InfraredRpcStateSending) {
infrared_tx_stop(infrared); infrared_tx_stop(infrared);
} }
view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup));
popup_reset(infrared->popup); popup_reset(infrared->popup);
} }

View File

@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h" #include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_ac_on_enter(void* context) { void infrared_scene_universal_ac_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context; InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel; ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force; InfraredBruteForce* brute_force = infrared->brute_force;
@ -122,16 +120,7 @@ void infrared_scene_universal_ac_on_enter(void* context) {
button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote"); button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); infrared_scene_universal_common_on_enter(context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
} }
bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) {

View File

@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h" #include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_audio_on_enter(void* context) { void infrared_scene_universal_audio_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context; InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel; ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force; InfraredBruteForce* brute_force = infrared->brute_force;
@ -119,16 +117,7 @@ void infrared_scene_universal_audio_on_enter(void* context) {
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote"); button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote");
button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); infrared_scene_universal_common_on_enter(context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
} }
bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) {

View File

@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h" #include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_projector_on_enter(void* context) { void infrared_scene_universal_projector_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context; InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel; ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force; InfraredBruteForce* brute_force = infrared->brute_force;
@ -94,16 +92,7 @@ void infrared_scene_universal_projector_on_enter(void* context) {
button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote"); button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote");
button_panel_add_icon(button_panel, 34, 68, &I_vol_ac_text_30x30); button_panel_add_icon(button_panel, 34, 68, &I_vol_ac_text_30x30);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); infrared_scene_universal_common_on_enter(context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
} }
bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) {

View File

@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h" #include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_tv_on_enter(void* context) { void infrared_scene_universal_tv_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context; InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel; ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force; InfraredBruteForce* brute_force = infrared->brute_force;
@ -95,16 +93,7 @@ void infrared_scene_universal_tv_on_enter(void* context) {
button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote"); button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); infrared_scene_universal_common_on_enter(context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
} }
bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) {

View File

@ -71,6 +71,7 @@ void datetime_timestamp_to_datetime(uint32_t timestamp, DateTime* datetime) {
uint32_t seconds_in_day = timestamp % SECONDS_PER_DAY; uint32_t seconds_in_day = timestamp % SECONDS_PER_DAY;
datetime->year = EPOCH_START_YEAR; datetime->year = EPOCH_START_YEAR;
datetime->weekday = ((days + 3) % 7) + 1;
while(days >= datetime_get_days_per_year(datetime->year)) { while(days >= datetime_get_days_per_year(datetime->year)) {
days -= datetime_get_days_per_year(datetime->year); days -= datetime_get_days_per_year(datetime->year);