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 modeReceived signal
XRemote learn modeXRemote received signal
+ 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);
Settings