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

Infrared: Add option to "Load from Library File" for Universal Remotes #255

Merged
merged 25 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 51 additions & 13 deletions applications/main/infrared/infrared_brute_force.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,32 +60,34 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
FuriString* signal_name = furi_string_alloc();
InfraredSignal* signal = infrared_signal_alloc();

do {
if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) {
error = InfraredErrorCodeFileOperationFailed;
break;
}

bool signals_valid = false;
if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) {
Willy-JL marked this conversation as resolved.
Show resolved Hide resolved
error = InfraredErrorCodeFileOperationFailed;
} else {
uint32_t total_signals = 0;
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
error = infrared_signal_read_body(signal, ff);
signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
if(!signals_valid) break;
if(INFRARED_ERROR_PRESENT(error) || !infrared_signal_is_valid(signal)) {
break;
}

InfraredBruteForceRecord* record =
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
if(record) { //-V547
++(record->count);

} else {
infrared_brute_force_add_record(
brute_force, total_signals, furi_string_get_cstr(signal_name));
}
total_signals++;
}
if(!signals_valid) break;
} while(false);
}

infrared_signal_free(signal);
furi_string_free(signal_name);

flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);

return error;
}

Expand All @@ -94,10 +96,12 @@ bool infrared_brute_force_start(
uint32_t index,
uint32_t* record_count) {
furi_assert(!brute_force->is_started);

bool success = false;
*record_count = 0;

InfraredBruteForceRecordDict_it_t it;

for(InfraredBruteForceRecordDict_it(it, brute_force->records);
!InfraredBruteForceRecordDict_end_p(it);
InfraredBruteForceRecordDict_next(it)) {
Expand All @@ -114,12 +118,17 @@ bool infrared_brute_force_start(
if(*record_count) {
Storage* storage = furi_record_open(RECORD_STORAGE);
brute_force->ff = flipper_format_buffered_file_alloc(storage);

brute_force->current_signal = infrared_signal_alloc();

brute_force->is_started = true;

success =
flipper_format_buffered_file_open_existing(brute_force->ff, brute_force->db_filename);

if(!success) infrared_brute_force_stop(brute_force);
}

return success;
}

Expand Down Expand Up @@ -151,7 +160,6 @@ bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
}
return success;
}

void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
Expand All @@ -160,10 +168,40 @@ void infrared_brute_force_add_record(
FuriString* key;
key = furi_string_alloc_set(name);
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);

furi_string_free(key);
}

void infrared_brute_force_reset(InfraredBruteForce* brute_force) {
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_reset(brute_force->records);
}

size_t infrared_brute_force_get_db_size(const InfraredBruteForce* brute_force) {
Willy-JL marked this conversation as resolved.
Show resolved Hide resolved
size_t size = InfraredBruteForceRecordDict_size(brute_force->records);
return size;
}

const char*
infrared_brute_force_get_button_name(const InfraredBruteForce* brute_force, size_t index) {
if(index >= infrared_brute_force_get_db_size(brute_force)) {
return NULL;
}

InfraredBruteForceRecordDict_it_t it;
size_t current_index = 0;

for(InfraredBruteForceRecordDict_it(it, brute_force->records);
!InfraredBruteForceRecordDict_end_p(it);
InfraredBruteForceRecordDict_next(it)) {
if(current_index == index) {
const char* button_name =
furi_string_get_cstr(InfraredBruteForceRecordDict_cref(it)->key);

return button_name;
}
current_index++;
}

return NULL; //just as fallback
}
21 changes: 21 additions & 0 deletions applications/main/infrared/infrared_brute_force.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "infrared_error_code.h"

/**
Expand Down Expand Up @@ -109,3 +110,23 @@ void infrared_brute_force_add_record(
* @param[in,out] brute_force pointer to the instance to be reset.
*/
void infrared_brute_force_reset(InfraredBruteForce* brute_force);

