[FL-2911] IR Universal Audio Remote (#1942)
* Add Audio universal remote * Add signal library for Audio Universal Remote * Update UniversalRemotes.md * Added IR profile for Samsung K450 soundbar (#1892) * Add symbols to API file * Rearrange Audio remote buttons * Add new icons, remove old ones * Remove old signals, add new ones * Add universal audio remote to CLI, refactor code * Improve help text * Correct formatting * Update UniversalRemotes.md * Furi: restore correct api_symbols.csv version Co-authored-by: Alexei Humeniy <yo@ahumeniy.net> Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
@@ -5,25 +5,21 @@
|
||||
#include <furi_hal_infrared.h>
|
||||
#include <flipper_format.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
#include "infrared_brute_force.h"
|
||||
|
||||
#include <m-dict.h>
|
||||
|
||||
#define INFRARED_CLI_BUF_SIZE 10
|
||||
#define INFRARED_ASSETS_FOLDER "infrared/assets"
|
||||
#define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0
|
||||
|
||||
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
|
||||
enum RemoteTypes { TV = 0, AC = 1 };
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type);
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal);
|
||||
|
||||
static const struct {
|
||||
const char* cmd;
|
||||
@@ -87,8 +83,10 @@ static void infrared_cli_print_usage(void) {
|
||||
INFRARED_MIN_FREQUENCY,
|
||||
INFRARED_MAX_FREQUENCY);
|
||||
printf("\tir decode <input_file> [<output_file>]\r\n");
|
||||
printf("\tir universal <tv, ac> <signal name>\r\n");
|
||||
printf("\tir universal list <tv, ac>\r\n");
|
||||
printf("\tir universal <remote_name> <signal_name>\r\n");
|
||||
printf("\tir universal list <remote_name>\r\n");
|
||||
// TODO: Do not hardcode universal remote names
|
||||
printf("\tAvailable universal remotes: tv audio ac\r\n");
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
@@ -356,89 +354,31 @@ static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
enum RemoteTypes Remote;
|
||||
|
||||
FuriString* command;
|
||||
FuriString* remote;
|
||||
FuriString* signal;
|
||||
command = furi_string_alloc();
|
||||
remote = furi_string_alloc();
|
||||
signal = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, command)) {
|
||||
infrared_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(command, "list") == 0) {
|
||||
args_read_string_and_trim(args, remote);
|
||||
if(furi_string_cmp_str(remote, "tv") == 0) {
|
||||
Remote = TV;
|
||||
} else if(furi_string_cmp_str(remote, "ac") == 0) {
|
||||
Remote = AC;
|
||||
} else {
|
||||
printf("Invalid remote type.\r\n");
|
||||
break;
|
||||
}
|
||||
infrared_cli_list_remote_signals(Remote);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(command, "tv") == 0) {
|
||||
Remote = TV;
|
||||
} else if(furi_string_cmp_str(command, "ac") == 0) {
|
||||
Remote = AC;
|
||||
} else {
|
||||
printf("Invalid remote type.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
args_read_string_and_trim(args, signal);
|
||||
if(furi_string_empty(signal)) {
|
||||
printf("Must supply a valid signal for type of remote selected.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
infrared_cli_brute_force_signals(cli, Remote, signal);
|
||||
break;
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(command);
|
||||
furi_string_free(remote);
|
||||
furi_string_free(signal);
|
||||
}
|
||||
|
||||
static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
dict_signals_t signals_dict;
|
||||
FuriString* key;
|
||||
const char* remote_file = NULL;
|
||||
bool success = false;
|
||||
int max = 1;
|
||||
|
||||
switch(remote_type) {
|
||||
case TV:
|
||||
remote_file = EXT_PATH("infrared/assets/tv.ir");
|
||||
break;
|
||||
case AC:
|
||||
remote_file = EXT_PATH("infrared/assets/ac.ir");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
static void infrared_cli_list_remote_signals(FuriString* remote_name) {
|
||||
if(furi_string_empty(remote_name)) {
|
||||
printf("Missing remote name.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dict_signals_init(signals_dict);
|
||||
key = furi_string_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
FuriString* remote_path = furi_string_alloc_printf(
|
||||
"%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name));
|
||||
|
||||
do {
|
||||
if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) {
|
||||
printf("Invalid remote name.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
dict_signals_t signals_dict;
|
||||
dict_signals_init(signals_dict);
|
||||
|
||||
FuriString* key = furi_string_alloc();
|
||||
FuriString* signal_name = furi_string_alloc();
|
||||
|
||||
success = flipper_format_buffered_file_open_existing(ff, remote_file);
|
||||
if(success) {
|
||||
FuriString* signal_name;
|
||||
signal_name = furi_string_alloc();
|
||||
printf("Valid signals:\r\n");
|
||||
int max = 1;
|
||||
while(flipper_format_read_string(ff, "name", signal_name)) {
|
||||
furi_string_set_str(key, furi_string_get_cstr(signal_name));
|
||||
int* v = dict_signals_get(signals_dict, key);
|
||||
@@ -449,57 +389,57 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
|
||||
dict_signals_set_at(signals_dict, key, 1);
|
||||
}
|
||||
}
|
||||
|
||||
dict_signals_it_t it;
|
||||
for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) {
|
||||
const struct dict_signals_pair_s* pair = dict_signals_cref(it);
|
||||
printf("\t%s\r\n", furi_string_get_cstr(pair->key));
|
||||
}
|
||||
furi_string_free(signal_name);
|
||||
}
|
||||
|
||||
furi_string_free(key);
|
||||
dict_signals_clear(signals_dict);
|
||||
furi_string_free(key);
|
||||
furi_string_free(signal_name);
|
||||
dict_signals_clear(signals_dict);
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_string_free(remote_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) {
|
||||
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
|
||||
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
|
||||
const char* remote_file = NULL;
|
||||
uint32_t i = 0;
|
||||
bool success = false;
|
||||
FuriString* remote_path = furi_string_alloc_printf(
|
||||
"%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name));
|
||||
|
||||
switch(remote_type) {
|
||||
case TV:
|
||||
remote_file = EXT_PATH("infrared/assets/tv.ir");
|
||||
break;
|
||||
case AC:
|
||||
remote_file = EXT_PATH("infrared/assets/ac.ir");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path));
|
||||
infrared_brute_force_add_record(
|
||||
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, furi_string_get_cstr(signal_name));
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, remote_file);
|
||||
infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal));
|
||||
|
||||
success = infrared_brute_force_calculate_messages(brute_force);
|
||||
if(success) {
|
||||
uint32_t record_count;
|
||||
uint32_t index = 0;
|
||||
int records_sent = 0;
|
||||
bool running = false;
|
||||
|
||||
running = infrared_brute_force_start(brute_force, index, &record_count);
|
||||
if(record_count <= 0) {
|
||||
printf("Invalid signal.\n");
|
||||
infrared_brute_force_reset(brute_force);
|
||||
return;
|
||||
do {
|
||||
if(furi_string_empty(signal_name)) {
|
||||
printf("Missing signal name.\r\n");
|
||||
break;
|
||||
}
|
||||
if(!infrared_brute_force_calculate_messages(brute_force)) {
|
||||
printf("Invalid remote name.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Sending %ld codes to the tv.\r\n", record_count);
|
||||
uint32_t record_count;
|
||||
bool running = infrared_brute_force_start(
|
||||
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count);
|
||||
|
||||
if(record_count <= 0) {
|
||||
printf("Invalid signal name.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Sending %ld signal(s)...\r\n", record_count);
|
||||
printf("Press Ctrl-C to stop.\r\n");
|
||||
|
||||
int records_sent = 0;
|
||||
while(running) {
|
||||
running = infrared_brute_force_send_next(brute_force);
|
||||
|
||||
@@ -510,14 +450,35 @@ static void
|
||||
}
|
||||
|
||||
infrared_brute_force_stop(brute_force);
|
||||
} else {
|
||||
printf("Invalid signal.\r\n");
|
||||
}
|
||||
} while(false);
|
||||
|
||||
furi_string_free(remote_path);
|
||||
infrared_brute_force_reset(brute_force);
|
||||
infrared_brute_force_free(brute_force);
|
||||
}
|
||||
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
FuriString* arg1 = furi_string_alloc();
|
||||
FuriString* arg2 = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, arg1)) break;
|
||||
if(!args_read_string_and_trim(args, arg2)) break;
|
||||
} while(false);
|
||||
|
||||
if(furi_string_empty(arg1)) {
|
||||
printf("Wrong arguments.\r\n");
|
||||
infrared_cli_print_usage();
|
||||
} else if(furi_string_equal_str(arg1, "list")) {
|
||||
infrared_cli_list_remote_signals(arg2);
|
||||
} else {
|
||||
infrared_cli_brute_force_signals(cli, arg1, arg2);
|
||||
}
|
||||
|
||||
furi_string_free(arg1);
|
||||
furi_string_free(arg2);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_infrared_is_busy()) {
|
||||
|
@@ -16,6 +16,7 @@ ADD_SCENE(infrared, remote_list, RemoteList)
|
||||
ADD_SCENE(infrared, universal, Universal)
|
||||
ADD_SCENE(infrared, universal_tv, UniversalTV)
|
||||
ADD_SCENE(infrared, universal_ac, UniversalAC)
|
||||
ADD_SCENE(infrared, universal_audio, UniversalAudio)
|
||||
ADD_SCENE(infrared, debug, Debug)
|
||||
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
||||
ADD_SCENE(infrared, rpc, Rpc)
|
||||
|
@@ -21,6 +21,12 @@ void infrared_scene_universal_on_enter(void* context) {
|
||||
SubmenuIndexUniversalTV,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Audio Players",
|
||||
SubmenuIndexUniversalAudio,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Air Conditioners",
|
||||
@@ -45,7 +51,7 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUniversalAudio) {
|
||||
//TODO Implement Audio universal remote
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,133 @@
|
||||
#include "../infrared_i.h"
|
||||
|
||||
#include "common/infrared_scene_universal_common.h"
|
||||
|
||||
void infrared_scene_universal_audio_on_enter(void* context) {
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
|
||||
Infrared* infrared = context;
|
||||
ButtonPanel* button_panel = infrared->button_panel;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/audio.ir"));
|
||||
|
||||
button_panel_reserve(button_panel, 2, 4);
|
||||
uint32_t i = 0;
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
11,
|
||||
&I_Power_25x27,
|
||||
&I_Power_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Power");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
0,
|
||||
36,
|
||||
11,
|
||||
&I_Mute_25x27,
|
||||
&I_Mute_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Mute");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
41,
|
||||
&I_Play_25x27,
|
||||
&I_Play_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Play");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
1,
|
||||
36,
|
||||
41,
|
||||
&I_Pause_25x27,
|
||||
&I_Pause_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Pause");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
71,
|
||||
&I_TrackPrev_25x27,
|
||||
&I_TrackPrev_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Prev");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
2,
|
||||
36,
|
||||
71,
|
||||
&I_TrackNext_25x27,
|
||||
&I_TrackNext_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Next");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
101,
|
||||
&I_Vol_down_25x27,
|
||||
&I_Vol_down_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Vol_dn");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
3,
|
||||
36,
|
||||
101,
|
||||
&I_Vol_up_25x27,
|
||||
&I_Vol_up_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Vol_up");
|
||||
|
||||
button_panel_add_label(button_panel, 1, 8, FontPrimary, "Mus. remote");
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
|
||||
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) {
|
||||
return infrared_scene_universal_common_on_event(context, event);
|
||||
}
|
||||
|
||||
void infrared_scene_universal_audio_on_exit(void* context) {
|
||||
infrared_scene_universal_common_on_exit(context);
|
||||
}
|
Reference in New Issue
Block a user