diff --git a/ReadMe.md b/ReadMe.md index 9aae945f7..3eac21459 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -23,6 +23,7 @@ Thank you to all the supporters! - Fixed snake scoring showing +7 - Added: [POCSAG (By Shmuma)](https://github.com/Shmuma/flipper-zero-pocsag) - Updated: [Intravelometer (By theageoflove)](https://github.com/theageoflove/flipperzero-zeitraffer) +- Added: [IR Remote (By Hong5489)](https://github.com/Hong5489/ir_remote) ## Install from Release FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE! diff --git a/applications/plugins/ir_remote/README.md b/applications/plugins/ir_remote/README.md new file mode 100644 index 000000000..4e490042a --- /dev/null +++ b/applications/plugins/ir_remote/README.md @@ -0,0 +1,63 @@ +# Alternative Infrared Remote for Flipperzero + +It is a plugin like [UniversalRF Remix](https://github.com/ESurge/flipperzero-firmware-unirfremix) but for infrared files. I do this plugin for convenience, because the main IR app need to navigate for different button abit troublesome (buttons like up,down,left,right,back). I found it useful for TV and TV box. + +It supports short press and long press input for different ir remote buttons. Tested on the [unleashed firmware version unlshd-015](https://github.com/DarkFlippers/unleashed-firmware/releases/tag/unlshd-015) + + +## How to install + +1. Update unleashed firmware to the version unlshd-015, then download the `ir_remote.fap` from [releases](https://github.com/Hong5489/ir_remote/tags) + +2. Put the `ir_remote.fap` file in your flipper's SD card, under `apps` folder + +## How to use + +1. Similar to UniRF app, put the path of the ir file and the ir button for each button on flipper (UP,DOWN,LEFT,RIGHT,BACK) + +The format With `HOLD` one is long press, without is short press + +Example of the configuration file: +``` +REMOTE: /ext/infrared/Philips_32PFL4208T.ir +UP: Up +DOWN: Down +LEFT: Left +RIGHT: Right +OK: +BACK: Back +UPHOLD: VOL+ +DOWNHOLD: VOL- +LEFTHOLD: Source +RIGHTHOLD: SmartTV +OKHOLD: POWER +``` + +Leave it empty for the button you don't need + +2. Save it as `.txt` file, then create a new folder in your SD card `ir_remote`, put it inside the folder + +3. Lastly, you can open the app, choose the configuration file, then you can try out the ir for each buttons + +4. Long press back button to exit the app + +## How to build + +You can clone this repo and put it inside the `applications_user` folder, then build it with the command: +``` +./fbt fap_ir_remote +``` +Or you can build and run it on your flipper with the command: +``` +./fbt launch_app APPSRC=applications_user/ir_remote +``` + +## Screenshots + +Choose config file to map + +![image](ir.png) + +Show all button name in the config file (If empty will show N/A). Upper part short press, Lower part long press + +![image2](ir2.png) \ No newline at end of file diff --git a/applications/plugins/ir_remote/application.fam b/applications/plugins/ir_remote/application.fam new file mode 100644 index 000000000..e74ea9131 --- /dev/null +++ b/applications/plugins/ir_remote/application.fam @@ -0,0 +1,14 @@ +App( + appid="IR_Remote", + name="IR Remote", + apptype=FlipperAppType.EXTERNAL, + entry_point="infrared_remote_app", + stack_size=3 * 1024, + requires=[ + "gui", + "dialogs", + ], + fap_category="Tools", + fap_icon="ir_10px.png", + fap_icon_assets="images", +) diff --git a/applications/plugins/ir_remote/example.txt b/applications/plugins/ir_remote/example.txt new file mode 100644 index 000000000..ffd192b8d --- /dev/null +++ b/applications/plugins/ir_remote/example.txt @@ -0,0 +1,12 @@ +REMOTE: /ext/infrared/Philips_32PFL4208T.ir +UP: Up +DOWN: Down +LEFT: Left +RIGHT: Right +OK: +BACK: Back +UPHOLD: VOL+ +DOWNHOLD: VOL- +LEFTHOLD: Source +RIGHTHOLD: SmartTV +OKHOLD: POWER \ No newline at end of file diff --git a/applications/plugins/ir_remote/images/ButtonDown_7x4.png b/applications/plugins/ir_remote/images/ButtonDown_7x4.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/applications/plugins/ir_remote/images/ButtonDown_7x4.png differ diff --git a/applications/plugins/ir_remote/images/ButtonLeft_4x7.png b/applications/plugins/ir_remote/images/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/applications/plugins/ir_remote/images/ButtonLeft_4x7.png differ diff --git a/applications/plugins/ir_remote/images/ButtonRight_4x7.png b/applications/plugins/ir_remote/images/ButtonRight_4x7.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/applications/plugins/ir_remote/images/ButtonRight_4x7.png differ diff --git a/applications/plugins/ir_remote/images/ButtonUp_7x4.png b/applications/plugins/ir_remote/images/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/applications/plugins/ir_remote/images/ButtonUp_7x4.png differ diff --git a/applications/plugins/ir_remote/images/Ok_btn_9x9.png b/applications/plugins/ir_remote/images/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/applications/plugins/ir_remote/images/Ok_btn_9x9.png differ diff --git a/applications/plugins/ir_remote/images/back_10px.png b/applications/plugins/ir_remote/images/back_10px.png new file mode 100644 index 000000000..f9c615a99 Binary files /dev/null and b/applications/plugins/ir_remote/images/back_10px.png differ diff --git a/applications/plugins/ir_remote/images/sub1_10px.png b/applications/plugins/ir_remote/images/sub1_10px.png new file mode 100644 index 000000000..5a25fdf4e Binary files /dev/null and b/applications/plugins/ir_remote/images/sub1_10px.png differ diff --git a/applications/plugins/ir_remote/infrared_remote.c b/applications/plugins/ir_remote/infrared_remote.c new file mode 100644 index 000000000..3a528a656 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_remote.c @@ -0,0 +1,188 @@ +#include "infrared_remote.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "InfraredRemote" + +ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST); + +struct InfraredRemote { + InfraredButtonArray_t buttons; + FuriString* name; + FuriString* path; +}; + +static void infrared_remote_clear_buttons(InfraredRemote* remote) { + InfraredButtonArray_it_t it; + for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); + InfraredButtonArray_next(it)) { + infrared_remote_button_free(*InfraredButtonArray_cref(it)); + } + InfraredButtonArray_reset(remote->buttons); +} + +InfraredRemote* infrared_remote_alloc() { + InfraredRemote* remote = malloc(sizeof(InfraredRemote)); + InfraredButtonArray_init(remote->buttons); + remote->name = furi_string_alloc(); + remote->path = furi_string_alloc(); + return remote; +} + +void infrared_remote_free(InfraredRemote* remote) { + infrared_remote_clear_buttons(remote); + InfraredButtonArray_clear(remote->buttons); + furi_string_free(remote->path); + furi_string_free(remote->name); + free(remote); +} + +void infrared_remote_reset(InfraredRemote* remote) { + infrared_remote_clear_buttons(remote); + furi_string_reset(remote->name); + furi_string_reset(remote->path); +} + +void infrared_remote_set_name(InfraredRemote* remote, const char* name) { + furi_string_set(remote->name, name); +} + +const char* infrared_remote_get_name(InfraredRemote* remote) { + return furi_string_get_cstr(remote->name); +} + +void infrared_remote_set_path(InfraredRemote* remote, const char* path) { + furi_string_set(remote->path, path); +} + +const char* infrared_remote_get_path(InfraredRemote* remote) { + return furi_string_get_cstr(remote->path); +} + +size_t infrared_remote_get_button_count(InfraredRemote* remote) { + return InfraredButtonArray_size(remote->buttons); +} + +InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) { + furi_assert(index < InfraredButtonArray_size(remote->buttons)); + return *InfraredButtonArray_get(remote->buttons, index); +} + +bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) { + for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) { + InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i); + if(!strcmp(infrared_remote_button_get_name(button), name)) { + *index = i; + return true; + } + } + return false; +} + +bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) { + InfraredRemoteButton* button = infrared_remote_button_alloc(); + infrared_remote_button_set_name(button, name); + infrared_remote_button_set_signal(button, signal); + InfraredButtonArray_push_back(remote->buttons, button); + return infrared_remote_store(remote); +} + +bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) { + furi_assert(index < InfraredButtonArray_size(remote->buttons)); + InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index); + infrared_remote_button_set_name(button, new_name); + return infrared_remote_store(remote); +} + +bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { + furi_assert(index < InfraredButtonArray_size(remote->buttons)); + InfraredRemoteButton* button; + InfraredButtonArray_pop_at(&button, remote->buttons, index); + infrared_remote_button_free(button); + return infrared_remote_store(remote); +} + +bool infrared_remote_store(InfraredRemote* remote) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + const char* path = furi_string_get_cstr(remote->path); + + FURI_LOG_I(TAG, "store file: \'%s\'", path); + + bool success = flipper_format_file_open_always(ff, path) && + flipper_format_write_header_cstr(ff, "IR signals file", 1); + if(success) { + InfraredButtonArray_it_t it; + for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); + InfraredButtonArray_next(it)) { + InfraredRemoteButton* button = *InfraredButtonArray_cref(it); + success = infrared_signal_save( + infrared_remote_button_get_signal(button), + ff, + infrared_remote_button_get_name(button)); + if(!success) { + break; + } + } + } + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + return success; +} + +bool infrared_remote_load(InfraredRemote* remote, FuriString* path) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + FuriString* buf; + buf = furi_string_alloc(); + + FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path)); + bool success = flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path)); + + if(success) { + uint32_t version; + success = flipper_format_read_header(ff, buf, &version) && + !furi_string_cmp(buf, "IR signals file") && (version == 1); + } + + if(success) { + path_extract_filename(path, buf, true); + infrared_remote_clear_buttons(remote); + infrared_remote_set_name(remote, furi_string_get_cstr(buf)); + infrared_remote_set_path(remote, furi_string_get_cstr(path)); + + for(bool can_read = true; can_read;) { + InfraredRemoteButton* button = infrared_remote_button_alloc(); + can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf); + if(can_read) { + infrared_remote_button_set_name(button, furi_string_get_cstr(buf)); + InfraredButtonArray_push_back(remote->buttons, button); + } else { + infrared_remote_button_free(button); + } + } + } + + furi_string_free(buf); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + return success; +} + +bool infrared_remote_remove(InfraredRemote* remote) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path)); + infrared_remote_reset(remote); + + furi_record_close(RECORD_STORAGE); + return (status == FSE_OK || status == FSE_NOT_EXIST); +} diff --git a/applications/plugins/ir_remote/infrared_remote.h b/applications/plugins/ir_remote/infrared_remote.h new file mode 100644 index 000000000..6eac193d3 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_remote.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "infrared_remote_button.h" + +typedef struct InfraredRemote InfraredRemote; + +InfraredRemote* infrared_remote_alloc(); +void infrared_remote_free(InfraredRemote* remote); +void infrared_remote_reset(InfraredRemote* remote); + +void infrared_remote_set_name(InfraredRemote* remote, const char* name); +const char* infrared_remote_get_name(InfraredRemote* remote); + +void infrared_remote_set_path(InfraredRemote* remote, const char* path); +const char* infrared_remote_get_path(InfraredRemote* remote); + +size_t infrared_remote_get_button_count(InfraredRemote* remote); +InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index); +bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index); + +bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal); +bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); +bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); + +bool infrared_remote_store(InfraredRemote* remote); +bool infrared_remote_load(InfraredRemote* remote, FuriString* path); +bool infrared_remote_remove(InfraredRemote* remote); diff --git a/applications/plugins/ir_remote/infrared_remote_app.c b/applications/plugins/ir_remote/infrared_remote_app.c new file mode 100644 index 000000000..05c13ee59 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_remote_app.c @@ -0,0 +1,499 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "infrared_signal.h" +#include "infrared_remote.h" +#include "infrared_remote_button.h" +#define TAG "IR_Remote" + +#include + +typedef struct { + int status; + ViewPort* view_port; + FuriString* up_button; + FuriString* down_button; + FuriString* left_button; + FuriString* right_button; + FuriString* ok_button; + FuriString* back_button; + FuriString* up_hold_button; + FuriString* down_hold_button; + FuriString* left_hold_button; + FuriString* right_hold_button; + FuriString* ok_hold_button; +}IRApp; + +// Screen is 128x64 px +static void app_draw_callback(Canvas* canvas, void* ctx) { + // Show config is incorrect when cannot read the remote file + // Showing button string in the screen, upper part is short press, lower part is long press + IRApp* app = ctx; + if(app->status){ + canvas_clear(canvas); + view_port_set_orientation(app->view_port, ViewPortOrientationHorizontal); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Config is incorrect."); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "Please configure map."); + canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); + }else{ + canvas_clear(canvas); + view_port_set_orientation(app->view_port, ViewPortOrientationVertical); + canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 0, 53, &I_back_10px); + + //Labels + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 32, 8,AlignCenter,AlignCenter, furi_string_get_cstr(app->up_button)); + canvas_draw_str_aligned(canvas, 32, 18,AlignCenter,AlignCenter, furi_string_get_cstr(app->down_button)); + canvas_draw_str_aligned(canvas, 32, 28,AlignCenter,AlignCenter, furi_string_get_cstr(app->left_button)); + canvas_draw_str_aligned(canvas, 32, 38,AlignCenter,AlignCenter, furi_string_get_cstr(app->right_button)); + canvas_draw_str_aligned(canvas, 32, 48,AlignCenter,AlignCenter, furi_string_get_cstr(app->ok_button)); + canvas_draw_str_aligned(canvas, 32, 58, AlignCenter, AlignCenter, furi_string_get_cstr(app->back_button)); + + canvas_draw_line(canvas, 0, 65, 64, 65); + + canvas_draw_icon(canvas, 1, 70, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 1, 80, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 2, 88, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 2, 98, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 0, 107, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 0, 118, &I_back_10px); + + canvas_draw_str_aligned(canvas, 32, 73,AlignCenter,AlignCenter, furi_string_get_cstr(app->up_hold_button)); + canvas_draw_str_aligned(canvas, 32, 83,AlignCenter,AlignCenter, furi_string_get_cstr(app->down_hold_button)); + canvas_draw_str_aligned(canvas, 32, 93,AlignCenter,AlignCenter, furi_string_get_cstr(app->left_hold_button)); + canvas_draw_str_aligned(canvas, 32, 103,AlignCenter,AlignCenter, furi_string_get_cstr(app->right_hold_button)); + canvas_draw_str_aligned(canvas, 32, 113,AlignCenter,AlignCenter, furi_string_get_cstr(app->ok_hold_button)); + canvas_draw_str_aligned(canvas, 32, 123, AlignCenter, AlignCenter, "Exit App"); + } + + +} + +static void app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t infrared_remote_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // App button string + IRApp* app = malloc(sizeof(IRApp)); + app->up_button = furi_string_alloc(); + app->down_button = furi_string_alloc(); + app->left_button = furi_string_alloc(); + app->right_button = furi_string_alloc(); + app->ok_button = furi_string_alloc(); + app->back_button = furi_string_alloc(); + app->up_hold_button = furi_string_alloc(); + app->down_hold_button = furi_string_alloc(); + app->left_hold_button = furi_string_alloc(); + app->right_hold_button = furi_string_alloc(); + app->ok_hold_button = furi_string_alloc(); + app->view_port = view_port_alloc(); + + // Configure view port + view_port_draw_callback_set(app->view_port, app_draw_callback, app); + view_port_input_callback_set(app->view_port, app_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, app->view_port, GuiLayerFullscreen); + + InputEvent event; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".txt", &I_sub1_10px); + FuriString* map_file = furi_string_alloc(); + furi_string_set(map_file,"/ext/infrared/ir_remote"); + + bool res = dialog_file_browser_show(dialogs, map_file, map_file, &browser_options); + + furi_record_close(RECORD_DIALOGS); + + // if user didn't choose anything, free everything and exit + if(!res){ + FURI_LOG_I(TAG, "exit"); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + furi_string_free(app->up_button); + furi_string_free(app->down_button); + furi_string_free(app->left_button); + furi_string_free(app->right_button); + furi_string_free(app->ok_button); + furi_string_free(app->back_button); + furi_string_free(app->up_hold_button); + furi_string_free(app->down_hold_button); + furi_string_free(app->left_hold_button); + furi_string_free(app->right_hold_button); + furi_string_free(app->ok_hold_button); + + view_port_enabled_set(app->view_port, false); + gui_remove_view_port(gui, app->view_port); + view_port_free(app->view_port); + free(app); + furi_message_queue_free(event_queue); + + furi_record_close(RECORD_GUI); + return 255; + } + + + InfraredRemote* remote = infrared_remote_alloc(); + FuriString* remote_path = furi_string_alloc(); + + InfraredSignal* up_signal = infrared_signal_alloc(); + InfraredSignal* down_signal = infrared_signal_alloc(); + InfraredSignal* left_signal = infrared_signal_alloc(); + InfraredSignal* right_signal = infrared_signal_alloc(); + InfraredSignal* ok_signal = infrared_signal_alloc(); + InfraredSignal* back_signal = infrared_signal_alloc(); + InfraredSignal* up_hold_signal = infrared_signal_alloc(); + InfraredSignal* down_hold_signal = infrared_signal_alloc(); + InfraredSignal* left_hold_signal = infrared_signal_alloc(); + InfraredSignal* right_hold_signal = infrared_signal_alloc(); + InfraredSignal* ok_hold_signal = infrared_signal_alloc(); + + bool up_enabled = false; + bool down_enabled = false; + bool left_enabled = false; + bool right_enabled = false; + bool ok_enabled = false; + bool back_enabled = false; + bool up_hold_enabled = false; + bool down_hold_enabled = false; + bool left_hold_enabled = false; + bool right_hold_enabled = false; + bool ok_hold_enabled = false; + + if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(map_file))) { + FURI_LOG_E(TAG, "Could not open MAP file %s",furi_string_get_cstr(map_file)); + app->status = 1; + } else { + //Filename Assignment/Check Start + + if(!flipper_format_read_string(ff, "REMOTE", remote_path)) { + FURI_LOG_E(TAG, "Could not read REMOTE string"); + app->status = 1; + } else { + if(!infrared_remote_load(remote, remote_path)){ + FURI_LOG_E(TAG, "Could not load ir file: %s",furi_string_get_cstr(remote_path)); + app->status = 1; + }else{ + FURI_LOG_I(TAG, "Loaded REMOTE file: %s", furi_string_get_cstr(remote_path)); + } + } + + + //assign variables to values within map file + //set missing filenames to N/A + //assign button signals + size_t index = 0; + if(!flipper_format_read_string(ff, "UP", app->up_button)) { + FURI_LOG_W(TAG, "Could not read UP string"); + furi_string_set(app->up_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->up_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + up_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + up_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "DOWN", app->down_button)) { + FURI_LOG_W(TAG, "Could not read DOWN string"); + furi_string_set(app->down_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->down_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + down_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + down_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "LEFT", app->left_button)) { + FURI_LOG_W(TAG, "Could not read LEFT string"); + furi_string_set(app->left_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->left_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + left_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + left_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "RIGHT", app->right_button)) { + FURI_LOG_W(TAG, "Could not read RIGHT string"); + furi_string_set(app->right_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->right_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + right_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + right_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "OK", app->ok_button)) { + FURI_LOG_W(TAG, "Could not read OK string"); + furi_string_set(app->ok_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->ok_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + ok_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + ok_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "BACK", app->back_button)) { + FURI_LOG_W(TAG, "Could not read BACK string"); + furi_string_set(app->back_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->back_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + back_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + back_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "UPHOLD", app->up_hold_button)) { + FURI_LOG_W(TAG, "Could not read UPHOLD string"); + furi_string_set(app->up_hold_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->up_hold_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + up_hold_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + up_hold_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "DOWNHOLD", app->down_hold_button)) { + FURI_LOG_W(TAG, "Could not read DOWNHOLD string"); + furi_string_set(app->down_hold_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->down_hold_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + down_hold_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + down_hold_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "LEFTHOLD", app->left_hold_button)) { + FURI_LOG_W(TAG, "Could not read LEFTHOLD string"); + furi_string_set(app->left_hold_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->left_hold_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + left_hold_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + left_hold_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "RIGHTHOLD", app->right_hold_button)) { + FURI_LOG_W(TAG, "Could not read RIGHTHOLD string"); + furi_string_set(app->right_hold_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->right_hold_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + right_hold_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + right_hold_enabled = true; + } + } + + if(!flipper_format_read_string(ff, "OKHOLD", app->ok_hold_button)) { + FURI_LOG_W(TAG, "Could not read OKHOLD string"); + furi_string_set(app->ok_hold_button,"N/A"); + } else { + if(!infrared_remote_find_button_by_name(remote,furi_string_get_cstr(app->ok_hold_button),&index)){ + FURI_LOG_W(TAG,"Error"); + }else{ + ok_hold_signal = infrared_remote_button_get_signal(infrared_remote_get_button(remote, index)); + ok_hold_enabled = true; + } + } + + } + + furi_string_free(remote_path); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + bool running = true; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + if(app->status){ + view_port_update(app->view_port); + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + if(event.type == InputTypeShort) { + switch(event.key) { + case InputKeyBack: + running = false; + break; + default: + break; + } + } + } + } + }else{ + view_port_update(app->view_port); + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + // short press signal + if(event.type == InputTypeShort) { + switch(event.key) { + case InputKeyUp: + if(up_enabled){ + infrared_signal_transmit(up_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "up"); + } + break; + case InputKeyDown: + if(down_enabled){ + infrared_signal_transmit(down_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "down"); + } + break; + case InputKeyRight: + if(right_enabled){ + infrared_signal_transmit(right_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "right"); + } + break; + case InputKeyLeft: + if(left_enabled){ + infrared_signal_transmit(left_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "left"); + } + break; + case InputKeyOk: + if(ok_enabled){ + infrared_signal_transmit(ok_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "ok"); + } + break; + case InputKeyBack: + if(back_enabled){ + infrared_signal_transmit(back_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "back"); + } + break; + default: + running = false; + break; + } + // long press signal + }else if (event.type == InputTypeLong){ + switch(event.key) { + case InputKeyUp: + if(up_hold_enabled){ + infrared_signal_transmit(up_hold_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "up!"); + } + break; + case InputKeyDown: + if(down_hold_enabled){ + infrared_signal_transmit(down_hold_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "down!"); + } + break; + case InputKeyRight: + if(right_hold_enabled){ + infrared_signal_transmit(right_hold_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "right!"); + } + break; + case InputKeyLeft: + if(left_hold_enabled){ + infrared_signal_transmit(left_hold_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "left!"); + } + break; + case InputKeyOk: + if(ok_hold_enabled){ + infrared_signal_transmit(ok_hold_signal); + notification_message(notification, &sequence_blink_start_magenta); + FURI_LOG_I(TAG, "ok!"); + } + break; + default: + running = false; + break; + } + }else if(event.type == InputTypeRelease){ + notification_message(notification, &sequence_blink_stop); + } + } + } + } + + // Free all things + furi_string_free(app->up_button); + furi_string_free(app->down_button); + furi_string_free(app->left_button); + furi_string_free(app->right_button); + furi_string_free(app->ok_button); + furi_string_free(app->back_button); + furi_string_free(app->up_hold_button); + furi_string_free(app->down_hold_button); + furi_string_free(app->left_hold_button); + furi_string_free(app->right_hold_button); + furi_string_free(app->ok_hold_button); + + infrared_remote_free(remote); + view_port_enabled_set(app->view_port, false); + gui_remove_view_port(gui, app->view_port); + view_port_free(app->view_port); + free(app); + furi_message_queue_free(event_queue); + + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/ir_remote/infrared_remote_button.c b/applications/plugins/ir_remote/infrared_remote_button.c new file mode 100644 index 000000000..1f6315ec5 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_remote_button.c @@ -0,0 +1,37 @@ +#include "infrared_remote_button.h" + +#include + +struct InfraredRemoteButton { + FuriString* name; + InfraredSignal* signal; +}; + +InfraredRemoteButton* infrared_remote_button_alloc() { + InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton)); + button->name = furi_string_alloc(); + button->signal = infrared_signal_alloc(); + return button; +} + +void infrared_remote_button_free(InfraredRemoteButton* button) { + furi_string_free(button->name); + infrared_signal_free(button->signal); + free(button); +} + +void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) { + furi_string_set(button->name, name); +} + +const char* infrared_remote_button_get_name(InfraredRemoteButton* button) { + return furi_string_get_cstr(button->name); +} + +void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) { + infrared_signal_set_signal(button->signal, signal); +} + +InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) { + return button->signal; +} diff --git a/applications/plugins/ir_remote/infrared_remote_button.h b/applications/plugins/ir_remote/infrared_remote_button.h new file mode 100644 index 000000000..f25b759b5 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_remote_button.h @@ -0,0 +1,14 @@ +#pragma once + +#include "infrared_signal.h" + +typedef struct InfraredRemoteButton InfraredRemoteButton; + +InfraredRemoteButton* infrared_remote_button_alloc(); +void infrared_remote_button_free(InfraredRemoteButton* button); + +void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name); +const char* infrared_remote_button_get_name(InfraredRemoteButton* button); + +void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal); +InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button); diff --git a/applications/plugins/ir_remote/infrared_signal.c b/applications/plugins/ir_remote/infrared_signal.c new file mode 100644 index 000000000..d4d66af06 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_signal.c @@ -0,0 +1,300 @@ +#include "infrared_signal.h" + +#include +#include +#include +#include +#include + +#define TAG "InfraredSignal" + +struct InfraredSignal { + bool is_raw; + union { + InfraredMessage message; + InfraredRawSignal raw; + } payload; +}; + +static void infrared_signal_clear_timings(InfraredSignal* signal) { + if(signal->is_raw) { + free(signal->payload.raw.timings); + signal->payload.raw.timings_size = 0; + signal->payload.raw.timings = NULL; + } +} + +static bool infrared_signal_is_message_valid(InfraredMessage* message) { + if(!infrared_is_protocol_valid(message->protocol)) { + FURI_LOG_E(TAG, "Unknown protocol"); + return false; + } + + uint32_t address_length = infrared_get_protocol_address_length(message->protocol); + uint32_t address_mask = (1UL << address_length) - 1; + + if(message->address != (message->address & address_mask)) { + FURI_LOG_E( + TAG, + "Address is out of range (mask 0x%08lX): 0x%lX\r\n", + address_mask, + message->address); + return false; + } + + uint32_t command_length = infrared_get_protocol_command_length(message->protocol); + uint32_t command_mask = (1UL << command_length) - 1; + + if(message->command != (message->command & command_mask)) { + FURI_LOG_E( + TAG, + "Command is out of range (mask 0x%08lX): 0x%lX\r\n", + command_mask, + message->command); + return false; + } + + return true; +} + +static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { + if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) { + FURI_LOG_E( + TAG, + "Frequency is out of range (%X - %X): %lX", + INFRARED_MIN_FREQUENCY, + INFRARED_MAX_FREQUENCY, + raw->frequency); + return false; + + } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) { + FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle); + return false; + + } else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) { + FURI_LOG_E( + TAG, + "Timings amount is out of range (0 - %X): %X", + MAX_TIMINGS_AMOUNT, + raw->timings_size); + return false; + } + + return true; +} + +static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) { + const char* protocol_name = infrared_get_protocol_name(message->protocol); + return flipper_format_write_string_cstr(ff, "type", "parsed") && + flipper_format_write_string_cstr(ff, "protocol", protocol_name) && + flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) && + flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4); +} + +static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) { + furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); + return flipper_format_write_string_cstr(ff, "type", "raw") && + flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) && + flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) && + flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size); +} + +static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { + FuriString* buf; + buf = furi_string_alloc(); + bool success = false; + + do { + if(!flipper_format_read_string(ff, "protocol", buf)) break; + + InfraredMessage message; + message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); + + success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) && + flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) && + infrared_signal_is_message_valid(&message); + + if(!success) break; + + infrared_signal_set_message(signal, &message); + } while(0); + + furi_string_free(buf); + return success; +} + +static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { + uint32_t timings_size, frequency; + float duty_cycle; + + bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) && + flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) && + flipper_format_get_value_count(ff, "data", &timings_size); + + if(!success || timings_size > MAX_TIMINGS_AMOUNT) { + return false; + } + + uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); + success = flipper_format_read_uint32(ff, "data", timings, timings_size); + + if(success) { + infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); + } + + free(timings); + return success; +} + +static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { + FuriString* tmp = furi_string_alloc(); + + bool success = false; + + do { + if(!flipper_format_read_string(ff, "type", tmp)) break; + if(furi_string_equal(tmp, "raw")) { + success = infrared_signal_read_raw(signal, ff); + } else if(furi_string_equal(tmp, "parsed")) { + success = infrared_signal_read_message(signal, ff); + } else { + FURI_LOG_E(TAG, "Unknown signal type"); + } + } while(false); + + furi_string_free(tmp); + return success; +} + +InfraredSignal* infrared_signal_alloc() { + InfraredSignal* signal = malloc(sizeof(InfraredSignal)); + + signal->is_raw = false; + signal->payload.message.protocol = InfraredProtocolUnknown; + + return signal; +} + +void infrared_signal_free(InfraredSignal* signal) { + infrared_signal_clear_timings(signal); + free(signal); +} + +bool infrared_signal_is_raw(InfraredSignal* signal) { + return signal->is_raw; +} + +bool infrared_signal_is_valid(InfraredSignal* signal) { + return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) : + infrared_signal_is_message_valid(&signal->payload.message); +} + +void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) { + if(other->is_raw) { + const InfraredRawSignal* raw = &other->payload.raw; + infrared_signal_set_raw_signal( + signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); + } else { + const InfraredMessage* message = &other->payload.message; + infrared_signal_set_message(signal, message); + } +} + +void infrared_signal_set_raw_signal( + InfraredSignal* signal, + const uint32_t* timings, + size_t timings_size, + uint32_t frequency, + float duty_cycle) { + infrared_signal_clear_timings(signal); + + signal->is_raw = true; + + signal->payload.raw.timings_size = timings_size; + signal->payload.raw.frequency = frequency; + signal->payload.raw.duty_cycle = duty_cycle; + + signal->payload.raw.timings = malloc(timings_size * sizeof(uint32_t)); + memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t)); +} + +InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) { + furi_assert(signal->is_raw); + return &signal->payload.raw; +} + +void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) { + infrared_signal_clear_timings(signal); + + signal->is_raw = false; + signal->payload.message = *message; +} + +InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) { + furi_assert(!signal->is_raw); + return &signal->payload.message; +} + +bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) { + if(!flipper_format_write_comment_cstr(ff, "") || + !flipper_format_write_string_cstr(ff, "name", name)) { + return false; + } else if(signal->is_raw) { + return infrared_signal_save_raw(&signal->payload.raw, ff); + } else { + return infrared_signal_save_message(&signal->payload.message, ff); + } +} + +bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { + FuriString* tmp = furi_string_alloc(); + + bool success = false; + + do { + if(!flipper_format_read_string(ff, "name", tmp)) break; + furi_string_set(name, tmp); + if(!infrared_signal_read_body(signal, ff)) break; + success = true; + } while(0); + + furi_string_free(tmp); + return success; +} + +bool infrared_signal_search_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const FuriString* name) { + bool success = false; + FuriString* tmp = furi_string_alloc(); + + do { + bool is_name_found = false; + while(flipper_format_read_string(ff, "name", tmp)) { + is_name_found = furi_string_equal(name, tmp); + if(is_name_found) break; + } + if(!is_name_found) break; + if(!infrared_signal_read_body(signal, ff)) break; + success = true; + } while(false); + + furi_string_free(tmp); + return success; +} + +void infrared_signal_transmit(InfraredSignal* signal) { + if(signal->is_raw) { + InfraredRawSignal* raw_signal = &signal->payload.raw; + infrared_send_raw_ext( + raw_signal->timings, + raw_signal->timings_size, + true, + raw_signal->frequency, + raw_signal->duty_cycle); + } else { + InfraredMessage* message = &signal->payload.message; + infrared_send(message, 1); + } +} diff --git a/applications/plugins/ir_remote/infrared_signal.h b/applications/plugins/ir_remote/infrared_signal.h new file mode 100644 index 000000000..637d859b0 --- /dev/null +++ b/applications/plugins/ir_remote/infrared_signal.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include +#include + +typedef struct InfraredSignal InfraredSignal; + +typedef struct { + size_t timings_size; + uint32_t* timings; + uint32_t frequency; + float duty_cycle; +} InfraredRawSignal; + +InfraredSignal* infrared_signal_alloc(); +void infrared_signal_free(InfraredSignal* signal); + +bool infrared_signal_is_raw(InfraredSignal* signal); +bool infrared_signal_is_valid(InfraredSignal* signal); + +void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other); + +void infrared_signal_set_raw_signal( + InfraredSignal* signal, + const uint32_t* timings, + size_t timings_size, + uint32_t frequency, + float duty_cycle); +InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal); + +void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message); +InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); + +bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); +bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); +bool infrared_signal_search_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const FuriString* name); + +void infrared_signal_transmit(InfraredSignal* signal); diff --git a/applications/plugins/ir_remote/ir.png b/applications/plugins/ir_remote/ir.png new file mode 100644 index 000000000..71bb60fa3 Binary files /dev/null and b/applications/plugins/ir_remote/ir.png differ diff --git a/applications/plugins/ir_remote/ir2.png b/applications/plugins/ir_remote/ir2.png new file mode 100644 index 000000000..133b1c866 Binary files /dev/null and b/applications/plugins/ir_remote/ir2.png differ diff --git a/applications/plugins/ir_remote/ir_10px.png b/applications/plugins/ir_remote/ir_10px.png new file mode 100644 index 000000000..22c986180 Binary files /dev/null and b/applications/plugins/ir_remote/ir_10px.png differ diff --git a/assets/resources/infrared/Roku.ir b/assets/resources/infrared/Roku.ir new file mode 100644 index 000000000..670e80862 --- /dev/null +++ b/assets/resources/infrared/Roku.ir @@ -0,0 +1,116 @@ +Filetype: IR signals file +Version: 1 +# +name: Power +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0F F0 00 00 +# +name: Ok +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 18 E7 00 00 +# +name: Up +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 42 BD 00 00 +# +name: Down +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 43 BC 00 00 +# +name: Left +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 16 E9 00 00 +# +name: Right +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 15 EA 00 00 +# +name: V_up +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0C F3 00 00 +# +name: V_down +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0D F2 00 00 +# +name: Back +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 1B E4 00 00 +# +name: Home +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 14 EB 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 09 00 00 00 +# +name: Netflix +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 4C 00 00 00 +# +name: Hulu +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 5C 00 00 00 +# +name: Sleep +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 1A 00 00 00 +# +name: Opt +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 0C 00 00 00 +# +name: Return +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 41 00 00 00 +# +name: Seek_fwd +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 5B 00 00 00 +# +name: Rewind +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 4F 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 42 00 00 00 diff --git a/assets/resources/infrared/ir_remote/Roku.txt b/assets/resources/infrared/ir_remote/Roku.txt new file mode 100644 index 000000000..9b874621c --- /dev/null +++ b/assets/resources/infrared/ir_remote/Roku.txt @@ -0,0 +1,12 @@ +REMOTE: /ext/infrared/Roku.ir +UP: Up +DOWN: Down +LEFT: Left +RIGHT: Right +OK: Ok +BACK: Back +UPHOLD: V_up +DOWNHOLD: V_down +LEFTHOLD: Opt +RIGHTHOLD: Return +OKHOLD: Power \ No newline at end of file