/**
* @brief Get the total number of unique button names in the database, for example,
* if a button name is "Power" and it appears 3 times in the db, then the
* db_size is 1, instead of 3.
*
* @param[in] brute_force pointer to the InfraredBruteForce instance.
* @return size_t number of unique button names.
*/
size_t infrared_brute_force_get_db_size(const InfraredBruteForce* brute_force);

/**
* @brief Get the button name at the specified index.
*
* @param[in] brute_force pointer to the InfraredBruteForce instance.
* @param[in] index index of the button name to retrieve.
* @return const char* button name, or NULL if index is out of range.
*/
const char*
infrared_brute_force_get_button_name(const InfraredBruteForce* brute_force, size_t index);
2 changes: 2 additions & 0 deletions applications/main/infrared/scenes/infrared_scene_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ ADD_SCENE(infrared, universal_fan, UniversalFan)
ADD_SCENE(infrared, universal_bluray, UniversalBluray)
ADD_SCENE(infrared, universal_monitor, UniversalMonitor)
ADD_SCENE(infrared, universal_digital_sign, UniversalDigitalSign)
ADD_SCENE(infrared, universal_more_devices, UniversalMoreDevices)

ADD_SCENE(infrared, gpio_settings, GpioSettings)
ADD_SCENE(infrared, debug, Debug)
ADD_SCENE(infrared, error_databases, ErrorDatabases)
Expand Down
12 changes: 12 additions & 0 deletions applications/main/infrared/scenes/infrared_scene_universal.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ typedef enum {
SubmenuIndexUniversalBluray,
SubmenuIndexUniversalMonitor,
SubmenuIndexUniversalDigitalSign,
SubmenuIndexUniversalMoreDevices,
} SubmenuIndex;

static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) {
Expand Down Expand Up @@ -84,6 +85,13 @@ void infrared_scene_universal_on_enter(void* context) {
infrared_scene_universal_submenu_callback,
context);

submenu_add_item(
submenu,
"Load Custom DB",
Willy-JL marked this conversation as resolved.
Show resolved Hide resolved
SubmenuIndexUniversalMoreDevices,
infrared_scene_universal_submenu_callback,
context);

submenu_set_selected_item(
submenu, scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneUniversal));

Expand Down Expand Up @@ -123,6 +131,10 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
} else if(event.event == SubmenuIndexUniversalDigitalSign) {
scene_manager_next_scene(scene_manager, InfraredSceneUniversalDigitalSign);
consumed = true;
} else if(event.event == SubmenuIndexUniversalMoreDevices) {
furi_string_set(infrared->file_path, INFRARED_APP_FOLDER);
scene_manager_next_scene(scene_manager, InfraredSceneUniversalMoreDevices);
consumed = true;
}
scene_manager_set_scene_state(scene_manager, InfraredSceneUniversal, event.event);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include "../infrared_app_i.h"
#include <storage/storage.h>
#include <furi.h>
#include <dolphin/dolphin.h>

static void
infrared_scene_universal_more_devices_callback(void* context, int32_t index, InputType type) {
UNUSED(type);
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}

static void infrared_scene_universal_more_devices_progress_back_callback(void* context) {
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}

static void
infrared_scene_universal_more_devices_show_popup(InfraredApp* infrared, uint32_t record_count) {
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
infrared_progress_view_set_progress_total(progress, record_count);
infrared_progress_view_set_back_callback(
progress, infrared_scene_universal_more_devices_progress_back_callback, infrared);
view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
}

static void infrared_scene_universal_more_devices_hide_popup(InfraredApp* infrared) {
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress));
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
}

static int32_t infrared_scene_universal_more_devices_task_callback(void* context) {
InfraredApp* infrared = context;
const InfraredErrorCode error = infrared_brute_force_calculate_messages(infrared->brute_force);
view_dispatcher_send_custom_event(
infrared->view_dispatcher,
infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0));

return error;
}

