mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
Infrared: Add option to "Load from Library File" for Universal Remotes (#255)
* init * comments * remove trash * remove code that mistakenly added from merging conflicts * remove code that mistakenly added from merging conflicts * format * remove header that added during debugging * ecit name * Revert some whitespace changes to avoid future conflicts * get_button_count() * Use same index values * Use common functions where possible * Unroll long if into guard check * Fix furi check failed due to inflated button index * Show "assets" folders * Load DB file only once and show loading animation * Add bool for auto_detect_buttons * Show error when tryingto load remote file as universal library * Remove unnecessary includes * Fix inputs * more_devices -> from_file * Consistency * Remember last selected library file * Update changelog --------- Co-authored-by: Willy-JL <49810075+Willy-JL@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
925481ffd0
commit
2f22fad58b
@@ -24,7 +24,9 @@
|
||||
- Static encrypted backdoor support: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor, allowing key recovery of all non-hardened MIFARE Classic tags on-device
|
||||
- Add SmartRider Parser (#203 by @jaylikesbunda)
|
||||
- Add API to enforce ISO15693 mode (#225 by @aaronjamt)
|
||||
- Infrared: Bluray/DVD Universal Remote (#250 by @jaylikesbunda)
|
||||
- Infrared:
|
||||
- Bluray/DVD Universal Remote (#250 by @jaylikesbunda)
|
||||
- Option to "Load from Library File" for Universal Remotes (#255 by @zxkmm)
|
||||
- Updater: New Yappy themed icon while updating (#253 by @the1anonlypr3 & @Kuronons & @nescap)
|
||||
- BadKB:
|
||||
- OFW: Add linux/gnome badusb demo files (by @thomasnemer)
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
#include "infrared_signal.h"
|
||||
|
||||
#define TAG "InfraredBruteforce"
|
||||
|
||||
#define INFRARED_FILE_HEADER "IR signals file"
|
||||
#define INFRARED_LIBRARY_HEADER "IR library file"
|
||||
#define INFRARED_LIBRARY_VERSION (1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t index;
|
||||
uint32_t count;
|
||||
@@ -50,7 +56,9 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const
|
||||
brute_force->db_filename = db_filename;
|
||||
}
|
||||
|
||||
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
|
||||
InfraredErrorCode infrared_brute_force_calculate_messages(
|
||||
InfraredBruteForce* brute_force,
|
||||
bool auto_detect_buttons) {
|
||||
furi_assert(!brute_force->is_started);
|
||||
furi_assert(brute_force->db_filename);
|
||||
InfraredErrorCode error = InfraredErrorCodeNone;
|
||||
@@ -66,7 +74,33 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t version;
|
||||
// Temporarily use signal_name to get header info
|
||||
if(!flipper_format_read_header(ff, signal_name, &version)) {
|
||||
error = InfraredErrorCodeFileOperationFailed;
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_equal(signal_name, INFRARED_FILE_HEADER)) {
|
||||
FURI_LOG_E(TAG, "Remote file can't be loaded in this context");
|
||||
error = InfraredErrorCodeWrongFileType;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_string_equal(signal_name, INFRARED_LIBRARY_HEADER)) {
|
||||
error = InfraredErrorCodeWrongFileType;
|
||||
FURI_LOG_E(TAG, "Filetype unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
if(version != INFRARED_LIBRARY_VERSION) {
|
||||
error = InfraredErrorCodeWrongFileVersion;
|
||||
FURI_LOG_E(TAG, "Wrong file version");
|
||||
break;
|
||||
}
|
||||
|
||||
bool signals_valid = false;
|
||||
uint32_t auto_detect_button_index = 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);
|
||||
@@ -74,6 +108,11 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
|
||||
|
||||
InfraredBruteForceRecord* record =
|
||||
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
|
||||
if(!record && auto_detect_buttons) {
|
||||
infrared_brute_force_add_record(
|
||||
brute_force, auto_detect_button_index++, furi_string_get_cstr(signal_name));
|
||||
record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
|
||||
}
|
||||
if(record) { //-V547
|
||||
++(record->count);
|
||||
}
|
||||
@@ -167,3 +206,31 @@ 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_button_count(const InfraredBruteForce* brute_force) {
|
||||
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_button_count(brute_force)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
InfraredBruteForceRecordDict_it_t it;
|
||||
for(InfraredBruteForceRecordDict_it(it, brute_force->records);
|
||||
!InfraredBruteForceRecordDict_end_p(it);
|
||||
InfraredBruteForceRecordDict_next(it)) {
|
||||
// Dict elements are unordered, they may be shuffled while adding elements, so the
|
||||
// index used in add_record() may differ when iterating here, so we have to check
|
||||
// the stored index not "position" index
|
||||
const InfraredBruteForceRecordDict_itref_t* pair = InfraredBruteForceRecordDict_cref(it);
|
||||
if(pair->value.index == index) {
|
||||
const char* button_name = furi_string_get_cstr(pair->key);
|
||||
return button_name;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; //just as fallback
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "infrared_error_code.h"
|
||||
|
||||
/**
|
||||
@@ -46,9 +47,12 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const
|
||||
* a infrared_brute_force_set_db_filename() call.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be updated.
|
||||
* @param[in] auto_detect_buttons bool whether to automatically register newly discovered buttons.
|
||||
* @returns InfraredErrorCodeNone on success, otherwise error code.
|
||||
*/
|
||||
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force);
|
||||
InfraredErrorCode infrared_brute_force_calculate_messages(
|
||||
InfraredBruteForce* brute_force,
|
||||
bool auto_detect_buttons);
|
||||
|
||||
/**
|
||||
* @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary.
|
||||
@@ -109,3 +113,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_button_count(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);
|
||||
|
||||
@@ -470,7 +470,7 @@ static void
|
||||
printf("Missing signal name.\r\n");
|
||||
break;
|
||||
}
|
||||
if(infrared_brute_force_calculate_messages(brute_force) != InfraredErrorCodeNone) {
|
||||
if(infrared_brute_force_calculate_messages(brute_force, false) != InfraredErrorCodeNone) {
|
||||
printf("Invalid remote name.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
|
||||
|
||||
static int32_t infrared_scene_universal_common_task_callback(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
const InfraredErrorCode error = infrared_brute_force_calculate_messages(infrared->brute_force);
|
||||
const InfraredErrorCode error =
|
||||
infrared_brute_force_calculate_messages(infrared->brute_force, false);
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher,
|
||||
infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0));
|
||||
|
||||
@@ -24,6 +24,7 @@ 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_from_file, UniversalFromFile)
|
||||
ADD_SCENE(infrared, gpio_settings, GpioSettings)
|
||||
ADD_SCENE(infrared, debug, Debug)
|
||||
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
||||
|
||||
@@ -85,6 +85,10 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
const uint32_t submenu_index = event.event;
|
||||
scene_manager_set_scene_state(scene_manager, InfraredSceneStart, submenu_index);
|
||||
if(submenu_index == SubmenuIndexUniversalRemotes) {
|
||||
// Set file_path only once here so repeated usages of
|
||||
// "Load from Library File" have file browser focused on
|
||||
// last selected file, feels more intuitive
|
||||
furi_string_set(infrared->file_path, INFRARED_APP_FOLDER);
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversal);
|
||||
} else if(
|
||||
submenu_index == SubmenuIndexLearnNewRemote ||
|
||||
|
||||
@@ -10,6 +10,7 @@ typedef enum {
|
||||
SubmenuIndexUniversalBluray,
|
||||
SubmenuIndexUniversalMonitor,
|
||||
SubmenuIndexUniversalDigitalSign,
|
||||
SubmenuIndexUniversalFromFile,
|
||||
} SubmenuIndex;
|
||||
|
||||
static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -84,6 +85,13 @@ void infrared_scene_universal_on_enter(void* context) {
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Load from Library File",
|
||||
SubmenuIndexUniversalFromFile,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneUniversal));
|
||||
|
||||
@@ -123,6 +131,9 @@ 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 == SubmenuIndexUniversalFromFile) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalFromFile);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(scene_manager, InfraredSceneUniversal, event.event);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "../infrared_app_i.h"
|
||||
|
||||
#include "common/infrared_scene_universal_common.h"
|
||||
|
||||
static void
|
||||
infrared_scene_universal_from_file_item_callback(void* context, int32_t index, InputType type) {
|
||||
if(type == InputTypeRelease) {
|
||||
InfraredApp* infrared = context;
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t infrared_scene_universal_from_file_task_callback(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
ButtonMenu* button_menu = infrared->button_menu;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
const InfraredErrorCode error =
|
||||
infrared_brute_force_calculate_messages(infrared->brute_force, true);
|
||||
|
||||
if(!INFRARED_ERROR_PRESENT(error)) {
|
||||
// add btns
|
||||
for(size_t i = 0; i < infrared_brute_force_get_button_count(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_from_file_item_callback,
|
||||
ButtonMenuItemTypeCommon,
|
||||
infrared);
|
||||
}
|
||||
}
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher,
|
||||
infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0));
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void infrared_scene_universal_from_file_on_enter(void* context) {
|
||||
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;
|
||||
browser_options.skip_assets = false;
|
||||
if(!dialog_file_browser_show(
|
||||
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) {
|
||||
scene_manager_previous_scene(infrared->scene_manager);
|
||||
return;
|
||||
}
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(infrared->file_path));
|
||||
|
||||
// File name in header
|
||||
// Using c-string functions on FuriString is a bad idea but file_path is not modified
|
||||
// for the lifetime of this scene so it should be fine
|
||||
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);
|
||||
|
||||
// Can't use infrared_scene_universal_common_on_enter() since we use ButtonMenu not ButtonPanel
|
||||
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_from_file_task_callback);
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_from_file_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
|
||||
// Only override InfraredCustomEventTypeTaskFinished on error condition
|
||||
if(!infrared_brute_force_is_started(brute_force) &&
|
||||
event.type == SceneManagerEventTypeCustom) {
|
||||
uint16_t event_type;
|
||||
int16_t event_value;
|
||||
infrared_custom_event_unpack(event.event, &event_type, &event_value);
|
||||
if(event_type == InfraredCustomEventTypeTaskFinished) {
|
||||
const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared);
|
||||
|
||||
if(INFRARED_ERROR_PRESENT(task_error)) {
|
||||
bool wrong_file_type =
|
||||
INFRARED_ERROR_CHECK(task_error, InfraredErrorCodeWrongFileType);
|
||||
const char* format = wrong_file_type ?
|
||||
"Remote file\n\"%s\" can't be openned as a library" :
|
||||
"Failed to load\n\"%s\"";
|
||||
|
||||
infrared_show_error_message(
|
||||
infrared, format, furi_string_get_cstr(infrared->file_path));
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use common function for all other functionality
|
||||
return infrared_scene_universal_common_on_event(context, event);
|
||||
}
|
||||
|
||||
void infrared_scene_universal_from_file_on_exit(void* context) {
|
||||
// Can't use infrared_scene_universal_common_on_exit() since we use ButtonMenu not ButtonPanel
|
||||
InfraredApp* infrared = context;
|
||||
ButtonMenu* button_menu = infrared->button_menu;
|
||||
view_stack_remove_view(infrared->view_stack, button_menu_get_view(button_menu));
|
||||
infrared_brute_force_reset(infrared->brute_force);
|
||||
button_menu_reset(button_menu);
|
||||
}
|
||||
Reference in New Issue
Block a user