diff --git a/.vscode/settings.json b/.vscode/settings.json
index fc28144..24f44c6 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,6 +9,9 @@
"infrared_remote.h": "c",
"xremote_navigation_view.h": "c",
"xremote_settings_view.h": "c",
- "xremote_learn_view.h": "c"
+ "xremote_learn_view.h": "c",
+ "system_error": "c",
+ "typeinfo": "c",
+ "xremote_analyzer.h": "c"
}
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 372d8fb..39b902b 100644
--- a/README.md
+++ b/README.md
@@ -40,8 +40,8 @@ Button name | Description
## Progress
- [x] Application menu
-- [ ] Learn new remote
-- [ ] Signal analyzer
+- [x] Learn new remote
+- [x] Signal analyzer
- [x] Use saved remote
- [x] General button page
- [x] Control buttons page
@@ -49,7 +49,7 @@ Button name | Description
- [x] Player buttons page
- [ ] Custom buttons page
- [ ] Full button list
- - [ ] Edit remote file
+ - [ ] Rename remote file
- [ ] Delete remote file
- [x] Application settings
- [x] GUI to change settings
@@ -81,6 +81,17 @@ Button name | Description
+
+
+ Learn mode |
+ Received signal |
+
+
+  |
+  |
+
+
+
Settings |
diff --git a/infrared/infrared_remote.c b/infrared/infrared_remote.c
index ff0d19f..07fb7d8 100644
--- a/infrared/infrared_remote.c
+++ b/infrared/infrared_remote.c
@@ -6,6 +6,8 @@
Modifications made:
- Added function infrared_remote_get_button_by_name()
+ - Added function infrared_remote_delete_button_by_name()
+ - Added function infrared_remote_push_button()
*/
#include "infrared_remote.h"
@@ -113,6 +115,13 @@ bool infrared_remote_add_button(InfraredRemote* remote, const char* name, Infrar
return infrared_remote_store(remote);
}
+void infrared_remote_push_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);
+}
+
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);
@@ -128,6 +137,12 @@ bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) {
return infrared_remote_store(remote);
}
+bool infrared_remote_delete_button_by_name(InfraredRemote* remote, const char* name) {
+ size_t index = 0;
+ if (!infrared_remote_find_button_by_name(remote, name, &index)) return false;
+ return infrared_remote_delete_button(remote, index);
+}
+
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) {
furi_assert(index_orig < InfraredButtonArray_size(remote->buttons));
furi_assert(index_dest < InfraredButtonArray_size(remote->buttons));
diff --git a/infrared/infrared_remote.h b/infrared/infrared_remote.h
index c6d9df9..850c528 100644
--- a/infrared/infrared_remote.h
+++ b/infrared/infrared_remote.h
@@ -6,6 +6,8 @@
Modifications made:
- Added function infrared_remote_get_button_by_name()
+ - Added function infrared_remote_delete_button_by_name()
+ - Added function infrared_remote_push_button()
*/
#pragma once
@@ -32,8 +34,10 @@ bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* nam
InfraredRemoteButton* infrared_remote_get_button_by_name(InfraredRemote* remote, const char* name);
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
+void infrared_remote_push_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_delete_button_by_name(InfraredRemote* remote, const char* name);
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest);
bool infrared_remote_store(InfraredRemote* remote);
diff --git a/screens/app_menu.png b/screens/app_menu.png
index 44022d0..23791b3 100644
Binary files a/screens/app_menu.png and b/screens/app_menu.png differ
diff --git a/screens/learn_mode.png b/screens/learn_mode.png
new file mode 100644
index 0000000..0c6881d
Binary files /dev/null and b/screens/learn_mode.png differ
diff --git a/screens/saved_remote_apps.png b/screens/saved_remote_apps.png
index 1e32e5b..8acaee4 100644
Binary files a/screens/saved_remote_apps.png and b/screens/saved_remote_apps.png differ
diff --git a/screens/signal_view.png b/screens/signal_view.png
new file mode 100644
index 0000000..63ec75d
Binary files /dev/null and b/screens/signal_view.png differ
diff --git a/views/xremote_common_view.c b/views/xremote_common_view.c
index cd5e712..9b246d0 100644
--- a/views/xremote_common_view.c
+++ b/views/xremote_common_view.c
@@ -9,8 +9,50 @@
#include "xremote_common_view.h"
#include "../xremote_app.h"
+typedef struct {
+ int index;
+ const char *name;
+} XRemoteButton;
+
+static const XRemoteButton g_buttons[XREMOTE_BUTTON_COUNT + 1] =
+{
+ { 0, XREMOTE_COMMAND_POWER },
+ { 1, XREMOTE_COMMAND_SETUP },
+ { 2, XREMOTE_COMMAND_INPUT },
+ { 3, XREMOTE_COMMAND_MENU },
+ { 4, XREMOTE_COMMAND_LIST },
+ { 5, XREMOTE_COMMAND_INFO },
+ { 6, XREMOTE_COMMAND_BACK },
+ { 7, XREMOTE_COMMAND_OK },
+ { 8, XREMOTE_COMMAND_UP },
+ { 9, XREMOTE_COMMAND_DOWN },
+ { 10, XREMOTE_COMMAND_LEFT },
+ { 11, XREMOTE_COMMAND_RIGHT },
+ { 12, XREMOTE_COMMAND_JUMP_FORWARD },
+ { 13, XREMOTE_COMMAND_JUMP_BACKWARD },
+ { 14, XREMOTE_COMMAND_FAST_FORWARD },
+ { 15, XREMOTE_COMMAND_FAST_BACKWARD },
+ { 16, XREMOTE_COMMAND_PLAY_PAUSE },
+ { 17, XREMOTE_COMMAND_PAUSE },
+ { 18, XREMOTE_COMMAND_PLAY },
+ { 19, XREMOTE_COMMAND_STOP },
+ { 20, XREMOTE_COMMAND_MUTE },
+ { 21, XREMOTE_COMMAND_MODE },
+ { 22, XREMOTE_COMMAND_VOL_UP },
+ { 23, XREMOTE_COMMAND_VOL_DOWN },
+ { 24, XREMOTE_COMMAND_NEXT_CHAN },
+ { 25, XREMOTE_COMMAND_PREV_CHAN },
+ { -1, NULL }
+};
+
+const char* xremote_button_get_name(int index)
+{
+ if (index > XREMOTE_BUTTON_COUNT) return NULL;
+ return g_buttons[index].name;
+}
+
struct XRemoteView {
- XRemoteViewClearCallback on_clear;
+ XRemoteClearCallback on_clear;
XRemoteAppContext* app_ctx;
View* view;
void *context;
@@ -39,17 +81,14 @@ void xremote_view_clear_context(XRemoteView* rview)
{
furi_assert(rview);
- if (rview->context != NULL &&
- rview->on_clear != NULL)
- {
+ if (rview->context && rview->on_clear)
rview->on_clear(rview->context);
- rview->context = NULL;
- }
+
+ rview->context = NULL;
}
-void xremote_view_set_context(XRemoteView* rview, void *context, XRemoteViewClearCallback on_clear)
+void xremote_view_set_context(XRemoteView* rview, void *context, XRemoteClearCallback on_clear)
{
- furi_assert(rview);
xremote_view_clear_context(rview);
rview->context = context;
rview->on_clear = on_clear;
@@ -209,9 +248,10 @@ void xremote_canvas_draw_header(Canvas* canvas, ViewOrientation orient, const ch
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, x, 0, align, AlignTop, "XRemote");
-
canvas_set_font(canvas, FontSecondary);
- elements_multiline_text_aligned(canvas, x, 12, align, AlignTop, section);
+
+ if (section != NULL)
+ elements_multiline_text_aligned(canvas, x, 12, align, AlignTop, section);
}
void xremote_canvas_draw_exit_footer(Canvas* canvas, ViewOrientation orient, const char *text)
diff --git a/views/xremote_common_view.h b/views/xremote_common_view.h
index cc5ab0d..cb74606 100644
--- a/views/xremote_common_view.h
+++ b/views/xremote_common_view.h
@@ -20,6 +20,7 @@
#include "../infrared/infrared_remote.h"
+#define XREMOTE_BUTTON_COUNT 26
#define XREMOTE_COMMAND_POWER "Power"
#define XREMOTE_COMMAND_SETUP "Setup"
#define XREMOTE_COMMAND_INPUT "Input"
@@ -47,6 +48,18 @@
#define XREMOTE_COMMAND_NEXT_CHAN "Ch_next"
#define XREMOTE_COMMAND_PREV_CHAN "Ch_prev"
+typedef enum {
+ XRemoteEventReserved = 200,
+ XRemoteEventSignalReceived,
+ XRemoteEventSignalFinish,
+ XRemoteEventSignalSave,
+ XRemoteEventSignalRetry,
+ XRemoteEventSignalSend,
+ XRemoteEventSignalSkip,
+ XRemoteEventSignalAskExit,
+ XRemoteEventSignalExit
+} XRemoteEvent;
+
typedef enum {
/* Navigation */
XRemoteIconOk,
@@ -80,11 +93,15 @@ typedef struct {
typedef enum {
XRemoteViewNone,
+ XRemoteViewSignal,
+ XRemoteViewTextInput,
+ XRemoteViewDialogExit,
/* Main page */
XRemoteViewSubmenu,
XRemoteViewLearn,
XRemoteViewSaved,
+ XRemoteViewAnalyzer,
XRemoteViewSettings,
XRemoteViewAbout,
@@ -98,8 +115,11 @@ typedef enum {
} XRemoteViewID;
typedef struct XRemoteView XRemoteView;
-typedef void (*XRemoteViewClearCallback)(void *context);
+typedef void (*XRemoteClearCallback)(void *context);
typedef void (*XRemoteViewDrawFunction)(Canvas*, XRemoteViewModel*);
+typedef XRemoteView* (*XRemoteViewAllocator)(void* app_ctx);
+
+const char* xremote_button_get_name(int index);
void xremote_canvas_draw_header(Canvas* canvas, ViewOrientation orient, const char* section);
void xremote_canvas_draw_exit_footer(Canvas* canvas, ViewOrientation orient, const char *text);
@@ -118,7 +138,7 @@ InfraredRemoteButton* xremote_view_get_button_by_name(XRemoteView *rview, const
bool xremote_view_press_button(XRemoteView *rview, InfraredRemoteButton* button);
bool xremote_view_send_ir_msg_by_name(XRemoteView *rview, const char *name);
-void xremote_view_set_context(XRemoteView* rview, void *context, XRemoteViewClearCallback on_clear);
+void xremote_view_set_context(XRemoteView* rview, void *context, XRemoteClearCallback on_clear);
void* xremote_view_get_context(XRemoteView* rview);
void xremote_view_clear_context(XRemoteView* rview);
void* xremote_view_get_app_context(XRemoteView* rview);
diff --git a/views/xremote_learn_view.c b/views/xremote_learn_view.c
index d02eb31..0b4a992 100644
--- a/views/xremote_learn_view.c
+++ b/views/xremote_learn_view.c
@@ -7,32 +7,242 @@
*/
#include "xremote_learn_view.h"
+#include "../xremote_learn.h"
#include "../xremote_app.h"
static void xremote_learn_view_draw_callback(Canvas* canvas, void* context)
{
furi_assert(context);
XRemoteViewModel* model = context;
- XRemoteAppContext *app_ctx = model->context;
+ XRemoteLearnContext* learn_ctx = model->context;
- ViewOrientation orientation = app_ctx->app_settings->orientation;
- uint64_t x = orientation == ViewOrientationVertical ? 70 : 34;
+ XRemoteAppContext* app_ctx = xremote_learn_get_app_context(learn_ctx);
+ const char *button_name = xremote_learn_get_curr_button_name(learn_ctx);
+ ViewOrientation orientation = app_ctx->app_settings->orientation;
xremote_canvas_draw_header(canvas, orientation, "Learn");
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str(canvas, 0, x, "Coming Soon.");
- xremote_canvas_draw_exit_footer(canvas, orientation, "Press to exit");
+
+ char info_text[128];
+ snprintf(info_text, sizeof(info_text),
+ "Press\n\"%s\"\nbutton on\nthe remote.",
+ button_name != NULL ? button_name : "");
+
+ if (orientation == ViewOrientationHorizontal)
+ {
+ elements_multiline_text_aligned(canvas, 0, 12, AlignLeft, AlignTop, info_text);
+ xremote_canvas_draw_button_wide(canvas, model->ok_pressed, 68, 22, "Finish", XRemoteIconEnter);
+ xremote_canvas_draw_button_wide(canvas, model->right_pressed, 68, 40, "Skip", XRemoteIconArrowRight);
+ }
+ else
+ {
+ elements_multiline_text_aligned(canvas, 0, 30, AlignLeft, AlignTop, info_text);
+ xremote_canvas_draw_button_wide(canvas, model->ok_pressed, 0, 82, "Finish", XRemoteIconEnter);
+ xremote_canvas_draw_button_wide(canvas, model->right_pressed, 0, 100, "Skip", XRemoteIconArrowRight);
+ }
+
+ const char *exit_str = xremote_app_context_get_exit_str(app_ctx);
+ xremote_canvas_draw_exit_footer(canvas, orientation, exit_str);
+}
+
+static void xremote_learn_success_view_draw_callback(Canvas* canvas, void* context)
+{
+ furi_assert(context);
+ XRemoteViewModel* model = context;
+ XRemoteLearnContext* learn_ctx = model->context;
+
+ XRemoteAppContext* app_ctx = xremote_learn_get_app_context(learn_ctx);
+ InfraredSignal *ir_signal = xremote_learn_get_ir_signal(learn_ctx);
+
+ xremote_canvas_draw_header(canvas, app_ctx->app_settings->orientation, NULL);
+ const char *button_name = xremote_learn_get_curr_button_name(learn_ctx);
+ char signal_info[128];
+
+ if (infrared_signal_is_raw(ir_signal))
+ {
+ InfraredRawSignal* raw = infrared_signal_get_raw_signal(ir_signal);
+
+ snprintf(signal_info, sizeof(signal_info),
+ "Name: %s\n"
+ "Type: RAW\n"
+ "T-Size: %u\n"
+ "D-Cycle: %.2f\n",
+ button_name,
+ raw->timings_size,
+ (double)raw->duty_cycle);
+ }
+ else
+ {
+ InfraredMessage* message = infrared_signal_get_message(ir_signal);
+ const char *infrared_protocol = infrared_get_protocol_name(message->protocol);
+
+ snprintf(signal_info, sizeof(signal_info),
+ "Name: %s\n"
+ "Proto: %s\n"
+ "Addr: 0x%lX\n"
+ "Cmd: 0x%lX\n",
+ button_name,
+ infrared_protocol,
+ message->address,
+ message->command);
+ }
+
+ if (app_ctx->app_settings->orientation == ViewOrientationHorizontal)
+ {
+ canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Received signal");
+ elements_multiline_text_aligned(canvas, 0, 16, AlignLeft, AlignTop, signal_info);
+ xremote_canvas_draw_button_wide(canvas, model->ok_pressed, 68, 12, "Finish", XRemoteIconEnter);
+ xremote_canvas_draw_button_wide(canvas, model->right_pressed, 68, 30, "Next", XRemoteIconArrowRight);
+ xremote_canvas_draw_button_wide(canvas, model->back_pressed, 68, 48, "Retry", XRemoteIconBack);
+ }
+ else
+ {
+ canvas_draw_str_aligned(canvas, 0, 12, AlignLeft, AlignTop, "Received signal");
+ elements_multiline_text_aligned(canvas, 0, 27, AlignLeft, AlignTop, signal_info);
+ xremote_canvas_draw_button_wide(canvas, model->ok_pressed, 0, 76, "Finish", XRemoteIconEnter);
+ xremote_canvas_draw_button_wide(canvas, model->right_pressed, 0, 94, "Next", XRemoteIconArrowRight);
+ xremote_canvas_draw_button_wide(canvas, model->back_pressed, 0, 112, "Retry", XRemoteIconBack);
+ }
+}
+
+static void xremote_learn_success_view_process(XRemoteView* view, InputEvent* event)
+{
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ XRemoteLearnContext *learn_ctx = xremote_view_get_context(view);
+ model->context = learn_ctx;
+
+ if (event->type == InputTypePress)
+ {
+ if (event->key == InputKeyOk)
+ {
+ model->ok_pressed = true;
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalFinish);
+ }
+ else if (event->key == InputKeyBack)
+ {
+ model->back_pressed = true;
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalRetry);
+ }
+ else if (event->key == InputKeyRight)
+ {
+ model->right_pressed = true;
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalSave);
+ }
+ }
+ else if (event->type == InputTypeRelease)
+ {
+ if (event->key == InputKeyOk) model->ok_pressed = false;
+ else if (event->key == InputKeyBack) model->back_pressed = false;
+ else if (event->key == InputKeyRight) model->right_pressed = false;
+ }
+ },
+ true
+ );
+}
+
+static void xremote_learn_view_process(XRemoteView* view, InputEvent* event)
+{
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ XRemoteLearnContext *learn_ctx = xremote_view_get_context(view);
+ XRemoteAppContext *app_ctx = xremote_view_get_app_context(view);
+
+ XRemoteAppExit exit = app_ctx->app_settings->exit_behavior;
+ model->context = learn_ctx;
+
+ if (event->type == InputTypePress)
+ {
+ if (event->key == InputKeyOk && xremote_learn_has_buttons(learn_ctx))
+ {
+ model->ok_pressed = true;
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalFinish);
+ }
+ else if (event->key == InputKeyRight)
+ {
+ model->right_pressed = true;
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalSkip);
+ }
+ }
+ else if ((event->type == InputTypeShort ||
+ event->type == InputTypeLong) &&
+ event->key == InputKeyBack)
+ {
+ if ((event->type == InputTypeShort && exit == XRemoteAppExitPress) ||
+ (event->type == InputTypeLong && exit == XRemoteAppExitHold))
+ {
+ model->back_pressed = true;
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalAskExit);
+ }
+ }
+ else if (event->type == InputTypeRelease)
+ {
+ if (event->key == InputKeyOk) model->ok_pressed = false;
+ else if (event->key == InputKeyBack) model->back_pressed = false;
+ else if (event->key == InputKeyRight) model->right_pressed = false;
+ }
+ },
+ true
+ );
+}
+
+static bool xremote_learn_success_view_input_callback(InputEvent* event, void* context)
+{
+ furi_assert(context);
+ XRemoteView* view = (XRemoteView*)context;
+ xremote_learn_success_view_process(view, event);
+ return true;
+}
+
+static bool xremote_learn_view_input_callback(InputEvent* event, void* context)
+{
+ furi_assert(context);
+ XRemoteView* view = (XRemoteView*)context;
+ xremote_learn_view_process(view, event);
+ return true;
+}
+
+XRemoteView* xremote_learn_success_view_alloc(void* app_ctx, void *learn_ctx)
+{
+ XRemoteView *view = xremote_view_alloc(app_ctx,
+ xremote_learn_success_view_input_callback,
+ xremote_learn_success_view_draw_callback);
+ xremote_view_set_context(view, learn_ctx, NULL);
+
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ model->context = learn_ctx;
+ model->right_pressed = false;
+ model->back_pressed = false;
+ model->ok_pressed = false;
+ },
+ true
+ );
+
+ return view;
}
-XRemoteView* xremote_learn_view_alloc(void* app_ctx)
+XRemoteView* xremote_learn_view_alloc(void* app_ctx, void *learn_ctx)
{
XRemoteView *view = xremote_view_alloc(app_ctx,
- NULL, xremote_learn_view_draw_callback);
+ xremote_learn_view_input_callback,
+ xremote_learn_view_draw_callback);
+ xremote_view_set_context(view, learn_ctx, NULL);
with_view_model(
xremote_view_get_view(view),
XRemoteViewModel* model,
- { model->context = app_ctx; },
+ {
+ model->context = learn_ctx;
+ model->right_pressed = false;
+ model->back_pressed = false;
+ model->ok_pressed = false;
+ },
true
);
diff --git a/views/xremote_learn_view.h b/views/xremote_learn_view.h
index 40e8538..ef9c259 100644
--- a/views/xremote_learn_view.h
+++ b/views/xremote_learn_view.h
@@ -10,4 +10,5 @@
#include "xremote_common_view.h"
-XRemoteView* xremote_learn_view_alloc(void* app_ctx);
+XRemoteView* xremote_learn_view_alloc(void* app_ctx, void *learn_ctx);
+XRemoteView* xremote_learn_success_view_alloc(void* app_ctx, void *rx_ctx);
diff --git a/views/xremote_player_view.c b/views/xremote_player_view.c
index bc8cd23..6dbf3d2 100644
--- a/views/xremote_player_view.c
+++ b/views/xremote_player_view.c
@@ -17,8 +17,8 @@ static void xremote_player_view_draw_vertical(Canvas* canvas, XRemoteViewModel*
xremote_canvas_draw_button(canvas, model->down_pressed, 23, 72, XRemoteIconJumpBackward);
xremote_canvas_draw_button(canvas, model->left_pressed, 2, 51, XRemoteIconFastBackward);
xremote_canvas_draw_button(canvas, model->right_pressed, 44, 51, XRemoteIconFastForward);
- xremote_canvas_draw_button(canvas, model->back_pressed, 2, 95, XRemoteIconPause);
- xremote_canvas_draw_button(canvas, model->ok_pressed, 23, 51, XRemoteIconPlay);
+ xremote_canvas_draw_button(canvas, model->back_pressed, 2, 95, XRemoteIconPlay);
+ xremote_canvas_draw_button(canvas, model->ok_pressed, 23, 51, XRemoteIconPause);
if (app_ctx->app_settings->exit_behavior == XRemoteAppExitPress)
canvas_draw_icon(canvas, 22, 107, &I_Hold_Text_17x4);
@@ -32,8 +32,8 @@ static void xremote_player_view_draw_horizontal(Canvas* canvas, XRemoteViewModel
xremote_canvas_draw_button(canvas, model->down_pressed, 23, 44, XRemoteIconJumpBackward);
xremote_canvas_draw_button(canvas, model->left_pressed, 2, 23, XRemoteIconFastBackward);
xremote_canvas_draw_button(canvas, model->right_pressed, 44, 23, XRemoteIconFastForward);
- xremote_canvas_draw_button(canvas, model->back_pressed, 70, 33, XRemoteIconPause);
- xremote_canvas_draw_button(canvas, model->ok_pressed, 23, 23, XRemoteIconPlay);
+ xremote_canvas_draw_button(canvas, model->back_pressed, 70, 33, XRemoteIconPlay);
+ xremote_canvas_draw_button(canvas, model->ok_pressed, 23, 23, XRemoteIconPause);
if (app_ctx->app_settings->exit_behavior == XRemoteAppExitPress)
canvas_draw_icon(canvas, 90, 45, &I_Hold_Text_17x4);
@@ -91,7 +91,7 @@ static void xremote_player_view_process(XRemoteView* view, InputEvent* event)
}
else if (event->key == InputKeyOk)
{
- button = xremote_view_get_button_by_name(view, XREMOTE_COMMAND_PLAY);
+ button = xremote_view_get_button_by_name(view, XREMOTE_COMMAND_PAUSE);
if (xremote_view_press_button(view, button)) model->ok_pressed = true;
}
}
@@ -99,14 +99,14 @@ static void xremote_player_view_process(XRemoteView* view, InputEvent* event)
event->key == InputKeyBack &&
exit == XRemoteAppExitHold)
{
- button = xremote_view_get_button_by_name(view, XREMOTE_COMMAND_PAUSE);
+ button = xremote_view_get_button_by_name(view, XREMOTE_COMMAND_PLAY);
if (xremote_view_press_button(view, button)) model->back_pressed = true;
}
else if (event->type == InputTypeLong &&
event->key == InputKeyBack &&
exit == XRemoteAppExitPress)
{
- button = xremote_view_get_button_by_name(view, XREMOTE_COMMAND_PAUSE);
+ button = xremote_view_get_button_by_name(view, XREMOTE_COMMAND_PLAY);
if (xremote_view_press_button(view, button)) model->back_pressed = true;
}
else if (event->type == InputTypeRelease)
diff --git a/views/xremote_signal_view.c b/views/xremote_signal_view.c
new file mode 100644
index 0000000..fcc00fb
--- /dev/null
+++ b/views/xremote_signal_view.c
@@ -0,0 +1,203 @@
+/*!
+ * @file flipper-xremote/views/xremote_signal_view.c
+ @license This project is released under the GNU GPLv3 License
+ * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
+ *
+ * @brief Signal analyzer page view components and functionality.
+ */
+
+#include "xremote_signal_view.h"
+#include "../xremote_analyzer.h"
+#include "../xremote_app.h"
+
+static void xremote_signal_view_draw_callback(Canvas* canvas, void* context)
+{
+ furi_assert(context);
+ XRemoteViewModel* model = context;
+ XRemoteSignalAnalyzer* analyzer = model->context;
+ XRemoteAppContext* app_ctx = xremote_signal_analyzer_get_app_context(analyzer);
+
+ ViewOrientation orientation = app_ctx->app_settings->orientation;
+ uint8_t y = orientation == ViewOrientationHorizontal ? 17 : 49;
+ const char *text = "Press any\nbutton on\nthe remote.";
+
+ xremote_canvas_draw_header(canvas, orientation, "Analyzer");
+ elements_multiline_text_aligned(canvas, 0, y, AlignLeft, AlignTop, text);
+
+ const char *exit_str = xremote_app_context_get_exit_str(app_ctx);
+ xremote_canvas_draw_exit_footer(canvas, orientation, exit_str);
+}
+
+static void xremote_signal_success_view_draw_callback(Canvas* canvas, void* context)
+{
+ furi_assert(context);
+ XRemoteViewModel* model = context;
+ XRemoteSignalAnalyzer* analyzer = model->context;
+
+ XRemoteAppContext* app_ctx = xremote_signal_analyzer_get_app_context(analyzer);
+ InfraredSignal *ir_signal = xremote_signal_analyzer_get_ir_signal(analyzer);
+
+ xremote_canvas_draw_header(canvas, app_ctx->app_settings->orientation, "IR Signal");
+ char signal_info[128];
+
+ if (infrared_signal_is_raw(ir_signal))
+ {
+ InfraredRawSignal* raw = infrared_signal_get_raw_signal(ir_signal);
+
+ snprintf(signal_info, sizeof(signal_info),
+ "Type: RAW\n"
+ "T-Size: %u\n"
+ "D-Cycle: %.2f\n",
+ raw->timings_size,
+ (double)raw->duty_cycle);
+ }
+ else
+ {
+ InfraredMessage* message = infrared_signal_get_message(ir_signal);
+ const char *infrared_protocol = infrared_get_protocol_name(message->protocol);
+
+ snprintf(signal_info, sizeof(signal_info),
+ "Proto: %s\n"
+ "Addr: 0x%lX\n"
+ "Cmd: 0x%lX\n",
+ infrared_protocol,
+ message->address,
+ message->command);
+ }
+
+ if (app_ctx->app_settings->orientation == ViewOrientationHorizontal)
+ {
+ elements_multiline_text_aligned(canvas, 0, 17, AlignLeft, AlignTop, signal_info);
+ xremote_canvas_draw_button_wide(canvas, model->ok_pressed, 68, 26, "Send", XRemoteIconEnter);
+ xremote_canvas_draw_button_wide(canvas, model->back_pressed, 68, 44, "Retry", XRemoteIconBack);
+ }
+ else
+ {
+ elements_multiline_text_aligned(canvas, 0, 39, AlignLeft, AlignTop, signal_info);
+ xremote_canvas_draw_button_wide(canvas, model->ok_pressed, 0, 88, "Send", XRemoteIconEnter);
+ xremote_canvas_draw_button_wide(canvas, model->back_pressed, 0, 106, "Retry", XRemoteIconBack);
+ }
+}
+
+static void xremote_signal_success_view_process(XRemoteView* view, InputEvent* event)
+{
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ XRemoteSignalAnalyzer *analyzer = xremote_view_get_context(view);
+ model->context = analyzer;
+
+ if (event->type == InputTypePress)
+ {
+ if (event->key == InputKeyOk)
+ {
+ model->ok_pressed = true;
+ xremote_signal_analyzer_send_event(analyzer, XRemoteEventSignalSend);
+ }
+ else if (event->key == InputKeyBack)
+ {
+ model->back_pressed = true;
+ xremote_signal_analyzer_send_event(analyzer, XRemoteEventSignalRetry);
+ }
+ }
+ else if (event->type == InputTypeRelease)
+ {
+ if (event->key == InputKeyOk) model->ok_pressed = false;
+ else if (event->key == InputKeyBack) model->back_pressed = false;
+ }
+ },
+ true
+ );
+}
+
+static void xremote_signal_view_process(XRemoteView* view, InputEvent* event)
+{
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ XRemoteSignalAnalyzer *analyzer = xremote_view_get_context(view);
+ XRemoteAppContext *app_ctx = xremote_view_get_app_context(view);
+
+ XRemoteAppExit exit = app_ctx->app_settings->exit_behavior;
+ model->context = analyzer;
+
+ if ((event->type == InputTypeShort ||
+ event->type == InputTypeLong) &&
+ event->key == InputKeyBack)
+ {
+ if ((event->type == InputTypeShort && exit == XRemoteAppExitPress) ||
+ (event->type == InputTypeLong && exit == XRemoteAppExitHold))
+ {
+ model->back_pressed = true;
+ xremote_signal_analyzer_send_event(analyzer, XRemoteEventSignalExit);
+ }
+ }
+ else if (event->type == InputTypeRelease)
+ {
+ if (event->key == InputKeyOk) model->ok_pressed = false;
+ else if (event->key == InputKeyBack) model->back_pressed = false;
+ else if (event->key == InputKeyRight) model->right_pressed = false;
+ }
+ },
+ true
+ );
+}
+
+static bool xremote_signal_success_view_input_callback(InputEvent* event, void* context)
+{
+ furi_assert(context);
+ XRemoteView* view = (XRemoteView*)context;
+ xremote_signal_success_view_process(view, event);
+ return true;
+}
+
+static bool xremote_signal_view_input_callback(InputEvent* event, void* context)
+{
+ furi_assert(context);
+ XRemoteView* view = (XRemoteView*)context;
+ xremote_signal_view_process(view, event);
+ return true;
+}
+
+XRemoteView* xremote_signal_success_view_alloc(void* app_ctx, void *analyzer)
+{
+ XRemoteView *view = xremote_view_alloc(app_ctx,
+ xremote_signal_success_view_input_callback,
+ xremote_signal_success_view_draw_callback);
+ xremote_view_set_context(view, analyzer, NULL);
+
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ model->context = analyzer;
+ model->back_pressed = false;
+ model->ok_pressed = false;
+ },
+ true
+ );
+
+ return view;
+}
+
+XRemoteView* xremote_signal_view_alloc(void* app_ctx, void *analyzer)
+{
+ XRemoteView *view = xremote_view_alloc(app_ctx,
+ xremote_signal_view_input_callback,
+ xremote_signal_view_draw_callback);
+ xremote_view_set_context(view, analyzer, NULL);
+
+ with_view_model(
+ xremote_view_get_view(view),
+ XRemoteViewModel* model,
+ {
+ model->context = analyzer;
+ model->back_pressed = false;
+ },
+ true
+ );
+
+ return view;
+}
diff --git a/views/xremote_signal_view.h b/views/xremote_signal_view.h
new file mode 100644
index 0000000..6b49110
--- /dev/null
+++ b/views/xremote_signal_view.h
@@ -0,0 +1,14 @@
+/*!
+ * @file flipper-xremote/views/xremote_signal_view.h
+ @license This project is released under the GNU GPLv3 License
+ * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
+ *
+ * @brief Signal analyzer page view components and functionality.
+ */
+
+#pragma once
+
+#include "xremote_common_view.h"
+
+XRemoteView* xremote_signal_view_alloc(void* app_ctx, void *learn_ctx);
+XRemoteView* xremote_signal_success_view_alloc(void* app_ctx, void *rx_ctx);
diff --git a/xremote.c b/xremote.c
index df9544d..13e3ae0 100644
--- a/xremote.c
+++ b/xremote.c
@@ -10,9 +10,11 @@
#include "xremote_learn.h"
#include "xremote_control.h"
#include "xremote_settings.h"
+#include "xremote_analyzer.h"
#include "views/xremote_about_view.h"
#include "views/xremote_learn_view.h"
+#include "views/xremote_signal_view.h"
#define TAG "XRemote"
@@ -64,6 +66,8 @@ void xremote_submenu_callback(void* context, uint32_t index)
child = xremote_learn_alloc(app->app_ctx);
else if (index == XRemoteViewIRSubmenu)
child = xremote_control_alloc(app->app_ctx);
+ else if (index == XRemoteViewAnalyzer)
+ child = xremote_analyzer_alloc(app->app_ctx);
else if (index == XRemoteViewSettings)
child = xremote_settings_alloc(app->app_ctx);
else if (index == XRemoteViewAbout)
@@ -87,6 +91,7 @@ int32_t xremote_main(void* p)
xremote_app_submenu_alloc(app, XRemoteViewSubmenu, xremote_exit_callback);
xremote_app_submenu_add(app, "Learn", XRemoteViewLearn, xremote_submenu_callback);
xremote_app_submenu_add(app, "Saved", XRemoteViewIRSubmenu, xremote_submenu_callback);
+ xremote_app_submenu_add(app, "Analyzer", XRemoteViewAnalyzer, xremote_submenu_callback);
xremote_app_submenu_add(app, "Settings", XRemoteViewSettings, xremote_submenu_callback);
xremote_app_submenu_add(app, "About", XRemoteViewAbout, xremote_submenu_callback);
diff --git a/xremote.h b/xremote.h
index 11e92e8..74df08f 100644
--- a/xremote.h
+++ b/xremote.h
@@ -8,8 +8,8 @@
#include "xremote_app.h"
-#define XREMOTE_VERSION_MAJOR 0
-#define XREMOTE_VERSION_MINOR 9
-#define XREMOTE_BUILD_NUMBER 26
+#define XREMOTE_VERSION_MAJOR 1
+#define XREMOTE_VERSION_MINOR 0
+#define XREMOTE_BUILD_NUMBER 1
void xremote_get_version(char *version, size_t length);
diff --git a/xremote_analyzer.c b/xremote_analyzer.c
new file mode 100644
index 0000000..4dafa58
--- /dev/null
+++ b/xremote_analyzer.c
@@ -0,0 +1,177 @@
+/*!
+ * @file flipper-xremote/xremote_analyzer.c
+ @license This project is released under the GNU GPLv3 License
+ * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
+ *
+ * @brief Infrared remote singnal analyzer and custom view events.
+ */
+
+#include "xremote_analyzer.h"
+#include "views/xremote_signal_view.h"
+
+#define XREMOTE_TEXT_MAX 128
+
+struct XRemoteSignalAnalyzer {
+ XRemoteSignalReceiver* ir_receiver;
+ XRemoteAppContext* app_ctx;
+ XRemoteView* signal_view;
+ InfraredSignal* ir_signal;
+ XRemoteClearCallback on_clear;
+ void* context;
+ bool pause;
+};
+
+InfraredSignal* xremote_signal_analyzer_get_ir_signal(XRemoteSignalAnalyzer *analyzer)
+{
+ xremote_app_assert(analyzer, NULL);
+ return analyzer->ir_signal;
+}
+
+XRemoteSignalReceiver* xremote_signal_analyzer_get_ir_receiver(XRemoteSignalAnalyzer *analyzer)
+{
+ xremote_app_assert(analyzer, NULL);
+ return analyzer->ir_receiver;
+}
+
+XRemoteAppContext* xremote_signal_analyzer_get_app_context(XRemoteSignalAnalyzer *analyzer)
+{
+ xremote_app_assert(analyzer, NULL);
+ return analyzer->app_ctx;
+}
+
+void xremote_signal_analyzer_send_event(XRemoteSignalAnalyzer* analyzer, XRemoteEvent event)
+{
+ xremote_app_assert_void(analyzer);
+ ViewDispatcher* view_disp = analyzer->app_ctx->view_dispatcher;
+ view_dispatcher_send_custom_event(view_disp, event);
+}
+
+static void xremote_signal_analyzer_switch_to_view(XRemoteSignalAnalyzer* analyzer, XRemoteViewID view_id)
+{
+ xremote_app_assert_void(analyzer);
+ ViewDispatcher* view_disp = analyzer->app_ctx->view_dispatcher;
+ view_dispatcher_switch_to_view(view_disp, view_id);
+}
+
+static void xremote_signal_analyzer_rx_stop(XRemoteSignalAnalyzer *analyzer)
+{
+ xremote_app_assert_void(analyzer);
+ analyzer->pause = true;
+ xremote_signal_receiver_stop(analyzer->ir_receiver);
+}
+
+static void xremote_signal_analyzer_rx_start(XRemoteSignalAnalyzer *analyzer)
+{
+ xremote_app_assert_void(analyzer);
+ analyzer->pause = false;
+ xremote_signal_receiver_start(analyzer->ir_receiver);
+}
+
+static uint32_t xremote_signal_analyzer_view_exit_callback(void* context)
+{
+ UNUSED(context);
+ return XRemoteViewAnalyzer;
+}
+
+static void xremote_signal_analyzer_signal_callback(void *context, InfraredSignal* signal)
+{
+ XRemoteSignalAnalyzer* analyzer = context;
+ xremote_app_assert_void(!analyzer->pause);
+ analyzer->pause = true;
+
+ infrared_signal_set_signal(analyzer->ir_signal, signal);
+ xremote_signal_analyzer_send_event(analyzer, XRemoteEventSignalReceived);
+}
+
+static bool xremote_signal_analyzer_custom_event_callback(void* context, uint32_t event)
+{
+ xremote_app_assert(context, false);
+ XRemoteSignalAnalyzer *analyzer = context;
+
+ if (event == XRemoteEventSignalExit)
+ {
+ xremote_signal_analyzer_rx_stop(analyzer);
+ xremote_signal_analyzer_switch_to_view(analyzer, XRemoteViewSubmenu);
+ }
+ else if (event == XRemoteEventSignalReceived)
+ {
+ xremote_signal_analyzer_rx_stop(analyzer);
+ xremote_signal_analyzer_switch_to_view(analyzer, XRemoteViewSignal);
+ }
+ else if (event == XRemoteEventSignalRetry)
+ {
+ xremote_signal_analyzer_rx_start(analyzer);
+ xremote_signal_analyzer_switch_to_view(analyzer, XRemoteViewAnalyzer);
+ }
+ else if (event == XRemoteEventSignalSend)
+ {
+ XRemoteAppContext* app_ctx = analyzer->app_ctx;
+ xremote_app_send_signal(app_ctx, analyzer->ir_signal);
+ }
+
+ return true;
+}
+
+static XRemoteSignalAnalyzer* xremote_signal_analyzer_alloc(XRemoteAppContext* app_ctx)
+{
+ XRemoteSignalAnalyzer *analyzer = malloc(sizeof(XRemoteSignalAnalyzer));
+ analyzer->ir_signal = infrared_signal_alloc();
+ analyzer->app_ctx = app_ctx;
+ analyzer->pause = false;
+
+ analyzer->signal_view = xremote_signal_success_view_alloc(app_ctx, analyzer);
+ View* view = xremote_view_get_view(analyzer->signal_view);
+ view_set_previous_callback(view, xremote_signal_analyzer_view_exit_callback);
+ view_dispatcher_add_view(app_ctx->view_dispatcher, XRemoteViewSignal, view);
+
+ view_dispatcher_set_custom_event_callback(app_ctx->view_dispatcher, xremote_signal_analyzer_custom_event_callback);
+ view_dispatcher_set_event_callback_context(app_ctx->view_dispatcher, analyzer);
+
+ analyzer->ir_receiver = xremote_signal_receiver_alloc(app_ctx);
+ xremote_signal_receiver_set_context(analyzer->ir_receiver, analyzer, NULL);
+ xremote_signal_receiver_set_rx_callback(analyzer->ir_receiver, xremote_signal_analyzer_signal_callback);
+
+ return analyzer;
+}
+
+static void xremote_signal_analyzer_free(XRemoteSignalAnalyzer* analyzer)
+{
+ xremote_app_assert_void(analyzer);
+ xremote_signal_receiver_stop(analyzer->ir_receiver);
+
+ ViewDispatcher* view_disp = analyzer->app_ctx->view_dispatcher;
+ view_dispatcher_set_custom_event_callback(view_disp, NULL);
+ view_dispatcher_set_event_callback_context(view_disp, NULL);
+
+ view_dispatcher_remove_view(view_disp, XRemoteViewSignal);
+ xremote_view_free(analyzer->signal_view);
+
+ xremote_signal_receiver_free(analyzer->ir_receiver);
+ infrared_signal_free(analyzer->ir_signal);
+ free(analyzer);
+}
+
+static void xremote_signal_analyzer_clear_callback(void* context)
+{
+ XRemoteSignalAnalyzer *analyzer = context;
+ xremote_signal_analyzer_free(analyzer);
+}
+
+XRemoteApp* xremote_analyzer_alloc(XRemoteAppContext* app_ctx)
+{
+ XRemoteApp* app = xremote_app_alloc(app_ctx);
+ app->view_id = XRemoteViewAnalyzer;
+
+ XRemoteSignalAnalyzer* analyzer = xremote_signal_analyzer_alloc(app_ctx);
+ app->view_ctx = xremote_signal_view_alloc(app->app_ctx, analyzer);
+ View* view = xremote_view_get_view(app->view_ctx);
+
+ ViewDispatcher* view_disp = app_ctx->view_dispatcher;
+ view_dispatcher_add_view(view_disp, app->view_id, view);
+
+ xremote_app_view_set_previous_callback(app, xremote_signal_analyzer_view_exit_callback);
+ xremote_app_set_view_context(app, analyzer, xremote_signal_analyzer_clear_callback);
+
+ xremote_signal_receiver_start(analyzer->ir_receiver);
+ return app;
+}
diff --git a/xremote_analyzer.h b/xremote_analyzer.h
new file mode 100644
index 0000000..d6f5efe
--- /dev/null
+++ b/xremote_analyzer.h
@@ -0,0 +1,21 @@
+/*!
+ * @file flipper-xremote/xremote_analyzer.h
+ @license This project is released under the GNU GPLv3 License
+ * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
+ *
+ * @brief Infrared remote singnal analyzer and custom view events.
+ */
+
+#pragma once
+
+#include "xremote_app.h"
+#include "xremote_signal.h"
+
+typedef struct XRemoteSignalAnalyzer XRemoteSignalAnalyzer;
+
+void xremote_signal_analyzer_send_event(XRemoteSignalAnalyzer* analyzer, XRemoteEvent event);
+XRemoteSignalReceiver* xremote_signal_analyzer_get_ir_receiver(XRemoteSignalAnalyzer *analyzer);
+XRemoteAppContext* xremote_signal_analyzer_get_app_context(XRemoteSignalAnalyzer *analyzer);
+InfraredSignal* xremote_signal_analyzer_get_ir_signal(XRemoteSignalAnalyzer *analyzer);
+
+XRemoteApp* xremote_analyzer_alloc(XRemoteAppContext* app_ctx);
diff --git a/xremote_app.c b/xremote_app.c
index 3f0a689..39ef6fc 100644
--- a/xremote_app.c
+++ b/xremote_app.c
@@ -115,6 +115,7 @@ XRemoteAppContext* xremote_app_context_alloc(void *arg)
ctx->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(ctx->view_dispatcher);
view_dispatcher_attach_to_gui(ctx->view_dispatcher, ctx->gui, ViewDispatcherTypeFullscreen);
+
return ctx;
}
@@ -144,11 +145,25 @@ const char* xremote_app_context_get_exit_str(XRemoteAppContext* ctx)
return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";
}
+void xremote_app_notification_blink(NotificationApp* notifications)
+{
+ xremote_app_assert_void(notifications);
+ notification_message(notifications, &g_sequence_blink_purple_50);
+}
+
void xremote_app_context_notify_led(XRemoteAppContext* app_ctx)
{
xremote_app_assert_void(app_ctx);
- NotificationApp* notifications = app_ctx->notifications;
- notification_message(notifications, &g_sequence_blink_purple_50);
+ xremote_app_notification_blink(app_ctx->notifications);
+}
+
+bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal)
+{
+ xremote_app_assert(signal, false);
+ XRemoteAppSettings* settings = app_ctx->app_settings;
+ infrared_signal_transmit_times(signal, settings->repeat_count);
+ xremote_app_context_notify_led(app_ctx);
+ return true;
}
void xremote_app_view_alloc(XRemoteApp *app, uint32_t view_id, XRemoteViewAllocator allocator)
@@ -259,14 +274,14 @@ void xremote_app_view_set_previous_callback(XRemoteApp* app, ViewNavigationCallb
view_set_previous_callback(view, callback);
}
-void xremote_app_set_view_context(XRemoteApp* app, void *context, XRemoteViewClearCallback on_clear)
+void xremote_app_set_view_context(XRemoteApp* app, void *context, XRemoteClearCallback on_clear)
{
furi_assert(app);
xremote_app_assert_void(app->view_ctx);
xremote_view_set_context(app->view_ctx, context, on_clear);
}
-void xremote_app_set_user_context(XRemoteApp* app, void *context, XRemoteAppClearCallback on_clear)
+void xremote_app_set_user_context(XRemoteApp* app, void *context, XRemoteClearCallback on_clear)
{
furi_assert(app);
app->on_clear = on_clear;
diff --git a/xremote_app.h b/xremote_app.h
index 08cd19b..3dbd390 100644
--- a/xremote_app.h
+++ b/xremote_app.h
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
@@ -22,6 +23,7 @@
#include
#include
#include
+#include
#include "views/xremote_common_view.h"
#include "xc_icons.h"
@@ -63,12 +65,11 @@ void xremote_app_context_free(XRemoteAppContext* ctx);
const char* xremote_app_context_get_exit_str(XRemoteAppContext* ctx);
void xremote_app_context_notify_led(XRemoteAppContext* app_ctx);
-
-typedef XRemoteView* (*XRemoteViewAllocator)(void* app_ctx);
-typedef void (*XRemoteAppClearCallback)(void *context);
+void xremote_app_notification_blink(NotificationApp* notifications);
+bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal);
typedef struct {
- XRemoteAppClearCallback on_clear;
+ XRemoteClearCallback on_clear;
XRemoteAppContext* app_ctx;
XRemoteViewID submenu_id;
XRemoteViewID view_id;
@@ -85,8 +86,8 @@ void xremote_app_view_alloc(XRemoteApp *app, uint32_t view_id, XRemoteViewAlloca
void xremote_app_view_free(XRemoteApp* app);
void xremote_app_view_set_previous_callback(XRemoteApp* app, ViewNavigationCallback callback);
-void xremote_app_set_view_context(XRemoteApp* app, void *context, XRemoteViewClearCallback on_clear);
-void xremote_app_set_user_context(XRemoteApp* app, void *context, XRemoteAppClearCallback on_clear);
+void xremote_app_set_view_context(XRemoteApp* app, void *context, XRemoteClearCallback on_clear);
+void xremote_app_set_user_context(XRemoteApp* app, void *context, XRemoteClearCallback on_clear);
void xremote_app_user_context_free(XRemoteApp* app);
bool xremote_app_has_view(XRemoteApp *app, uint32_t view_id);
diff --git a/xremote_learn.c b/xremote_learn.c
index 9846af5..6246419 100644
--- a/xremote_learn.c
+++ b/xremote_learn.c
@@ -9,16 +9,436 @@
#include "xremote_learn.h"
#include "views/xremote_learn_view.h"
+#define XREMOTE_TEXT_MAX 128
+
+struct XRemoteLearnContext {
+ /* XRemote context */
+ XRemoteSignalReceiver* ir_receiver;
+ XRemoteAppContext* app_ctx;
+ XRemoteView* signal_view;
+ XRemoteViewID curr_view;
+ XRemoteViewID prev_view;
+
+ /* Main infrared app context */
+ InfraredRemote* ir_remote;
+ InfraredSignal* ir_signal;
+
+ /* User interactions */
+ TextInput* text_input;
+ DialogEx* dialog_ex;
+
+ /* User context and clear callback */
+ XRemoteClearCallback on_clear;
+ void* context;
+
+ /* Private control flags */
+ char text_store[XREMOTE_TEXT_MAX + 1];
+ uint8_t current_button;
+ bool finish_learning;
+ bool stop_receiver;
+ bool is_dirty;
+};
+
+void xremote_learn_send_event(XRemoteLearnContext* learn_ctx, XRemoteEvent event)
+{
+ xremote_app_assert_void(learn_ctx);
+ if (event == XRemoteEventSignalFinish) learn_ctx->finish_learning = true;
+ ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
+ view_dispatcher_send_custom_event(view_disp, event);
+}
+
+const char* xremote_learn_get_curr_button_name(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, NULL);
+ return xremote_button_get_name(learn_ctx->current_button);
+}
+
+int xremote_learn_get_curr_button_index(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, -1);
+ return learn_ctx->current_button;
+}
+
+InfraredRemote* xremote_learn_get_ir_remote(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, NULL);
+ return learn_ctx->ir_remote;
+}
+
+InfraredSignal* xremote_learn_get_ir_signal(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, NULL);
+ return learn_ctx->ir_signal;
+}
+
+XRemoteSignalReceiver* xremote_learn_get_ir_receiver(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, NULL);
+ return learn_ctx->ir_receiver;
+}
+
+XRemoteAppContext* xremote_learn_get_app_context(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, NULL);
+ return learn_ctx->app_ctx;
+}
+
+bool xremote_learn_has_buttons(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert(learn_ctx, false);
+ return infrared_remote_get_button_count(learn_ctx->ir_remote) > 0;
+}
+
+static void xremote_learn_switch_to_view(XRemoteLearnContext* learn_ctx, XRemoteViewID view_id)
+{
+ xremote_app_assert_void(learn_ctx);
+ learn_ctx->prev_view = learn_ctx->curr_view;
+ learn_ctx->curr_view = view_id;
+ ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
+ view_dispatcher_switch_to_view(view_disp, view_id);
+}
+
+static void xremote_learn_context_rx_stop(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert_void(learn_ctx);
+ learn_ctx->stop_receiver = true;
+ xremote_signal_receiver_stop(learn_ctx->ir_receiver);
+}
+
+static void xremote_learn_context_rx_start(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert_void(learn_ctx);
+ learn_ctx->finish_learning = false;
+ learn_ctx->stop_receiver = false;
+ xremote_signal_receiver_start(learn_ctx->ir_receiver);
+}
+
+
+static void xremote_learn_exit_dialog_free(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert_void(learn_ctx);
+ xremote_app_assert_void(learn_ctx->dialog_ex);
+
+ ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
+ view_dispatcher_remove_view(view_disp, XRemoteViewDialogExit);
+
+ dialog_ex_free(learn_ctx->dialog_ex);
+ learn_ctx->dialog_ex = NULL;
+}
+
+static void xremote_learn_exit_dialog_alloc(XRemoteLearnContext *learn_ctx, DialogExResultCallback callback)
+{
+ xremote_app_assert_void(learn_ctx);
+ xremote_learn_exit_dialog_free(learn_ctx);
+
+ ViewDispatcher *view_disp = learn_ctx->app_ctx->view_dispatcher;
+ const char* dialog_text = "All unsaved data\nwill be lost!";
+ const char* header_text = "Exit to XRemote Menu?";
+
+ learn_ctx->dialog_ex = dialog_ex_alloc();
+ View *view = dialog_ex_get_view(learn_ctx->dialog_ex);
+ view_dispatcher_add_view(view_disp, XRemoteViewDialogExit, view);
+
+ dialog_ex_set_header(learn_ctx->dialog_ex, header_text, 64, 11, AlignCenter, AlignTop);
+ dialog_ex_set_text(learn_ctx->dialog_ex, dialog_text, 64, 25, AlignCenter, AlignTop);
+ dialog_ex_set_icon(learn_ctx->dialog_ex, 0, 0, NULL);
+
+ dialog_ex_set_left_button_text(learn_ctx->dialog_ex, "Exit");
+ dialog_ex_set_center_button_text(learn_ctx->dialog_ex, "Save");
+ dialog_ex_set_right_button_text(learn_ctx->dialog_ex, "Stay");
+
+ dialog_ex_set_result_callback(learn_ctx->dialog_ex, callback);
+ dialog_ex_set_context(learn_ctx->dialog_ex, learn_ctx);
+}
+
static uint32_t xremote_learn_view_exit_callback(void* context)
{
UNUSED(context);
- return XRemoteViewSubmenu;
+ return XRemoteViewLearn;
+}
+
+static void xremote_learn_dialog_exit_callback(DialogExResult result, void* context)
+{
+ XRemoteLearnContext* learn_ctx = (XRemoteLearnContext*)context;
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
+
+ if (result == DialogExResultLeft)
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalExit);
+ else if (result == DialogExResultRight)
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalRetry);
+ else if (result == DialogExResultCenter)
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalFinish);
+}
+
+static uint32_t xremote_learn_text_input_exit_callback(void* context)
+{
+ TextInput* text_input = context;
+ XRemoteLearnContext* learn_ctx;
+
+ learn_ctx = text_input_get_validator_callback_context(text_input);
+ xremote_app_assert(learn_ctx, XRemoteViewSubmenu);
+
+ XRemoteEvent event = learn_ctx->prev_view == XRemoteViewSignal ?
+ XRemoteEventSignalReceived : XRemoteEventSignalRetry;
+
+ if (learn_ctx->current_button >= XREMOTE_BUTTON_COUNT)
+ learn_ctx->current_button = XREMOTE_BUTTON_COUNT - 1;
+
+ learn_ctx->finish_learning = false;
+ xremote_learn_send_event(learn_ctx, event);
+
+ return XRemoteViewTextInput;
+}
+
+static void xremote_learn_text_input_callback(void* context)
+{
+ xremote_app_assert_void(context);
+ XRemoteLearnContext *learn_ctx = context;
+
+ if (learn_ctx->is_dirty)
+ {
+ const char* name = xremote_learn_get_curr_button_name(learn_ctx);
+ if (!infrared_remote_get_button_by_name(learn_ctx->ir_remote, name))
+ {
+ InfraredSignal* signal = xremote_learn_get_ir_signal(learn_ctx);
+ infrared_remote_push_button(learn_ctx->ir_remote, name, signal);
+ }
+
+ learn_ctx->is_dirty = false;
+ }
+
+ if (infrared_remote_get_button_count(learn_ctx->ir_remote) && learn_ctx->text_store[0] != '\0')
+ {
+ char output_file[256];
+ snprintf(output_file, sizeof(output_file), "%s/%s.ir",
+ XREMOTE_APP_FOLDER, learn_ctx->text_store);
+
+ infrared_remote_set_name(learn_ctx->ir_remote, learn_ctx->text_store);
+ infrared_remote_set_path(learn_ctx->ir_remote, output_file);
+ infrared_remote_store(learn_ctx->ir_remote);
+ infrared_remote_reset(learn_ctx->ir_remote);
+ }
+
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalExit);
+}
+
+static void xremote_learn_signal_callback(void *context, InfraredSignal* signal)
+{
+ XRemoteLearnContext* learn_ctx = context;
+ xremote_app_assert_void(!learn_ctx->stop_receiver);
+ xremote_app_assert_void(!learn_ctx->finish_learning);
+
+ learn_ctx->stop_receiver = true;
+ learn_ctx->is_dirty = true;
+
+ infrared_signal_set_signal(learn_ctx->ir_signal, signal);
+ xremote_learn_send_event(learn_ctx, XRemoteEventSignalReceived);
+}
+
+static void xremote_learn_exit_dialog_ask(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert_void(learn_ctx);
+
+ if (infrared_remote_get_button_count(learn_ctx->ir_remote) || learn_ctx->is_dirty)
+ {
+ xremote_learn_exit_dialog_alloc(learn_ctx, xremote_learn_dialog_exit_callback);
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewDialogExit);
+ return;
+ }
+
+ learn_ctx->is_dirty = false;
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
+}
+
+static void xremote_learn_finish(XRemoteLearnContext *learn_ctx)
+{
+ xremote_app_assert_void(learn_ctx);
+ xremote_app_assert_void(learn_ctx->text_input);
+
+ if (infrared_remote_get_button_count(learn_ctx->ir_remote) || learn_ctx->is_dirty)
+ {
+ snprintf(learn_ctx->text_store, XREMOTE_TEXT_MAX, "Remote_");
+ text_input_set_header_text(learn_ctx->text_input, "Name new remote");
+
+ text_input_set_result_callback(
+ learn_ctx->text_input,
+ xremote_learn_text_input_callback,
+ learn_ctx,
+ learn_ctx->text_store,
+ XREMOTE_TEXT_MAX,
+ true);
+
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewTextInput);
+ return;
+ }
+
+ learn_ctx->is_dirty = false;
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
+}
+
+static bool xremote_learn_custom_event_callback(void* context, uint32_t event)
+{
+ xremote_app_assert(context, false);
+ XRemoteLearnContext *learn_ctx = context;
+
+ if (learn_ctx->finish_learning &&
+ event != XRemoteEventSignalFinish &&
+ event != XRemoteEventSignalAskExit &&
+ event != XRemoteEventSignalExit) return true;
+
+ if (event == XRemoteEventSignalReceived)
+ {
+ xremote_learn_context_rx_stop(learn_ctx);
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewSignal);
+ }
+ else if (event == XRemoteEventSignalSave)
+ {
+ const char* name = xremote_learn_get_curr_button_name(learn_ctx);
+ infrared_remote_delete_button_by_name(learn_ctx->ir_remote, name);
+
+ InfraredSignal* signal = xremote_learn_get_ir_signal(learn_ctx);
+ infrared_remote_push_button(learn_ctx->ir_remote, name, signal);
+ learn_ctx->is_dirty = false;
+
+ if (++learn_ctx->current_button >= XREMOTE_BUTTON_COUNT)
+ {
+ xremote_learn_context_rx_stop(learn_ctx);
+ xremote_learn_finish(learn_ctx);
+ return true;
+ }
+
+ xremote_learn_context_rx_start(learn_ctx);
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewLearn);
+ }
+ else if (event == XRemoteEventSignalSkip)
+ {
+ learn_ctx->current_button++;
+ learn_ctx->is_dirty = false;
+
+ if (learn_ctx->current_button >= XREMOTE_BUTTON_COUNT)
+ {
+ if (xremote_learn_has_buttons(learn_ctx))
+ {
+ xremote_learn_context_rx_stop(learn_ctx);
+ xremote_learn_finish(learn_ctx);
+ return true;
+ }
+
+ learn_ctx->current_button = 0;
+ }
+
+ xremote_learn_context_rx_start(learn_ctx);
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewLearn);
+ }
+ else if (event == XRemoteEventSignalRetry)
+ {
+ learn_ctx->is_dirty = false;
+ xremote_learn_context_rx_start(learn_ctx);
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewLearn);
+ }
+ else if (event == XRemoteEventSignalFinish)
+ {
+ xremote_learn_context_rx_stop(learn_ctx);
+ xremote_learn_finish(learn_ctx);
+ }
+ else if (event == XRemoteEventSignalAskExit)
+ {
+ xremote_learn_context_rx_stop(learn_ctx);
+ xremote_learn_exit_dialog_ask(learn_ctx);
+ }
+ else if (event == XRemoteEventSignalExit)
+ {
+ learn_ctx->is_dirty = false;
+ xremote_learn_context_rx_stop(learn_ctx);
+ xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
+ }
+
+ return true;
+}
+
+static XRemoteLearnContext* xremote_learn_context_alloc(XRemoteAppContext* app_ctx)
+{
+ XRemoteLearnContext *learn_ctx = malloc(sizeof(XRemoteLearnContext));
+ learn_ctx->ir_signal = infrared_signal_alloc();
+ learn_ctx->ir_remote = infrared_remote_alloc();
+
+ learn_ctx->app_ctx = app_ctx;
+ learn_ctx->dialog_ex = NULL;
+ learn_ctx->text_store[0] = 0;
+ learn_ctx->current_button = 0;
+
+ learn_ctx->curr_view = XRemoteViewLearn;
+ learn_ctx->prev_view = XRemoteViewNone;
+
+ learn_ctx->finish_learning = false;
+ learn_ctx->stop_receiver = false;
+ learn_ctx->is_dirty = false;
+
+ learn_ctx->signal_view = xremote_learn_success_view_alloc(app_ctx, learn_ctx);
+ View* view = xremote_view_get_view(learn_ctx->signal_view);
+ view_set_previous_callback(view, xremote_learn_view_exit_callback);
+ view_dispatcher_add_view(app_ctx->view_dispatcher, XRemoteViewSignal, view);
+
+ learn_ctx->text_input = text_input_alloc();
+ text_input_set_validator(learn_ctx->text_input, NULL, learn_ctx);
+
+ view = text_input_get_view(learn_ctx->text_input);
+ view_set_previous_callback(view, xremote_learn_text_input_exit_callback);
+ view_dispatcher_add_view(app_ctx->view_dispatcher, XRemoteViewTextInput, view);
+
+ view_dispatcher_set_custom_event_callback(app_ctx->view_dispatcher, xremote_learn_custom_event_callback);
+ view_dispatcher_set_event_callback_context(app_ctx->view_dispatcher, learn_ctx);
+
+ learn_ctx->ir_receiver = xremote_signal_receiver_alloc(app_ctx);
+ xremote_signal_receiver_set_context(learn_ctx->ir_receiver, learn_ctx, NULL);
+ xremote_signal_receiver_set_rx_callback(learn_ctx->ir_receiver, xremote_learn_signal_callback);
+
+ return learn_ctx;
+}
+
+static void xremote_learn_context_free(XRemoteLearnContext* learn_ctx)
+{
+ xremote_app_assert_void(learn_ctx);
+ xremote_signal_receiver_stop(learn_ctx->ir_receiver);
+ xremote_learn_exit_dialog_free(learn_ctx);
+
+ ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
+ view_dispatcher_set_custom_event_callback(view_disp, NULL);
+ view_dispatcher_set_event_callback_context(view_disp, NULL);
+
+ view_dispatcher_remove_view(view_disp, XRemoteViewTextInput);
+ text_input_free(learn_ctx->text_input);
+
+ view_dispatcher_remove_view(view_disp, XRemoteViewSignal);
+ xremote_view_free(learn_ctx->signal_view);
+
+ xremote_signal_receiver_free(learn_ctx->ir_receiver);
+ infrared_signal_free(learn_ctx->ir_signal);
+ infrared_remote_free(learn_ctx->ir_remote);
+ free(learn_ctx);
+}
+
+static void xremote_learn_context_clear_callback(void* context)
+{
+ XRemoteLearnContext *learn = context;
+ xremote_learn_context_free(learn);
}
XRemoteApp* xremote_learn_alloc(XRemoteAppContext* app_ctx)
{
XRemoteApp* app = xremote_app_alloc(app_ctx);
- xremote_app_view_alloc(app, XRemoteViewLearn, xremote_learn_view_alloc);
+ app->view_id = XRemoteViewLearn;
+
+ XRemoteLearnContext* learn = xremote_learn_context_alloc(app_ctx);
+ app->view_ctx = xremote_learn_view_alloc(app->app_ctx, learn);
+ View* view = xremote_view_get_view(app->view_ctx);
+
+ ViewDispatcher* view_disp = app_ctx->view_dispatcher;
+ view_dispatcher_add_view(view_disp, app->view_id, view);
+
xremote_app_view_set_previous_callback(app, xremote_learn_view_exit_callback);
+ xremote_app_set_view_context(app, learn, xremote_learn_context_clear_callback);
+
+ xremote_signal_receiver_start(learn->ir_receiver);
return app;
}
diff --git a/xremote_learn.h b/xremote_learn.h
index 9a20409..cb36d56 100644
--- a/xremote_learn.h
+++ b/xremote_learn.h
@@ -9,5 +9,18 @@
#pragma once
#include "xremote_app.h"
+#include "xremote_signal.h"
+
+typedef struct XRemoteLearnContext XRemoteLearnContext;
+
+void xremote_learn_send_event(XRemoteLearnContext* learn_ctx, XRemoteEvent event);
+const char* xremote_learn_get_curr_button_name(XRemoteLearnContext *learn_ctx);
+int xremote_learn_get_curr_button_index(XRemoteLearnContext *learn_ctx);
+bool xremote_learn_has_buttons(XRemoteLearnContext *learn_ctx);
+
+XRemoteSignalReceiver* xremote_learn_get_ir_receiver(XRemoteLearnContext *learn_ctx);
+XRemoteAppContext* xremote_learn_get_app_context(XRemoteLearnContext *learn_ctx);
+InfraredRemote* xremote_learn_get_ir_remote(XRemoteLearnContext *learn_ctx);
+InfraredSignal* xremote_learn_get_ir_signal(XRemoteLearnContext *learn_ctx);
XRemoteApp* xremote_learn_alloc(XRemoteAppContext* app_ctx);
diff --git a/xremote_signal.c b/xremote_signal.c
new file mode 100644
index 0000000..029d84f
--- /dev/null
+++ b/xremote_signal.c
@@ -0,0 +1,131 @@
+/*!
+ * @file flipper-xremote/xremote_signal.h
+ @license This project is released under the GNU GPLv3 License
+ * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
+ *
+ * @brief Implementation of infrared signal receiver functionality
+ */
+
+#include "xremote_signal.h"
+
+struct XRemoteSignalReceiver {
+ XRemoteClearCallback on_clear;
+ XRemoteRxCallback rx_callback;
+
+ NotificationApp* notifications;
+ InfraredWorker* worker;
+ InfraredSignal* signal;
+
+ void* context;
+ bool started;
+};
+
+static void xremote_signal_receiver_rx_callback(void* context, InfraredWorkerSignal* ir_signal)
+{
+ furi_assert(context);
+ XRemoteSignalReceiver *rx_ctx = context;
+ xremote_app_notification_blink(rx_ctx->notifications);
+
+ if (infrared_worker_signal_is_decoded(ir_signal))
+ {
+ const InfraredMessage* message;
+ message = infrared_worker_get_decoded_signal(ir_signal);
+ infrared_signal_set_message(rx_ctx->signal, message);
+ }
+ else
+ {
+ const uint32_t* timings;
+ size_t timings_size = 0;
+
+ infrared_worker_get_raw_signal(ir_signal, &timings, &timings_size);
+ infrared_signal_set_raw_signal(rx_ctx->signal, timings, timings_size,
+ INFRARED_COMMON_CARRIER_FREQUENCY, INFRARED_COMMON_DUTY_CYCLE);
+ }
+
+ if (rx_ctx->rx_callback != NULL)
+ rx_ctx->rx_callback(rx_ctx->context, rx_ctx->signal);
+}
+
+static void xremote_signal_receiver_clear_context(XRemoteSignalReceiver* rx_ctx)
+{
+ xremote_app_assert_void(rx_ctx);
+ xremote_app_assert_void(rx_ctx->context);
+ xremote_app_assert_void(rx_ctx->on_clear);
+ rx_ctx->on_clear(rx_ctx->context);
+ rx_ctx->context = NULL;
+}
+
+XRemoteSignalReceiver* xremote_signal_receiver_alloc(XRemoteAppContext* app_ctx)
+{
+ XRemoteSignalReceiver *rx_ctx = malloc(sizeof(XRemoteSignalReceiver));
+ rx_ctx->signal = infrared_signal_alloc();
+ rx_ctx->worker = infrared_worker_alloc();
+
+ rx_ctx->notifications = app_ctx->notifications;
+ rx_ctx->rx_callback = NULL;
+ rx_ctx->on_clear = NULL;
+ rx_ctx->context = NULL;
+ rx_ctx->started = false;
+ return rx_ctx;
+}
+
+void xremote_signal_receiver_free(XRemoteSignalReceiver* rx_ctx)
+{
+ xremote_app_assert_void(rx_ctx);
+ xremote_signal_receiver_stop(rx_ctx);
+ infrared_worker_free(rx_ctx->worker);
+ infrared_signal_free(rx_ctx->signal);
+ xremote_signal_receiver_clear_context(rx_ctx);
+ free(rx_ctx);
+}
+
+void xremote_signal_receiver_set_context(XRemoteSignalReceiver* rx_ctx, void *context, XRemoteClearCallback on_clear)
+{
+ xremote_signal_receiver_clear_context(rx_ctx);
+ rx_ctx->on_clear = on_clear;
+ rx_ctx->context = context;
+}
+
+void xremote_signal_receiver_set_rx_callback(XRemoteSignalReceiver* rx_ctx, XRemoteRxCallback rx_callback)
+{
+ xremote_app_assert_void(rx_ctx);
+ rx_ctx->rx_callback = rx_callback;
+}
+
+void xremote_signal_receiver_start(XRemoteSignalReceiver *rx_ctx)
+{
+ xremote_app_assert_void((rx_ctx && rx_ctx->worker && !rx_ctx->started));
+ infrared_worker_rx_set_received_signal_callback(rx_ctx->worker,
+ xremote_signal_receiver_rx_callback, (void*)rx_ctx);
+
+ infrared_worker_rx_start(rx_ctx->worker);
+ xremote_app_notification_blink(rx_ctx->notifications);
+ rx_ctx->started = true;
+}
+
+void xremote_signal_receiver_stop(XRemoteSignalReceiver *rx_ctx)
+{
+ xremote_app_assert_void((rx_ctx && rx_ctx->worker && rx_ctx->started));
+ infrared_worker_rx_set_received_signal_callback(rx_ctx->worker, NULL, NULL);
+ infrared_worker_rx_stop(rx_ctx->worker);
+ rx_ctx->started = false;
+}
+
+void xremote_signal_receiver_pause(XRemoteSignalReceiver *rx_ctx)
+{
+ xremote_app_assert_void((rx_ctx && rx_ctx->worker));
+ infrared_worker_rx_set_received_signal_callback(rx_ctx->worker, NULL, NULL);
+}
+
+void xremote_signal_receiver_resume(XRemoteSignalReceiver *rx_ctx)
+{
+ xremote_app_assert_void((rx_ctx && rx_ctx->worker));
+ infrared_worker_rx_set_received_signal_callback(rx_ctx->worker,
+ xremote_signal_receiver_rx_callback, (void*)rx_ctx);
+}
+
+InfraredSignal* xremote_signal_receiver_get_signal(XRemoteSignalReceiver *rx_ctx)
+{
+ xremote_app_assert(rx_ctx, NULL);
+ return rx_ctx->signal;
+}
\ No newline at end of file
diff --git a/xremote_signal.h b/xremote_signal.h
new file mode 100644
index 0000000..e1421f9
--- /dev/null
+++ b/xremote_signal.h
@@ -0,0 +1,28 @@
+/*!
+ * @file flipper-xremote/xremote_signal.h
+ @license This project is released under the GNU GPLv3 License
+ * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
+ *
+ * @brief Implementation of infrared signal receiver functionality
+ */
+
+#pragma once
+
+#include "xremote_app.h"
+#include "infrared/infrared_signal.h"
+
+typedef void (*XRemoteRxCallback)(void *context, InfraredSignal* signal);
+typedef struct XRemoteSignalReceiver XRemoteSignalReceiver;
+
+XRemoteSignalReceiver* xremote_signal_receiver_alloc(XRemoteAppContext* app_ctx);
+void xremote_signal_receiver_free(XRemoteSignalReceiver* rx_ctx);
+
+void xremote_signal_receiver_set_context(XRemoteSignalReceiver* rx_ctx, void *context, XRemoteClearCallback on_clear);
+void xremote_signal_receiver_set_rx_callback(XRemoteSignalReceiver* rx_ctx, XRemoteRxCallback rx_callback);
+InfraredSignal* xremote_signal_receiver_get_signal(XRemoteSignalReceiver *rx_ctx);
+
+void xremote_signal_receiver_start(XRemoteSignalReceiver *rx_ctx);
+void xremote_signal_receiver_stop(XRemoteSignalReceiver *rx_ctx);
+
+void xremote_signal_receiver_pause(XRemoteSignalReceiver *rx_ctx);
+void xremote_signal_receiver_resume(XRemoteSignalReceiver *rx_ctx);