IR Remote
@@ -23,6 +23,7 @@ Thank you to all the supporters!
|
|||||||
- Fixed snake scoring showing +7
|
- Fixed snake scoring showing +7
|
||||||
- Added: [POCSAG (By Shmuma)](https://github.com/Shmuma/flipper-zero-pocsag)
|
- Added: [POCSAG (By Shmuma)](https://github.com/Shmuma/flipper-zero-pocsag)
|
||||||
- Updated: [Intravelometer (By theageoflove)](https://github.com/theageoflove/flipperzero-zeitraffer)
|
- Updated: [Intravelometer (By theageoflove)](https://github.com/theageoflove/flipperzero-zeitraffer)
|
||||||
|
- Added: [IR Remote (By Hong5489)](https://github.com/Hong5489/ir_remote)
|
||||||
|
|
||||||
## Install from Release
|
## Install from Release
|
||||||
FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE!
|
FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE!
|
||||||
|
|||||||
63
applications/plugins/ir_remote/README.md
Normal file
@@ -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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Show all button name in the config file (If empty will show N/A). Upper part short press, Lower part long press
|
||||||
|
|
||||||
|

|
||||||
14
applications/plugins/ir_remote/application.fam
Normal file
@@ -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",
|
||||||
|
)
|
||||||
12
applications/plugins/ir_remote/example.txt
Normal file
@@ -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
|
||||||
BIN
applications/plugins/ir_remote/images/ButtonDown_7x4.png
Normal file
|
After Width: | Height: | Size: 102 B |
BIN
applications/plugins/ir_remote/images/ButtonLeft_4x7.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
applications/plugins/ir_remote/images/ButtonRight_4x7.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
applications/plugins/ir_remote/images/ButtonUp_7x4.png
Normal file
|
After Width: | Height: | Size: 102 B |
BIN
applications/plugins/ir_remote/images/Ok_btn_9x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/ir_remote/images/back_10px.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
applications/plugins/ir_remote/images/sub1_10px.png
Normal file
|
After Width: | Height: | Size: 299 B |
188
applications/plugins/ir_remote/infrared_remote.c
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#include "infrared_remote.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <m-array.h>
|
||||||
|
#include <toolbox/path.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <core/common_defines.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
29
applications/plugins/ir_remote/infrared_remote.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
499
applications/plugins/ir_remote/infrared_remote_app.c
Normal file
@@ -0,0 +1,499 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <IR_Remote_icons.h>
|
||||||
|
|
||||||
|
#include <notification/notification.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
#include "infrared_signal.h"
|
||||||
|
#include "infrared_remote.h"
|
||||||
|
#include "infrared_remote_button.h"
|
||||||
|
#define TAG "IR_Remote"
|
||||||
|
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
37
applications/plugins/ir_remote/infrared_remote_button.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "infrared_remote_button.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
14
applications/plugins/ir_remote/infrared_remote_button.h
Normal file
@@ -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);
|
||||||
300
applications/plugins/ir_remote/infrared_signal.c
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
#include "infrared_signal.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <core/check.h>
|
||||||
|
#include <lib/infrared/worker/infrared_transmit.h>
|
||||||
|
#include <lib/infrared/worker/infrared_worker.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
applications/plugins/ir_remote/infrared_signal.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <lib/infrared/encoder_decoder/infrared.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
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);
|
||||||
BIN
applications/plugins/ir_remote/ir.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
applications/plugins/ir_remote/ir2.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
applications/plugins/ir_remote/ir_10px.png
Normal file
|
After Width: | Height: | Size: 305 B |
116
assets/resources/infrared/Roku.ir
Normal file
@@ -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
|
||||||
12
assets/resources/infrared/ir_remote/Roku.txt
Normal file
@@ -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
|
||||||