void infrared_scene_universal_more_devices_on_enter(void* context) {
Willy-JL marked this conversation as resolved.
Show resolved Hide resolved
// in this func, note that it's actually loaded the db twice, here's what i did and why it's harmless:
// 1. load db previously cuz need to use the db to add btns in runtime
// 2. load db again with blocking task
// reason:
// 1. there's a funny policy in infrared_brute_force_calculate_messages func, that it'll check if name in it, if yes, it will increase the num and add, if yes, it will make a new field.
// it's probablt a work around to satisfying other places, but since momentum is not a distro, i really don't want to edit that func to make it's hard to merge upstream changes.
// why it's harmless:
// 1. do it twice can make it cover the first custom item.
// 2. only the count (which controls the for loop to go through the next item by name field) x2, not index, so signal index not impacted.
InfraredApp* infrared = context;
ButtonMenu* button_menu = infrared->button_menu;
InfraredBruteForce* brute_force = infrared->brute_force;

DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px);
browser_options.base_path = INFRARED_APP_FOLDER;

bool file_selected = dialog_file_browser_show(
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options);

if(file_selected) {
infrared_brute_force_set_db_filename(
brute_force, furi_string_get_cstr(infrared->file_path));

// load db previously cuz need to use the db to add btns in runtime
InfraredErrorCode error = infrared_brute_force_calculate_messages(brute_force);

if(INFRARED_ERROR_PRESENT(error)) {
infrared_show_error_message(infrared, "Failed to load database");
scene_manager_previous_scene(infrared->scene_manager);
return;
}

// add btns
for(size_t i = 0; i < infrared_brute_force_get_db_size(brute_force); ++i) {
const char* button_name = infrared_brute_force_get_button_name(brute_force, i);
button_menu_add_item(
button_menu,
button_name,
i,
infrared_scene_universal_more_devices_callback,
ButtonMenuItemTypeCommon,
infrared);
}

///header name handler
const char* file_name = strrchr(furi_string_get_cstr(infrared->file_path), '/');
if(file_name) {
file_name++; // skip dir seperator
} else {
file_name = furi_string_get_cstr(infrared->file_path); // fallback
}
button_menu_set_header(button_menu, file_name);

view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_stack_add_view(infrared->view_stack, button_menu_get_view(infrared->button_menu));

// Load universal remote data in background
infrared_blocking_task_start(
infrared, infrared_scene_universal_more_devices_task_callback);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
} else {
scene_manager_previous_scene(infrared->scene_manager);
Willy-JL marked this conversation as resolved.
Show resolved Hide resolved
}
}

bool infrared_scene_universal_more_devices_on_event(void* context, SceneManagerEvent event) {
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
InfraredBruteForce* brute_force = infrared->brute_force;
bool consumed = false;

if(infrared_brute_force_is_started(brute_force)) {
if(event.type == SceneManagerEventTypeTick) {
bool success = infrared_brute_force_send_next(brute_force);
if(success) {
success = infrared_progress_view_increase_progress(infrared->progress);
}
if(!success) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_more_devices_hide_popup(infrared);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_more_devices_hide_popup(infrared);
}
consumed = true;
}
} else {
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
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, event_value, &record_count)) {
dolphin_deed(DolphinDeedIrSend);
infrared_scene_universal_more_devices_show_popup(infrared, record_count);
} else {
scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases);
}
} else if(event_type == InfraredCustomEventTypeTaskFinished) {
const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared);

if(INFRARED_ERROR_PRESENT(task_error)) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
} else {
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
}
}
consumed = true;
}
}

return consumed;
}

void infrared_scene_universal_more_devices_on_exit(void* context) {
InfraredApp* infrared = context;
ButtonMenu* button_menu = infrared->button_menu;
view_stack_remove_view(infrared->view_stack, button_menu_get_view(button_menu));
button_menu_reset(button_menu);
infrared_brute_force_reset(infrared->brute_force);
}
4 changes: 2 additions & 2 deletions applications/main/infrared/views/infrared_progress_view.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ bool infrared_progress_view_input_callback(InputEvent* event, void* context) {
if(instance->back_callback) {
instance->back_callback(instance->context);
}
return true;
Willy-JL marked this conversation as resolved.
Show resolved Hide resolved
}

return true;
return false;
}

InfraredProgressView* infrared_progress_view_alloc(void) {
Expand Down