From 31c31db479316e7e264f2675319ca833a91214cd Mon Sep 17 00:00:00 2001 From: Albert Kharisov Date: Wed, 2 Jun 2021 18:16:05 +0300 Subject: [PATCH] [FL-1250, FL-1252, FL-1323, FL-1324] New IRDA Application (part 1) (#497) * Add new IrdaApp (half ready), add ButtonMenu * Fix NEC's extension * clang-format * Fix leak * Add submenu optional header * IRDA: add Edit button * clang-format * IrdaApp: Fix scene flow * Add IRDA NEC extended protocol * IRDA: Add address/command length Co-authored-by: SG --- .gitignore | 6 + applications/gui/canvas.c | 26 ++ applications/gui/canvas.h | 22 ++ applications/gui/elements.c | 22 +- applications/gui/elements.h | 12 + applications/gui/modules/button_menu.c | 288 ++++++++++++++++++ applications/gui/modules/button_menu.h | 73 +++++ applications/gui/modules/submenu.c | 56 +++- applications/gui/modules/submenu.h | 17 +- applications/irda/irda-app-event.hpp | 26 ++ applications/irda/irda-app-receiver.cpp | 46 +++ applications/irda/irda-app-receiver.hpp | 19 ++ applications/irda/irda-app-remote-manager.cpp | 130 ++++++++ applications/irda/irda-app-remote-manager.hpp | 53 ++++ applications/irda/irda-app-view-manager.cpp | 110 +++++++ applications/irda/irda-app-view-manager.hpp | 52 ++++ applications/irda/irda-app.cpp | 156 ++++++++++ applications/irda/irda-app.hpp | 108 +++++++ .../irda/irda-decoder/irda-decoder-nec.c | 146 --------- .../irda/irda-decoder/irda-decoder-nec.h | 39 --- .../irda/irda-decoder/irda-decoder-types.h | 12 - applications/irda/irda-decoder/irda-decoder.c | 41 --- applications/irda/irda-decoder/irda-decoder.h | 17 -- applications/irda/irda-runner.cpp | 9 + .../irda/{irda_app.c => irda_app_old.c} | 2 +- applications/irda/irda_protocols.h | 19 -- .../scene/irda-app-scene-edit-delete-done.cpp | 35 +++ .../irda/scene/irda-app-scene-edit-delete.cpp | 81 +++++ .../scene/irda-app-scene-edit-key-select.cpp | 54 ++++ .../scene/irda-app-scene-edit-rename-done.cpp | 31 ++ .../irda/scene/irda-app-scene-edit-rename.cpp | 46 +++ .../irda/scene/irda-app-scene-edit.cpp | 74 +++++ .../scene/irda-app-scene-learn-done-after.cpp | 34 +++ .../irda/scene/irda-app-scene-learn-done.cpp | 42 +++ .../scene/irda-app-scene-learn-enter-name.cpp | 51 ++++ .../scene/irda-app-scene-learn-success.cpp | 63 ++++ .../irda/scene/irda-app-scene-learn.cpp | 31 ++ .../irda/scene/irda-app-scene-remote-list.cpp | 59 ++++ .../irda/scene/irda-app-scene-remote.cpp | 75 +++++ .../irda/scene/irda-app-scene-start.cpp | 59 ++++ .../irda/scene/irda-app-scene-universal.cpp | 57 ++++ applications/irda/scene/irda-app-scene.hpp | 130 ++++++++ applications/irda_monitor/irda_monitor.c | 8 +- .../tests/irda_decoder/irda_decoder_test.c | 13 +- .../irda_decoder_nec_test_data.srcdata | 110 +++---- .../irda_decoder_necext_test_data.srcdata | 223 ++++++++++++++ assets/icons/Irda/IrdaArrowDown_4x8.png | Bin 0 -> 3589 bytes assets/icons/Irda/IrdaArrowUp_4x8.png | Bin 0 -> 3592 bytes assets/icons/Irda/IrdaLearnShort_128x31.png | Bin 0 -> 3952 bytes assets/icons/Irda/IrdaLearn_128x64.png | Bin 0 -> 4161 bytes assets/icons/Irda/IrdaSendShort_128x34.png | Bin 0 -> 4009 bytes assets/icons/Irda/IrdaSend_128x64.png | Bin 0 -> 4219 bytes core/furi/memmgr.h | 6 +- lib/irda/irda.c | 40 ++- lib/irda/irda.h | 24 ++ lib/irda/irda_common_decoder.c | 10 + lib/irda/irda_common_decoder_i.h | 1 + lib/irda/irda_i.h | 1 + lib/irda/irda_protocol_defs_i.h | 4 + lib/irda/nec/irda_decoder_nec.c | 52 +++- lib/irda/nec/irda_encoder_nec.c | 18 ++ lib/irda/samsung/irda_decoder_samsung.c | 4 + 62 files changed, 2568 insertions(+), 375 deletions(-) create mode 100644 applications/gui/modules/button_menu.c create mode 100644 applications/gui/modules/button_menu.h create mode 100644 applications/irda/irda-app-event.hpp create mode 100644 applications/irda/irda-app-receiver.cpp create mode 100644 applications/irda/irda-app-receiver.hpp create mode 100644 applications/irda/irda-app-remote-manager.cpp create mode 100644 applications/irda/irda-app-remote-manager.hpp create mode 100644 applications/irda/irda-app-view-manager.cpp create mode 100644 applications/irda/irda-app-view-manager.hpp create mode 100644 applications/irda/irda-app.cpp create mode 100644 applications/irda/irda-app.hpp delete mode 100644 applications/irda/irda-decoder/irda-decoder-nec.c delete mode 100644 applications/irda/irda-decoder/irda-decoder-nec.h delete mode 100644 applications/irda/irda-decoder/irda-decoder-types.h delete mode 100644 applications/irda/irda-decoder/irda-decoder.c delete mode 100644 applications/irda/irda-decoder/irda-decoder.h create mode 100644 applications/irda/irda-runner.cpp rename applications/irda/{irda_app.c => irda_app_old.c} (99%) delete mode 100644 applications/irda/irda_protocols.h create mode 100644 applications/irda/scene/irda-app-scene-edit-delete-done.cpp create mode 100644 applications/irda/scene/irda-app-scene-edit-delete.cpp create mode 100644 applications/irda/scene/irda-app-scene-edit-key-select.cpp create mode 100644 applications/irda/scene/irda-app-scene-edit-rename-done.cpp create mode 100644 applications/irda/scene/irda-app-scene-edit-rename.cpp create mode 100644 applications/irda/scene/irda-app-scene-edit.cpp create mode 100644 applications/irda/scene/irda-app-scene-learn-done-after.cpp create mode 100644 applications/irda/scene/irda-app-scene-learn-done.cpp create mode 100644 applications/irda/scene/irda-app-scene-learn-enter-name.cpp create mode 100644 applications/irda/scene/irda-app-scene-learn-success.cpp create mode 100644 applications/irda/scene/irda-app-scene-learn.cpp create mode 100644 applications/irda/scene/irda-app-scene-remote-list.cpp create mode 100644 applications/irda/scene/irda-app-scene-remote.cpp create mode 100644 applications/irda/scene/irda-app-scene-start.cpp create mode 100644 applications/irda/scene/irda-app-scene-universal.cpp create mode 100644 applications/irda/scene/irda-app-scene.hpp create mode 100644 applications/tests/irda_decoder/test_data/irda_decoder_necext_test_data.srcdata create mode 100644 assets/icons/Irda/IrdaArrowDown_4x8.png create mode 100644 assets/icons/Irda/IrdaArrowUp_4x8.png create mode 100644 assets/icons/Irda/IrdaLearnShort_128x31.png create mode 100644 assets/icons/Irda/IrdaLearn_128x64.png create mode 100644 assets/icons/Irda/IrdaSendShort_128x34.png create mode 100644 assets/icons/Irda/IrdaSend_128x64.png diff --git a/.gitignore b/.gitignore index 7ea5bafc..0d49334a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ *.swp +*.gdb_history + + +# LSP +.cache +compile_commands.json # JetBrains IDEs .idea/ diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c index f2d52e66..610c5d7a 100644 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -202,6 +202,19 @@ void canvas_draw_box(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_ u8g2_DrawBox(&canvas->fb, x, y, width, height); } +void canvas_draw_rbox( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius) { + furi_assert(canvas); + x += canvas->offset_x; + y += canvas->offset_y; + u8g2_DrawRBox(&canvas->fb, x, y, width, height, radius); +} + void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { furi_assert(canvas); x += canvas->offset_x; @@ -209,6 +222,19 @@ void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint u8g2_DrawFrame(&canvas->fb, x, y, width, height); } +void canvas_draw_rframe( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius) { + furi_assert(canvas); + x += canvas->offset_x; + y += canvas->offset_y; + u8g2_DrawRFrame(&canvas->fb, x, y, width, height, radius); +} + void canvas_draw_line(Canvas* canvas, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { furi_assert(canvas); x1 += canvas->offset_x; diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h index e5674b0e..f387e20b 100644 --- a/applications/gui/canvas.h +++ b/applications/gui/canvas.h @@ -157,6 +157,28 @@ void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch); */ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha); +/* + * Draw rounded-corner frame of width, height at x,y, with round value raduis + */ +void canvas_draw_rframe( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius); + +/* + * Draw rounded-corner box of width, height at x,y, with round value raduis + */ +void canvas_draw_rbox( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius); + #ifdef __cplusplus } #endif diff --git a/applications/gui/elements.c b/applications/gui/elements.c index f3762418..64597f53 100644 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -1,6 +1,8 @@ #include "elements.h" +#include "gui/canvas.h" #include #include +#include #include #include "canvas_i.h" #include @@ -44,7 +46,7 @@ void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total) { } // Position block if(total) { - uint8_t block_h = ((float)height) / total; + float block_h = ((float)height) / total; canvas_draw_box(canvas, width - 3, block_h * pos, 3, MAX(block_h, 1)); } } @@ -249,13 +251,17 @@ void elements_slightly_rounded_frame( uint8_t width, uint8_t height) { furi_assert(canvas); - canvas_draw_frame(canvas, x, y, width, height); - canvas_invert_color(canvas); - canvas_draw_dot(canvas, x, y); - canvas_draw_dot(canvas, x + width - 1, y + height - 1); - canvas_draw_dot(canvas, x + width - 1, y); - canvas_draw_dot(canvas, x, y + height - 1); - canvas_invert_color(canvas); + canvas_draw_rframe(canvas, x, y, width, height, 1); +} + +void elements_slightly_rounded_box( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height) { + furi_assert(canvas); + canvas_draw_rbox(canvas, x, y, width, height, 1); } void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { diff --git a/applications/gui/elements.h b/applications/gui/elements.h index 76e43cff..5a13b87b 100644 --- a/applications/gui/elements.h +++ b/applications/gui/elements.h @@ -106,6 +106,18 @@ void elements_slightly_rounded_frame( uint8_t width, uint8_t height); +/* + * Draw slightly rounded box + * @param x, y - top left corner coordinates + * @param width, height - size of box + */ +void elements_slightly_rounded_box( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height); + /* * Trim string buffer to fit width in pixels * @param string - string to trim diff --git a/applications/gui/modules/button_menu.c b/applications/gui/modules/button_menu.c new file mode 100644 index 00000000..0545fac1 --- /dev/null +++ b/applications/gui/modules/button_menu.c @@ -0,0 +1,288 @@ +#include "button_menu.h" +#include "gui/canvas.h" +#include "gui/elements.h" +#include +#include +#include + +#define ITEM_FIRST_OFFSET 17 +#define ITEM_NEXT_OFFSET 4 +#define ITEM_HEIGHT 14 +#define ITEM_WIDTH 64 +#define BUTTONS_PER_SCREEN 6 + +struct ButtonMenuItem { + const char* label; + int32_t index; + ButtonMenuItemCallback callback; + ButtonMenuItemType type; + void* callback_context; +}; + +ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST); + +struct ButtonMenu { + View* view; +}; + +typedef struct { + ButtonMenuItemArray_t items; + uint8_t position; + const char* header; +} ButtonMenuModel; + +static void button_menu_draw_control_button( + Canvas* canvas, + uint8_t item_position, + const char* text, + bool selected) { + furi_assert(canvas); + furi_assert(text); + + uint8_t item_x = 0; + uint8_t item_y = ITEM_FIRST_OFFSET + (item_position * (ITEM_HEIGHT + ITEM_NEXT_OFFSET)); + + canvas_set_color(canvas, ColorBlack); + + if(selected) { + elements_slightly_rounded_box(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_str_aligned( + canvas, + item_x + (ITEM_WIDTH / 2), + item_y + (ITEM_HEIGHT / 2), + AlignCenter, + AlignCenter, + text); +} + +static void button_menu_draw_common_button( + Canvas* canvas, + uint8_t item_position, + const char* text, + bool selected) { + furi_assert(canvas); + furi_assert(text); + + uint8_t item_x = 0; + uint8_t item_y = ITEM_FIRST_OFFSET + (item_position * (ITEM_HEIGHT + ITEM_NEXT_OFFSET)); + + canvas_set_color(canvas, ColorBlack); + + if(selected) { + canvas_draw_rbox(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5); + } + canvas_draw_str_aligned( + canvas, + item_x + (ITEM_WIDTH / 2), + item_y + (ITEM_HEIGHT / 2), + AlignCenter, + AlignCenter, + text); +} + +static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { + furi_assert(canvas); + furi_assert(_model); + + ButtonMenuModel* model = (ButtonMenuModel*)_model; + + canvas_clear(canvas); + canvas_set_font(canvas, FontSecondary); + + uint8_t item_position = 0; + int8_t active_screen = model->position / BUTTONS_PER_SCREEN; + size_t items_size = ButtonMenuItemArray_size(model->items); + int8_t max_screen = ((int16_t)items_size - 1) / BUTTONS_PER_SCREEN; + ButtonMenuItemArray_it_t it; + + if(active_screen > 0) { + canvas_draw_icon_name(canvas, 28, 1, I_IrdaArrowUp_4x8); + } + + if(max_screen > active_screen) { + canvas_draw_icon_name(canvas, 28, 123, I_IrdaArrowDown_4x8); + } + + canvas_draw_str_aligned(canvas, 32, 10, AlignCenter, AlignCenter, model->header); + + for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); + ButtonMenuItemArray_next(it), ++item_position) { + if(active_screen == (item_position / BUTTONS_PER_SCREEN)) { + if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeControl) { + button_menu_draw_control_button( + canvas, + item_position % BUTTONS_PER_SCREEN, + ButtonMenuItemArray_cref(it)->label, + (item_position == model->position)); + } else if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeCommon) { + button_menu_draw_common_button( + canvas, + item_position % BUTTONS_PER_SCREEN, + ButtonMenuItemArray_cref(it)->label, + (item_position == model->position)); + } + } + } +} + +static void button_menu_process_up(ButtonMenu* button_menu) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + if(model->position > 0) { + model->position--; + } else { + model->position = ButtonMenuItemArray_size(model->items) - 1; + } + return true; + }); +} + +static void button_menu_process_down(ButtonMenu* button_menu) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + if(model->position < (ButtonMenuItemArray_size(model->items) - 1)) { + model->position++; + } else { + model->position = 0; + } + return true; + }); +} + +static void button_menu_process_ok(ButtonMenu* button_menu) { + furi_assert(button_menu); + + ButtonMenuItem* item = NULL; + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + if(model->position < (ButtonMenuItemArray_size(model->items))) { + item = ButtonMenuItemArray_get(model->items, model->position); + } + return true; + }); + + if(item && item->callback) { + item->callback(item->callback_context, item->index); + } +} + +static bool button_menu_view_input_callback(InputEvent* event, void* context) { + furi_assert(event); + + ButtonMenu* button_menu = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyUp: + consumed = true; + button_menu_process_up(button_menu); + break; + case InputKeyDown: + consumed = true; + button_menu_process_down(button_menu); + break; + case InputKeyOk: + consumed = true; + button_menu_process_ok(button_menu); + break; + default: + break; + } + } + + return consumed; +} + +View* button_menu_get_view(ButtonMenu* button_menu) { + furi_assert(button_menu); + return button_menu->view; +} + +void button_menu_clean(ButtonMenu* button_menu) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + ButtonMenuItemArray_clean(model->items); + model->position = 0; + return true; + }); +} + +void button_menu_set_header(ButtonMenu* button_menu, const char* header) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + model->header = header; + return true; + }); +} + +ButtonMenuItem* button_menu_add_item( + ButtonMenu* button_menu, + const char* label, + int32_t index, + ButtonMenuItemCallback callback, + ButtonMenuItemType type, + void* callback_context) { + ButtonMenuItem* item = NULL; + furi_assert(label); + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + item = ButtonMenuItemArray_push_new(model->items); + item->label = label; + item->index = index; + item->type = type; + item->callback = callback; + item->callback_context = callback_context; + return true; + }); + + return item; +} + +ButtonMenu* button_menu_alloc(void) { + ButtonMenu* button_menu = furi_alloc(sizeof(ButtonMenu)); + button_menu->view = view_alloc(); + view_set_orientation(button_menu->view, ViewOrientationVertical); + view_set_context(button_menu->view, button_menu); + view_allocate_model(button_menu->view, ViewModelTypeLocking, sizeof(ButtonMenuModel)); + view_set_draw_callback(button_menu->view, button_menu_view_draw_callback); + view_set_input_callback(button_menu->view, button_menu_view_input_callback); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + ButtonMenuItemArray_init(model->items); + model->position = 0; + model->header = NULL; + return true; + }); + + return button_menu; +} + +void button_menu_free(ButtonMenu* button_menu) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + ButtonMenuItemArray_clear(model->items); + return true; + }); + view_free(button_menu->view); + free(button_menu); +} diff --git a/applications/gui/modules/button_menu.h b/applications/gui/modules/button_menu.h new file mode 100644 index 00000000..5aa25008 --- /dev/null +++ b/applications/gui/modules/button_menu.h @@ -0,0 +1,73 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ButtonMenu anonymous structure */ +typedef struct ButtonMenu ButtonMenu; +typedef struct ButtonMenuItem ButtonMenuItem; + +/* Callback for any button menu actions */ +typedef void (*ButtonMenuItemCallback)(void* context, int32_t index); + +/* Type of button. Difference in drawing buttons. */ +typedef enum { + ButtonMenuItemTypeCommon, + ButtonMenuItemTypeControl, +} ButtonMenuItemType; + +/** + * @brief Get button menu view + * @param button_menu - ButtonMenu instance + * @return View instance that can be used for embedding + */ +View* button_menu_get_view(ButtonMenu* button_menu); + +/** + * @brief Clean button menu + * @param button_menu - ButtonMenu instance + */ +void button_menu_clean(ButtonMenu* button_menu); + +/** + * @brief Add item to button menu instance + * @param button_menu - ButtonMenu instance + * @param label - text inside new button + * @param index - value to distinct between buttons inside ButtonMenuItemCallback + * @param type - type of button to create. Differ by button drawing. + * Control buttons have no frames, and have more squared borders. + * @return pointer to just-created item + */ +ButtonMenuItem* button_menu_add_item( + ButtonMenu* button_menu, + const char* label, + int32_t index, + ButtonMenuItemCallback callback, + ButtonMenuItemType type, + void* callback_context); + +/** + * @brief Allocate and initialize new instance of ButtonMenu model + * @return just-created ButtonMenu model + */ +ButtonMenu* button_menu_alloc(void); + +/** + * @brief Free ButtonMenu element + * @param button_menu - ButtonMenu instance + */ +void button_menu_free(ButtonMenu* button_menu); + +/** + * @brief Set ButtonMenu header on top of canvas + * @param button_menu - ButtonMenu instance + * @param header - header on the top of button menu + */ +void button_menu_set_header(ButtonMenu* button_menu, const char* header); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/submenu.c b/applications/gui/modules/submenu.c index 75ae96c1..d05f3302 100644 --- a/applications/gui/modules/submenu.c +++ b/applications/gui/modules/submenu.c @@ -1,7 +1,9 @@ #include "submenu.h" +#include "gui/canvas.h" #include #include #include +#include struct SubmenuItem { const char* label; @@ -18,6 +20,7 @@ struct Submenu { typedef struct { SubmenuItemArray_t items; + const char* header; uint8_t position; uint8_t window_position; } SubmenuModel; @@ -33,34 +36,39 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { const uint8_t item_width = 123; canvas_clear(canvas); - canvas_set_font(canvas, FontSecondary); + canvas_set_font(canvas, FontPrimary); uint8_t position = 0; SubmenuItemArray_it_t it; + if(model->header) { + canvas_draw_str(canvas, 4, 11, model->header); + } + + canvas_set_font(canvas, FontSecondary); for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { uint8_t item_position = position - model->window_position; + uint8_t elements_on_screen = model->header ? 3 : 4; + uint8_t y_offset = model->header ? 16 : 0; - if(item_position < 4) { + if(item_position < elements_on_screen) { if(position == model->position) { canvas_set_color(canvas, ColorBlack); - canvas_draw_box( - canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2); + elements_slightly_rounded_box( + canvas, + 0, + y_offset + (item_position * item_height) + 1, + item_width, + item_height - 2); canvas_set_color(canvas, ColorWhite); - - canvas_draw_dot(canvas, 0, (item_position * item_height) + 1); - canvas_draw_dot(canvas, 0, (item_position * item_height) + item_height - 2); - canvas_draw_dot(canvas, item_width - 1, (item_position * item_height) + 1); - canvas_draw_dot( - canvas, item_width - 1, (item_position * item_height) + item_height - 2); } else { canvas_set_color(canvas, ColorBlack); } canvas_draw_str( canvas, 6, - (item_position * item_height) + item_height - 4, + y_offset + (item_position * item_height) + item_height - 4, SubmenuItemArray_cref(it)->label); } @@ -110,6 +118,7 @@ Submenu* submenu_alloc() { SubmenuItemArray_init(model->items); model->position = 0; model->window_position = 0; + model->header = NULL; return true; }); @@ -164,6 +173,7 @@ void submenu_clean(Submenu* submenu) { SubmenuItemArray_clean(model->items); model->position = 0; model->window_position = 0; + model->header = NULL; return true; }); } @@ -207,15 +217,17 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { void submenu_process_up(Submenu* submenu) { with_view_model( submenu->view, (SubmenuModel * model) { + uint8_t elements_on_screen = model->header ? 3 : 4; if(model->position > 0) { model->position--; - if((model->position - model->window_position) < 1 && model->window_position > 0) { + if(((model->position - model->window_position) < 1) && + model->window_position > 0) { model->window_position--; } } else { model->position = SubmenuItemArray_size(model->items) - 1; - if(model->position > 3) { - model->window_position = model->position - 3; + if(model->position > (elements_on_screen - 1)) { + model->window_position = model->position - (elements_on_screen - 1); } } return true; @@ -225,10 +237,12 @@ void submenu_process_up(Submenu* submenu) { void submenu_process_down(Submenu* submenu) { with_view_model( submenu->view, (SubmenuModel * model) { + uint8_t elements_on_screen = model->header ? 3 : 4; if(model->position < (SubmenuItemArray_size(model->items) - 1)) { model->position++; - if((model->position - model->window_position) > 2 && - model->window_position < (SubmenuItemArray_size(model->items) - 4)) { + if((model->position - model->window_position) > (elements_on_screen - 2) && + model->window_position < + (SubmenuItemArray_size(model->items) - elements_on_screen)) { model->window_position++; } } else { @@ -254,3 +268,13 @@ void submenu_process_ok(Submenu* submenu) { item->callback(item->callback_context, item->index); } } + +void submenu_set_header(Submenu* submenu, const char* header) { + furi_assert(submenu); + + with_view_model( + submenu->view, (SubmenuModel * model) { + model->header = header; + return true; + }); +} diff --git a/applications/gui/modules/submenu.h b/applications/gui/modules/submenu.h index 7350d257..64cb9b96 100644 --- a/applications/gui/modules/submenu.h +++ b/applications/gui/modules/submenu.h @@ -16,12 +16,6 @@ typedef void (*SubmenuItemCallback)(void* context, uint32_t index); */ Submenu* submenu_alloc(); -/** - * @brief Allocate and initialize submenu for vertical display - * This submenu is used to select one option - */ -Submenu* submenu_vertical_alloc(); - /** * @brief Deinitialize and free submenu * @param submenu - Submenu instance @@ -59,11 +53,18 @@ void submenu_clean(Submenu* submenu); /** * @brief Set submenu item selector - * @param submenu - * @param index + * @param submenu + * @param index */ void submenu_set_selected_item(Submenu* submenu, uint32_t index); +/** + * @brief Set optional header for submenu + * @param submenu - submenu entity + * @param header - header to set + */ +void submenu_set_header(Submenu* submenu, const char* header); + #ifdef __cplusplus } #endif diff --git a/applications/irda/irda-app-event.hpp b/applications/irda/irda-app-event.hpp new file mode 100644 index 00000000..93e323d4 --- /dev/null +++ b/applications/irda/irda-app-event.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +class IrdaAppEvent { +public: + enum class Type : uint8_t { + Tick, + Back, + MenuSelected, + DialogExSelected, + NextScene, + IrdaMessageReceived, + TextEditDone, + PopupTimer, + }; + + union { + int32_t menu_index; + DialogExResult dialog_ex_result; + } payload; + + Type type; +}; + diff --git a/applications/irda/irda-app-receiver.cpp b/applications/irda/irda-app-receiver.cpp new file mode 100644 index 00000000..7e792979 --- /dev/null +++ b/applications/irda/irda-app-receiver.cpp @@ -0,0 +1,46 @@ +#include "irda-app.hpp" +#include "irda.h" +#include + +void IrdaAppSignalReceiver::irda_rx_callback(void* ctx, bool level, uint32_t duration) { + IrdaAppEvent event; + const IrdaMessage* irda_message; + IrdaAppSignalReceiver* this_ = static_cast(ctx); + + irda_message = irda_decode(this_->decoder, level, duration); + if(irda_message) { + this_->capture_stop(); + this_->message = *irda_message; + event.type = IrdaAppEvent::Type::IrdaMessageReceived; + osStatus_t result = osMessageQueuePut(this_->event_queue, &event, 0, 0); + furi_check(result == osOK); + } +} + +IrdaAppSignalReceiver::IrdaAppSignalReceiver(void) + : decoder(irda_alloc_decoder()) { +} + +IrdaAppSignalReceiver::~IrdaAppSignalReceiver() { + api_hal_irda_rx_irq_deinit(); + irda_free_decoder(decoder); +} + +void IrdaAppSignalReceiver::capture_once_start(osMessageQueueId_t queue) { + event_queue = queue; + irda_reset_decoder(decoder); + api_hal_irda_rx_irq_init(); + api_hal_irda_rx_irq_set_callback(IrdaAppSignalReceiver::irda_rx_callback, this); +} + +void IrdaAppSignalReceiver::capture_stop(void) { + api_hal_irda_rx_irq_deinit(); +} + +IrdaMessage* IrdaAppSignalReceiver::get_last_message(void) { + return &message; +} + +void IrdaAppSignalReceiver::send_message(const IrdaMessage* message) { + irda_send(message, 1); +} diff --git a/applications/irda/irda-app-receiver.hpp b/applications/irda/irda-app-receiver.hpp new file mode 100644 index 00000000..fc7a3221 --- /dev/null +++ b/applications/irda/irda-app-receiver.hpp @@ -0,0 +1,19 @@ +#include +#include + +class IrdaAppSignalReceiver { +public: + IrdaAppSignalReceiver(void); + ~IrdaAppSignalReceiver(void); + void capture_once_start(osMessageQueueId_t event_queue); + void capture_stop(void); + IrdaMessage* get_last_message(void); + void send_message(const IrdaMessage* message); + +private: + osMessageQueueId_t event_queue; + static void irda_rx_callback(void* ctx, bool level, uint32_t duration); + IrdaHandler* decoder; + IrdaMessage message; +}; + diff --git a/applications/irda/irda-app-remote-manager.cpp b/applications/irda/irda-app-remote-manager.cpp new file mode 100644 index 00000000..7e68b4c9 --- /dev/null +++ b/applications/irda/irda-app-remote-manager.cpp @@ -0,0 +1,130 @@ +#include "irda-app-remote-manager.hpp" +#include "furi.h" +#include +#include + +IrdaAppRemoteManager::IrdaAppRemoteManager() { + // Read from api-hal-storage, and fill remotes +} + +static const std::string default_remote_name = "remote"; + +void IrdaAppRemoteManager::add_button(const char* button_name, const IrdaMessage* message) { + remotes[current_remote_index].buttons.emplace_back(button_name, message); +} + +void IrdaAppRemoteManager::add_remote_with_button( + const char* button_name, + const IrdaMessage* message) { + bool found = true; + int i = 0; + + // find first free common name for remote + do { + found = false; + ++i; + for(const auto& it : remotes) { + if(it.name == (default_remote_name + std::to_string(i))) { + found = true; + break; + } + } + } while(found); + + remotes.emplace_back(default_remote_name + std::to_string(i)); + current_remote_index = remotes.size() - 1; + add_button(button_name, message); +} + +IrdaAppRemote::IrdaAppRemote(std::string name) + : name(name) { +} + +std::vector IrdaAppRemoteManager::get_button_list(void) const { + std::vector name_vector; + auto remote = remotes[current_remote_index]; + name_vector.reserve(remote.buttons.size()); + + for(const auto& it : remote.buttons) { + name_vector.emplace_back(it.name); + } + + // copy elision + return name_vector; +} + +std::vector IrdaAppRemoteManager::get_remote_list() const { + std::vector name_vector; + name_vector.reserve(remotes.size()); + + for(const auto& it : remotes) { + name_vector.push_back(it.name); + } + + // copy elision + return name_vector; +} + +size_t IrdaAppRemoteManager::get_current_remote(void) const { + return current_remote_index; +} + +size_t IrdaAppRemoteManager::get_current_button(void) const { + return current_button_index; +} + +void IrdaAppRemote::add_button( + size_t remote_index, + const char* button_name, + const IrdaMessage* message) { + buttons.emplace_back(button_name, message); +} + +const IrdaMessage* IrdaAppRemoteManager::get_button_data(size_t button_index) const { + furi_check(remotes[current_remote_index].buttons.size() > button_index); + auto& b = remotes[current_remote_index].buttons.at(button_index); + return &b.message; +} + +void IrdaAppRemoteManager::set_current_remote(size_t index) { + furi_check(index < remotes.size()); + current_remote_index = index; +} + +void IrdaAppRemoteManager::set_current_button(size_t index) { + furi_check(current_remote_index < remotes.size()); + furi_check(index < remotes[current_remote_index].buttons.size()); + current_button_index = index; +} + +void IrdaAppRemoteManager::delete_current_remote() { + remotes.erase(remotes.begin() + current_remote_index); + current_remote_index = 0; +} + +void IrdaAppRemoteManager::delete_current_button() { + auto& buttons = remotes[current_remote_index].buttons; + buttons.erase(buttons.begin() + current_button_index); + current_button_index = 0; +} + +std::string IrdaAppRemoteManager::get_current_button_name() { + auto buttons = remotes[current_remote_index].buttons; + return buttons[current_button_index].name; +} + +std::string IrdaAppRemoteManager::get_current_remote_name() { + return remotes[current_remote_index].name; +} + +void IrdaAppRemoteManager::rename_remote(const char* str) { + remotes[current_remote_index].name = str; +} + +void IrdaAppRemoteManager::rename_button(const char* str) { + remotes[current_remote_index].buttons[current_button_index].name = str; +} + +size_t IrdaAppRemoteManager::get_current_remote_buttons_number() { + return remotes[current_remote_index].buttons.size(); +} diff --git a/applications/irda/irda-app-remote-manager.hpp b/applications/irda/irda-app-remote-manager.hpp new file mode 100644 index 00000000..1930ba5b --- /dev/null +++ b/applications/irda/irda-app-remote-manager.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include +#include + +class IrdaAppRemoteButton { + friend class IrdaAppRemoteManager; + std::string name; + IrdaMessage message; +public: + IrdaAppRemoteButton(const char* name, const IrdaMessage* message) + : name(name), message (*message) {} + ~IrdaAppRemoteButton() {} +}; + +class IrdaAppRemote { + friend class IrdaAppRemoteManager; + std::vector buttons; + std::string name; + bool add(const IrdaMessage*); + void add_button(size_t remote_index, const char* button_name, const IrdaMessage* message); +public: + IrdaAppRemote(std::string name); +}; + +class IrdaAppRemoteManager { + size_t current_remote_index; + size_t current_button_index; + std::vector remotes; +public: + std::vector get_remote_list() const; + std::vector get_button_list() const; + void add_remote_with_button(const char* button_name, const IrdaMessage* message); + void add_button(const char* button_name, const IrdaMessage* message); + + size_t get_current_remote(void) const; + size_t get_current_button(void) const; + const IrdaMessage* get_button_data(size_t button_index) const; + void set_current_remote(size_t index); + void set_current_button(size_t index); + void rename_button(const char* str); + void rename_remote(const char* str); + std::string get_current_button_name(); + std::string get_current_remote_name(); + size_t get_current_remote_buttons_number(); + void delete_current_button(); + void delete_current_remote(); + IrdaAppRemoteManager(); + ~IrdaAppRemoteManager() {}; +}; + diff --git a/applications/irda/irda-app-view-manager.cpp b/applications/irda/irda-app-view-manager.cpp new file mode 100644 index 00000000..ce25356f --- /dev/null +++ b/applications/irda/irda-app-view-manager.cpp @@ -0,0 +1,110 @@ +#include "furi.h" +#include "gui/modules/button_menu.h" +#include "gui/modules/dialog_ex.h" +#include "gui/modules/text_input.h" +#include "irda-app.hpp" +#include + +IrdaAppViewManager::IrdaAppViewManager() { + event_queue = osMessageQueueNew(10, sizeof(IrdaAppEvent), NULL); + + view_dispatcher = view_dispatcher_alloc(); + auto callback = cbc::obtain_connector(this, &IrdaAppViewManager::previous_view_callback); + + gui = static_cast(furi_record_open("gui")); + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + + button_menu = button_menu_alloc(); + submenu = submenu_alloc(); + popup = popup_alloc(); + dialog_ex = dialog_ex_alloc(); + text_input = text_input_alloc(); + + add_view(ViewType::ButtonMenu, button_menu_get_view(button_menu)); + add_view(ViewType::Submenu, submenu_get_view(submenu)); + add_view(ViewType::Popup, popup_get_view(popup)); + add_view(ViewType::DialogEx, dialog_ex_get_view(dialog_ex)); + add_view(ViewType::TextInput, text_input_get_view(text_input)); + + view_set_previous_callback(button_menu_get_view(button_menu), callback); + view_set_previous_callback(submenu_get_view(submenu), callback); + view_set_previous_callback(popup_get_view(popup), callback); + view_set_previous_callback(dialog_ex_get_view(dialog_ex), callback); + view_set_previous_callback(text_input_get_view(text_input), callback); +} + +IrdaAppViewManager::~IrdaAppViewManager() { + view_dispatcher_remove_view( + view_dispatcher, static_cast(IrdaAppViewManager::ViewType::ButtonMenu)); + view_dispatcher_remove_view( + view_dispatcher, static_cast(IrdaAppViewManager::ViewType::TextInput)); + view_dispatcher_remove_view( + view_dispatcher, static_cast(IrdaAppViewManager::ViewType::DialogEx)); + view_dispatcher_remove_view( + view_dispatcher, static_cast(IrdaAppViewManager::ViewType::Submenu)); + view_dispatcher_remove_view( + view_dispatcher, static_cast(IrdaAppViewManager::ViewType::Popup)); + + submenu_free(submenu); + popup_free(popup); + button_menu_free(button_menu); + dialog_ex_free(dialog_ex); + text_input_free(text_input); + + view_dispatcher_free(view_dispatcher); + furi_record_close("gui"); + osMessageQueueDelete(event_queue); +} + +void IrdaAppViewManager::switch_to(ViewType type) { + view_dispatcher_switch_to_view(view_dispatcher, static_cast(type)); +} + +TextInput* IrdaAppViewManager::get_text_input() { + return text_input; +} + +DialogEx* IrdaAppViewManager::get_dialog_ex() { + return dialog_ex; +} + +Submenu* IrdaAppViewManager::get_submenu() { + return submenu; +} + +Popup* IrdaAppViewManager::get_popup() { + return popup; +} + +ButtonMenu* IrdaAppViewManager::get_button_menu() { + return button_menu; +} + +osMessageQueueId_t IrdaAppViewManager::get_event_queue() { + return event_queue; +} + +void IrdaAppViewManager::receive_event(IrdaAppEvent* event) { + if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) { + event->type = IrdaAppEvent::Type::Tick; + } +} + +void IrdaAppViewManager::send_event(IrdaAppEvent* event) { + osStatus_t result = osMessageQueuePut(event_queue, event, 0, 0); + furi_check(result == osOK); +} + +uint32_t IrdaAppViewManager::previous_view_callback(void* context) { + if(event_queue != NULL) { + IrdaAppEvent event; + event.type = IrdaAppEvent::Type::Back; + send_event(&event); + } + + return VIEW_IGNORE; +} + +void IrdaAppViewManager::add_view(ViewType view_type, View* view) { + view_dispatcher_add_view(view_dispatcher, static_cast(view_type), view); +} diff --git a/applications/irda/irda-app-view-manager.hpp b/applications/irda/irda-app-view-manager.hpp new file mode 100644 index 00000000..71097e5b --- /dev/null +++ b/applications/irda/irda-app-view-manager.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "gui/modules/button_menu.h" +#include "gui/modules/text_input.h" +#include +#include +#include +#include +#include +#include "irda-app.hpp" + +class IrdaAppViewManager { +public: + enum class ViewType : uint8_t { + DialogEx, + TextInput, + Submenu, + ButtonMenu, + Popup, + }; + + IrdaAppViewManager(); + ~IrdaAppViewManager(); + + void switch_to(ViewType type); + + void receive_event(IrdaAppEvent* event); + void send_event(IrdaAppEvent* event); + + DialogEx* get_dialog_ex(); + Submenu* get_submenu(); + Popup* get_popup(); + TextInput* get_text_input(); + ButtonMenu* get_button_menu(); + + osMessageQueueId_t get_event_queue(); + + uint32_t previous_view_callback(void* context); + +private: + ViewDispatcher* view_dispatcher; + Gui* gui; + TextInput* text_input; + DialogEx* dialog_ex; + Submenu* submenu; + Popup* popup; + ButtonMenu* button_menu; + + osMessageQueueId_t event_queue; + + void add_view(ViewType view_type, View* view); +}; + diff --git a/applications/irda/irda-app.cpp b/applications/irda/irda-app.cpp new file mode 100644 index 00000000..a4d69403 --- /dev/null +++ b/applications/irda/irda-app.cpp @@ -0,0 +1,156 @@ +#include "irda-app.hpp" +#include +#include +#include +#include +#include +#include + +void IrdaApp::run(void) { + IrdaAppEvent event; + bool consumed; + bool exit = false; + + scenes[current_scene]->on_enter(this); + + while(!exit) { + view_manager.receive_event(&event); + + consumed = scenes[current_scene]->on_event(this, &event); + + if(!consumed) { + if(event.type == IrdaAppEvent::Type::Back) { + exit = switch_to_previous_scene(); + } + } + }; + + scenes[current_scene]->on_exit(this); +}; + +IrdaAppViewManager* IrdaApp::get_view_manager() { + return &view_manager; +} + +void IrdaApp::set_learn_new_remote(bool value) { + learn_new_remote = value; +} + +bool IrdaApp::get_learn_new_remote() { + return learn_new_remote; +} + +void IrdaApp::switch_to_next_scene(Scene next_scene) { + previous_scenes_list.push_front(current_scene); + switch_to_next_scene_without_saving(next_scene); +} + +void IrdaApp::switch_to_next_scene_without_saving(Scene next_scene) { + if(next_scene != Scene::Exit) { + scenes[current_scene]->on_exit(this); + current_scene = next_scene; + scenes[current_scene]->on_enter(this); + } +} + +void IrdaApp::search_and_switch_to_previous_scene(const std::initializer_list& scenes_list) { + Scene previous_scene = Scene::Start; + bool scene_found = false; + + while(!scene_found) { + previous_scene = get_previous_scene(); + for(Scene element : scenes_list) { + if(previous_scene == element) { + scene_found = true; + break; + } + } + } + + scenes[current_scene]->on_exit(this); + current_scene = previous_scene; + scenes[current_scene]->on_enter(this); +} + +bool IrdaApp::switch_to_previous_scene(uint8_t count) { + Scene previous_scene = Scene::Start; + + for(uint8_t i = 0; i < count; i++) previous_scene = get_previous_scene(); + + if(previous_scene == Scene::Exit) return true; + + scenes[current_scene]->on_exit(this); + current_scene = previous_scene; + scenes[current_scene]->on_enter(this); + return false; +} + +IrdaApp::Scene IrdaApp::get_previous_scene() { + Scene scene = Scene::Exit; + + if(!previous_scenes_list.empty()) { + scene = previous_scenes_list.front(); + previous_scenes_list.pop_front(); + } + + return scene; +} + +IrdaAppRemoteManager* IrdaApp::get_remote_manager() { + return &remote_manager; +} + +IrdaAppSignalReceiver* IrdaApp::get_receiver() { + return &receiver; +} + +void IrdaApp::set_text_store(uint8_t index, const char* text...) { + furi_check(index < text_store_max); + + va_list args; + va_start(args, text); + + vsnprintf(text_store[index], text_store_size, text, args); + + va_end(args); +} + +char* IrdaApp::get_text_store(uint8_t index) { + furi_check(index < text_store_max); + + return text_store[index]; +} + +uint8_t IrdaApp::get_text_store_size() { + return text_store_size; +} + +void IrdaApp::text_input_callback(void* context, char* text) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + event.type = IrdaAppEvent::Type::TextEditDone; + app->get_view_manager()->send_event(&event); +} + +void IrdaApp::popup_callback(void* context) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + event.type = IrdaAppEvent::Type::PopupTimer; + app->get_view_manager()->send_event(&event); +} + +void IrdaApp::set_edit_element(IrdaApp::EditElement value) { + element = value; +} + +IrdaApp::EditElement IrdaApp::get_edit_element(void) { + return element; +} + +void IrdaApp::set_edit_action(IrdaApp::EditAction value) { + action = value; +} + +IrdaApp::EditAction IrdaApp::get_edit_action(void) { + return action; +} diff --git a/applications/irda/irda-app.hpp b/applications/irda/irda-app.hpp new file mode 100644 index 00000000..c1e8ce62 --- /dev/null +++ b/applications/irda/irda-app.hpp @@ -0,0 +1,108 @@ +#pragma once +#include +#include +#include +#include "irda-app-event.hpp" +#include "scene/irda-app-scene.hpp" +#include "irda-app-view-manager.hpp" +#include "irda-app-remote-manager.hpp" +#include "irda-app-receiver.hpp" +#include +#include + + +class IrdaApp { +public: + enum class EditElement : uint8_t { + Button, + Remote, + }; + enum class EditAction : uint8_t { + Rename, + Delete, + }; + enum class Scene : uint8_t { + Exit, + Start, + Universal, + UniversalTV, + UniversalAudio, + UniversalAirConditioner, + Learn, + LearnSuccess, + LearnEnterName, + LearnDone, + LearnDoneAfter, + Remote, + RemoteList, + Edit, + EditKeySelect, + EditRename, + EditDelete, + EditRenameDone, + EditDeleteDone, + }; + + void run(void); + void switch_to_next_scene(Scene index); + void switch_to_next_scene_without_saving(Scene index); + bool switch_to_previous_scene(uint8_t count = 1); + Scene get_previous_scene(); + IrdaAppViewManager* get_view_manager(); + IrdaAppSignalReceiver* get_receiver(); + void set_text_store(uint8_t index, const char* text...); + char* get_text_store(uint8_t index); + uint8_t get_text_store_size(); + IrdaAppRemoteManager* get_remote_manager(); + void search_and_switch_to_previous_scene(const std::initializer_list& scenes_list); + + void set_edit_element(EditElement value); + EditElement get_edit_element(void); + + void set_edit_action(EditAction value); + EditAction get_edit_action(void); + + bool get_learn_new_remote(); + void set_learn_new_remote(bool value); + + static void text_input_callback(void* context, char* text); + static void popup_callback(void* context); + + IrdaApp() {} + ~IrdaApp() { + for (auto &it : scenes) + delete it.second; + } +private: + static const uint8_t text_store_size = 128; + static const uint8_t text_store_max = 2; + char text_store[text_store_max][text_store_size + 1]; + bool learn_new_remote; + EditElement element; + EditAction action; + + IrdaAppSignalReceiver receiver; + IrdaAppViewManager view_manager; + IrdaAppRemoteManager remote_manager; + + std::forward_list previous_scenes_list; + Scene current_scene = Scene::Start; + + std::map scenes = { + {Scene::Start, new IrdaAppSceneStart()}, + {Scene::Universal, new IrdaAppSceneUniversal()}, + {Scene::Learn, new IrdaAppSceneLearn()}, + {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()}, + {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()}, + {Scene::LearnDone, new IrdaAppSceneLearnDone()}, + {Scene::LearnDoneAfter, new IrdaAppSceneLearnDoneAfter()}, + {Scene::Remote, new IrdaAppSceneRemote()}, + {Scene::RemoteList, new IrdaAppSceneRemoteList()}, + {Scene::Edit, new IrdaAppSceneEdit()}, + {Scene::EditKeySelect, new IrdaAppSceneEditKeySelect()}, + {Scene::EditRename, new IrdaAppSceneEditRename()}, + {Scene::EditDelete, new IrdaAppSceneEditDelete()}, + {Scene::EditRenameDone, new IrdaAppSceneEditRenameDone()}, + {Scene::EditDeleteDone, new IrdaAppSceneEditDeleteDone()}, + }; +}; diff --git a/applications/irda/irda-decoder/irda-decoder-nec.c b/applications/irda/irda-decoder/irda-decoder-nec.c deleted file mode 100644 index 4cd5880f..00000000 --- a/applications/irda/irda-decoder/irda-decoder-nec.c +++ /dev/null @@ -1,146 +0,0 @@ -#include "irda-decoder-nec.h" -#include "string.h" - -const uint32_t PREAMBULA_HIGH_MIN = 9000 - 900; -const uint32_t PREAMBULA_HIGH_MAX = 9000 + 900; - -const uint32_t PREAMBULA_LOW_MIN = 4500 - 450; -const uint32_t PREAMBULA_LOW_MAX = 4500 + 450; - -const uint32_t PREAMBULA_RETRY_LOW_MIN = 2500 - 350; -const uint32_t PREAMBULA_RETRY_LOW_MAX = 2500 + 250; - -const uint32_t BIT_HIGH_MIN = 560 - 100; -const uint32_t BIT_HIGH_MAX = 560 + 100; - -const uint32_t BIT_LOW_ONE_MIN = 1690 - 200; -const uint32_t BIT_LOW_ONE_MAX = 1690 + 200; - -const uint32_t BIT_LOW_ZERO_MIN = 560 - 100; -const uint32_t BIT_LOW_ZERO_MAX = 560 + 100; - -#define SET_STATE(_state) \ - { decoder->state = _state; } - -#define TIME_FIT(_prefix) ((time > _prefix##_MIN) && (time < _prefix##_MAX)) - -#ifndef MIN -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#endif - -bool save_decoder_nec_data(IrDANecDecoder* decoder, IrDADecoderOutputData* out) { - bool result = false; - - if((decoder->data.simple.cmd + decoder->data.simple.cmd_inverse) == 0xFF) { - if(out->data_length < sizeof(IrDANecDataType)) { - out->flags |= IRDA_TOO_SHORT_BUFFER; - } - - memcpy(out->data, &decoder->data.data, MIN(sizeof(IrDANecDataType), out->data_length)); - result = true; - } else { - reset_decoder_nec(decoder); - } - - return result; -} - -bool process_decoder_nec( - IrDANecDecoder* decoder, - bool polarity, - uint32_t time, - IrDADecoderOutputData* out) { - bool error = true; - bool result = false; - - switch(decoder->state) { - case(WAIT_PREAMBULA_HIGH): - if(polarity) { - if(TIME_FIT(PREAMBULA_HIGH)) { - SET_STATE(WAIT_PREAMBULA_LOW); - } - } - // any values before preambula start is correct - error = false; - break; - case(WAIT_PREAMBULA_LOW): - if(!polarity) { - if(TIME_FIT(PREAMBULA_LOW)) { - // new data, reset storage - reset_decoder_nec(decoder); - SET_STATE(WAIT_BIT_HIGH); - error = false; - } else if(TIME_FIT(PREAMBULA_RETRY_LOW)) { - // wait for data repeat command - SET_STATE(WAIT_RETRY_HIGH); - error = false; - } - } - break; - case(WAIT_RETRY_HIGH): - if(polarity) { - if(TIME_FIT(BIT_HIGH)) { - SET_STATE(WAIT_PREAMBULA_HIGH); - - // repeat event - result = save_decoder_nec_data(decoder, out); - out->flags |= IRDA_REPEAT; - error = false; - } - } - break; - case(WAIT_BIT_HIGH): - if(polarity) { - if(TIME_FIT(BIT_HIGH)) { - SET_STATE(WAIT_BIT_LOW); - error = false; - } - } - break; - case(WAIT_BIT_STOP_HIGH): - if(polarity) { - if(TIME_FIT(BIT_HIGH)) { - SET_STATE(WAIT_PREAMBULA_HIGH); - - // message end event - result = save_decoder_nec_data(decoder, out); - error = false; - } - } - break; - case(WAIT_BIT_LOW): - if(!polarity) { - int8_t bit = -1; - if(TIME_FIT(BIT_LOW_ZERO)) { - SET_STATE(WAIT_BIT_HIGH); - bit = 0; - error = false; - } else if(TIME_FIT(BIT_LOW_ONE)) { - SET_STATE(WAIT_BIT_HIGH); - bit = 1; - error = false; - } - - if(bit != -1) { - decoder->data.data |= (bit << decoder->current_data_index); - decoder->current_data_index++; - - if(decoder->current_data_index > 31) { - decoder->current_data_index = 0; - SET_STATE(WAIT_BIT_STOP_HIGH); - } - } - } - break; - } - - if(error) reset_decoder_nec(decoder); - - return result; -} - -void reset_decoder_nec(IrDANecDecoder* decoder) { - decoder->state = WAIT_PREAMBULA_HIGH; - decoder->data.data = 0; - decoder->current_data_index = 0; -} \ No newline at end of file diff --git a/applications/irda/irda-decoder/irda-decoder-nec.h b/applications/irda/irda-decoder/irda-decoder-nec.h deleted file mode 100644 index 7a6a325a..00000000 --- a/applications/irda/irda-decoder/irda-decoder-nec.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include -#include -#include "irda-decoder-types.h" - -typedef enum { - WAIT_PREAMBULA_HIGH, - WAIT_PREAMBULA_LOW, - WAIT_RETRY_HIGH, - WAIT_BIT_HIGH, - WAIT_BIT_LOW, - WAIT_BIT_STOP_HIGH, -} IrDANecDecoderState; - -typedef struct { - uint8_t addr2; - uint8_t addr1; - uint8_t cmd_inverse; - uint8_t cmd; -} IrDANecData; - -typedef uint32_t IrDANecDataType; - -typedef struct { - union { - IrDANecData simple; - IrDANecDataType data; - } data; - uint8_t current_data_index; - IrDANecDecoderState state; -} IrDANecDecoder; - -bool process_decoder_nec( - IrDANecDecoder* decoder, - bool polarity, - uint32_t time, - IrDADecoderOutputData* out); - -void reset_decoder_nec(IrDANecDecoder* decoder); \ No newline at end of file diff --git a/applications/irda/irda-decoder/irda-decoder-types.h b/applications/irda/irda-decoder/irda-decoder-types.h deleted file mode 100644 index 0ea0dbb8..00000000 --- a/applications/irda/irda-decoder/irda-decoder-types.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include - -typedef enum { IRDA_UNKNOWN, IRDA_NEC, IRDA_SAMSUNG } IrDAProtocolType; -typedef enum { IRDA_REPEAT = (1 << 0), IRDA_TOO_SHORT_BUFFER = (1 << 1) } IrDAProtocolFlags; - -typedef struct { - IrDAProtocolType protocol; - uint8_t flags; - uint8_t* data; /** < ponter to output data, filled by app */ - uint32_t data_length; /** < output data length, filled by app */ -} IrDADecoderOutputData; \ No newline at end of file diff --git a/applications/irda/irda-decoder/irda-decoder.c b/applications/irda/irda-decoder/irda-decoder.c deleted file mode 100644 index 824f5055..00000000 --- a/applications/irda/irda-decoder/irda-decoder.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "irda-decoder.h" - -IrDADecoder* alloc_decoder(void) { - IrDADecoder* decoder = malloc(sizeof(IrDADecoder)); - - // init decoders - reset_decoder_nec(&decoder->nec); - - return decoder; -} - -void free_decoder(IrDADecoder* decoder) { - free(decoder); -} - -bool process_decoder( - IrDADecoder* decoder, - bool start_polarity, - uint32_t* timings, - uint32_t timings_length, - IrDADecoderOutputData* out) { - bool result = false; - - // zero result - memset(out->data, 0, out->data_length); - out->protocol = IRDA_UNKNOWN; - out->flags = 0; - - // process data - for(uint32_t timings_index = 0; timings_index < timings_length; timings_index++) { - if(process_decoder_nec(&decoder->nec, start_polarity, timings[timings_index], out)) { - out->protocol = IRDA_NEC; - result = true; - break; - } - - start_polarity = !start_polarity; - } - - return result; -} \ No newline at end of file diff --git a/applications/irda/irda-decoder/irda-decoder.h b/applications/irda/irda-decoder/irda-decoder.h deleted file mode 100644 index 78f4415c..00000000 --- a/applications/irda/irda-decoder/irda-decoder.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include -#include "irda-decoder-nec.h" -#include "irda-decoder-types.h" - -typedef struct { - IrDANecDecoder nec; -} IrDADecoder; - -IrDADecoder* alloc_decoder(void); -void free_decoder(IrDADecoder* decoder); -bool process_decoder( - IrDADecoder* decoder, - bool start_polarity, - uint32_t* timings, - uint32_t timings_length, - IrDADecoderOutputData* out); \ No newline at end of file diff --git a/applications/irda/irda-runner.cpp b/applications/irda/irda-runner.cpp new file mode 100644 index 00000000..9a26fa13 --- /dev/null +++ b/applications/irda/irda-runner.cpp @@ -0,0 +1,9 @@ +#include "irda-app.hpp" + +extern "C" int32_t irda(void* p) { + IrdaApp* app = new IrdaApp(); + app->run(); + delete app; + + return 0; +} diff --git a/applications/irda/irda_app.c b/applications/irda/irda_app_old.c similarity index 99% rename from applications/irda/irda_app.c rename to applications/irda/irda_app_old.c index d5aa03a5..991d068e 100644 --- a/applications/irda/irda_app.c +++ b/applications/irda/irda_app_old.c @@ -290,7 +290,7 @@ void irda_rx_callback(void* ctx, bool level, uint32_t duration) { } } -int32_t irda(void* p) { +int32_t irda2(void* p) { osMessageQueueId_t event_queue = osMessageQueueNew(32, sizeof(AppEvent), NULL); State _state; diff --git a/applications/irda/irda_protocols.h b/applications/irda/irda_protocols.h deleted file mode 100644 index ceb96535..00000000 --- a/applications/irda/irda_protocols.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -// our tx pin is TIM2_CH4 -extern TIM_HandleTypeDef TIM_A; - -#define RC5_CARRIER_FREQUENCY 36000 -#define RC5_DUTY_CYCLE 0.33 - -#define RC6_CARRIER_FREQUENCY 36000 -#define RC6_DUTY_CYCLE 0.33 - -#define SAMSUNG_CARRIER_FREQUENCY 37900 -#define SAMSUNG_DUTY_CYCLE 0.33 - -#define NEC_CARRIER_FREQUENCY 38000 -#define NEC_DUTY_CYCLE 0.33 - -#define SIRC_CARRIER_FREQUENCY 40000 -#define SIRC_DUTY_CYCLE 0.5 \ No newline at end of file diff --git a/applications/irda/scene/irda-app-scene-edit-delete-done.cpp b/applications/irda/scene/irda-app-scene-edit-delete-done.cpp new file mode 100644 index 00000000..927ac9ea --- /dev/null +++ b/applications/irda/scene/irda-app-scene-edit-delete-done.cpp @@ -0,0 +1,35 @@ +#include "../irda-app.hpp" + +void IrdaAppSceneEditDeleteDone::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Popup* popup = view_manager->get_popup(); + + popup_set_icon(popup, 0, 2, I_DolphinMafia_115x62); + popup_set_text(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + + popup_set_callback(popup, IrdaApp::popup_callback); + popup_set_context(popup, app); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); +} + +bool IrdaAppSceneEditDeleteDone::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::PopupTimer) { + if(app->get_edit_element() == IrdaApp::EditElement::Remote) { + app->search_and_switch_to_previous_scene( + {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + } else { + app->search_and_switch_to_previous_scene({IrdaApp::Scene::Remote}); + } + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneEditDeleteDone::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-edit-delete.cpp b/applications/irda/scene/irda-app-scene-edit-delete.cpp new file mode 100644 index 00000000..929dd2d4 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-edit-delete.cpp @@ -0,0 +1,81 @@ +#include "../irda-app.hpp" +#include "irda.h" +#include +#include + +static void dialog_result_callback(DialogExResult result, void* context) { + auto app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::DialogExSelected; + event.payload.dialog_ex_result = result; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + DialogEx* dialog_ex = view_manager->get_dialog_ex(); + + auto remote_manager = app->get_remote_manager(); + + if(app->get_edit_element() == IrdaApp::EditElement::Button) { + auto message = remote_manager->get_button_data(remote_manager->get_current_button()); + dialog_ex_set_header(dialog_ex, "Delete button?", 64, 6, AlignCenter, AlignCenter); + app->set_text_store( + 0, + "%s\n%s\nA=0x%0*lX C=0x%0*lX", + remote_manager->get_current_button_name().c_str(), + irda_get_protocol_name(message->protocol), + irda_get_protocol_address_length(message->protocol), + message->address, + irda_get_protocol_command_length(message->protocol), + message->command); + } else { + dialog_ex_set_header(dialog_ex, "Delete remote?", 64, 6, AlignCenter, AlignCenter); + app->set_text_store( + 0, + "%s\n with %lu buttons", + remote_manager->get_current_remote_name().c_str(), + remote_manager->get_current_remote_buttons_number()); + } + + dialog_ex_set_text(dialog_ex, app->get_text_store(0), 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, -1, -1, I_ButtonCenter_7x7); + dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_right_button_text(dialog_ex, "Delete"); + dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); + dialog_ex_set_context(dialog_ex, app); + + view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); +} + +bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::DialogExSelected) { + switch(event->payload.dialog_ex_result) { + case DialogExResultLeft: + app->switch_to_previous_scene(); + break; + case DialogExResultCenter: + furi_assert(0); + break; + case DialogExResultRight: + auto remote_manager = app->get_remote_manager(); + if(app->get_edit_element() == IrdaApp::EditElement::Remote) { + remote_manager->delete_current_remote(); + } else { + remote_manager->delete_current_button(); + } + + app->switch_to_next_scene(IrdaApp::Scene::EditDeleteDone); + break; + } + } + + return consumed; +} + +void IrdaAppSceneEditDelete::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-edit-key-select.cpp b/applications/irda/scene/irda-app-scene-edit-key-select.cpp new file mode 100644 index 00000000..e0ead609 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-edit-key-select.cpp @@ -0,0 +1,54 @@ +#include "../irda-app.hpp" +#include "gui/modules/submenu.h" + +static void submenu_callback(void* context, uint32_t index) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneEditKeySelect::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + int i = 0; + + const char* header = app->get_edit_action() == IrdaApp::EditAction::Rename ? "Rename key:" : + "Delete key:"; + submenu_set_header(submenu, header); + + auto remote_manager = app->get_remote_manager(); + buttons_names = remote_manager->get_button_list(); + for(const auto& it : buttons_names) { + submenu_add_item(submenu, it.c_str(), i++, submenu_callback, app); + } + + view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); +} + +bool IrdaAppSceneEditKeySelect::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::MenuSelected) { + auto remote_manager = app->get_remote_manager(); + remote_manager->set_current_button(event->payload.menu_index); + consumed = true; + if(app->get_edit_action() == IrdaApp::EditAction::Rename) { + app->switch_to_next_scene(IrdaApp::Scene::EditRename); + } else { + app->switch_to_next_scene(IrdaApp::Scene::EditDelete); + } + } + + return consumed; +} + +void IrdaAppSceneEditKeySelect::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_clean(submenu); +} diff --git a/applications/irda/scene/irda-app-scene-edit-rename-done.cpp b/applications/irda/scene/irda-app-scene-edit-rename-done.cpp new file mode 100644 index 00000000..7e95f2f0 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-edit-rename-done.cpp @@ -0,0 +1,31 @@ +#include "../irda-app.hpp" + +void IrdaAppSceneEditRenameDone::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Popup* popup = view_manager->get_popup(); + + popup_set_icon(popup, 32, 5, I_DolphinNice_96x59); + + popup_set_text(popup, "Saved!", 13, 22, AlignLeft, AlignTop); + + popup_set_callback(popup, IrdaApp::popup_callback); + popup_set_context(popup, app); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); +} + +bool IrdaAppSceneEditRenameDone::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::PopupTimer) { + app->switch_to_next_scene(IrdaApp::Scene::Remote); + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneEditRenameDone::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-edit-rename.cpp b/applications/irda/scene/irda-app-scene-edit-rename.cpp new file mode 100644 index 00000000..0ade41ca --- /dev/null +++ b/applications/irda/scene/irda-app-scene-edit-rename.cpp @@ -0,0 +1,46 @@ +#include "../irda-app.hpp" +#include + +void IrdaAppSceneEditRename::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + TextInput* text_input = view_manager->get_text_input(); + + auto remote_manager = app->get_remote_manager(); + if(app->get_edit_element() == IrdaApp::EditElement::Button) { + auto button_name = remote_manager->get_current_button_name(); + strncpy(app->get_text_store(0), button_name.c_str(), app->get_text_store_size()); + } else { + auto remote_name = remote_manager->get_current_remote_name(); + strncpy(app->get_text_store(0), remote_name.c_str(), app->get_text_store_size()); + } + + text_input_set_header_text(text_input, "Name the key"); + text_input_set_result_callback( + text_input, + IrdaApp::text_input_callback, + app, + app->get_text_store(0), + app->get_text_store_size()); + + view_manager->switch_to(IrdaAppViewManager::ViewType::TextInput); +} + +bool IrdaAppSceneEditRename::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::TextEditDone) { + auto remote_manager = app->get_remote_manager(); + if(app->get_edit_element() == IrdaApp::EditElement::Button) { + remote_manager->rename_button(app->get_text_store(0)); + } else { + remote_manager->rename_remote(app->get_text_store(0)); + } + app->switch_to_next_scene_without_saving(IrdaApp::Scene::EditRenameDone); + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneEditRename::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-edit.cpp b/applications/irda/scene/irda-app-scene-edit.cpp new file mode 100644 index 00000000..cd8a84fc --- /dev/null +++ b/applications/irda/scene/irda-app-scene-edit.cpp @@ -0,0 +1,74 @@ +#include "../irda-app.hpp" + +typedef enum { + SubmenuIndexAddKey, + SubmenuIndexRenameKey, + SubmenuIndexDeleteKey, + SubmenuIndexRenameRemote, + SubmenuIndexDeleteRemote, +} SubmenuIndex; + +static void submenu_callback(void* context, uint32_t index) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneEdit::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_add_item(submenu, "Add key", SubmenuIndexAddKey, submenu_callback, app); + submenu_add_item(submenu, "Rename key", SubmenuIndexRenameKey, submenu_callback, app); + submenu_add_item(submenu, "Delete key", SubmenuIndexDeleteKey, submenu_callback, app); + submenu_add_item(submenu, "Rename remote", SubmenuIndexRenameRemote, submenu_callback, app); + submenu_add_item(submenu, "Delete remote", SubmenuIndexDeleteRemote, submenu_callback, app); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); +} + +bool IrdaAppSceneEdit::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::MenuSelected) { + switch(event->payload.menu_index) { + case SubmenuIndexAddKey: + app->switch_to_next_scene(IrdaApp::Scene::Learn); + break; + case SubmenuIndexRenameKey: + app->set_edit_action(IrdaApp::EditAction::Rename); + app->set_edit_element(IrdaApp::EditElement::Button); + app->switch_to_next_scene(IrdaApp::Scene::EditKeySelect); + break; + case SubmenuIndexDeleteKey: + app->set_edit_action(IrdaApp::EditAction::Delete); + app->set_edit_element(IrdaApp::EditElement::Button); + app->switch_to_next_scene(IrdaApp::Scene::EditKeySelect); + break; + case SubmenuIndexRenameRemote: + app->set_edit_action(IrdaApp::EditAction::Rename); + app->set_edit_element(IrdaApp::EditElement::Remote); + app->switch_to_next_scene(IrdaApp::Scene::EditRename); + break; + case SubmenuIndexDeleteRemote: + app->set_edit_action(IrdaApp::EditAction::Delete); + app->set_edit_element(IrdaApp::EditElement::Remote); + app->switch_to_next_scene(IrdaApp::Scene::EditDelete); + break; + } + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneEdit::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_clean(submenu); +} diff --git a/applications/irda/scene/irda-app-scene-learn-done-after.cpp b/applications/irda/scene/irda-app-scene-learn-done-after.cpp new file mode 100644 index 00000000..75760333 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-learn-done-after.cpp @@ -0,0 +1,34 @@ +#include "../irda-app.hpp" +#include +#include +#include + +void IrdaAppSceneLearnDoneAfter::on_enter(IrdaApp* app) { + auto view_manager = app->get_view_manager(); + auto popup = view_manager->get_popup(); + + popup_set_icon(popup, 0, 30, I_IrdaSendShort_128x34); + popup_set_text( + popup, "Get ready!\nPoint flipper at target.", 64, 16, AlignCenter, AlignCenter); + + popup_set_callback(popup, IrdaApp::popup_callback); + popup_set_context(popup, app); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); +} + +bool IrdaAppSceneLearnDoneAfter::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::PopupTimer) { + app->switch_to_next_scene(IrdaApp::Scene::Remote); + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneLearnDoneAfter::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-learn-done.cpp b/applications/irda/scene/irda-app-scene-learn-done.cpp new file mode 100644 index 00000000..9aff4f16 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-learn-done.cpp @@ -0,0 +1,42 @@ +#include "../irda-app.hpp" +#include +#include + +void IrdaAppSceneLearnDone::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Popup* popup = view_manager->get_popup(); + + popup_set_icon(popup, 32, 5, I_DolphinNice_96x59); + + if(app->get_learn_new_remote()) { + popup_set_text(popup, "New remote\ncreated!", 5, 7, AlignLeft, AlignTop); + } else { + popup_set_text(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + } + + popup_set_callback(popup, IrdaApp::popup_callback); + popup_set_context(popup, app); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); +} + +bool IrdaAppSceneLearnDone::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::PopupTimer) { + if(app->get_learn_new_remote()) { + app->switch_to_next_scene(IrdaApp::Scene::LearnDoneAfter); + } else { + app->switch_to_next_scene(IrdaApp::Scene::Remote); + } + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneLearnDone::on_exit(IrdaApp* app) { + app->set_learn_new_remote(false); +} diff --git a/applications/irda/scene/irda-app-scene-learn-enter-name.cpp b/applications/irda/scene/irda-app-scene-learn-enter-name.cpp new file mode 100644 index 00000000..49b581d1 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-learn-enter-name.cpp @@ -0,0 +1,51 @@ +#include "../irda-app.hpp" +#include "gui/modules/text_input.h" +#include +#include +#include + +void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + TextInput* text_input = view_manager->get_text_input(); + + auto receiver = app->get_receiver(); + auto message = receiver->get_last_message(); + + app->set_text_store( + 0, + "%.4s_%0*lX", + irda_get_protocol_name(message->protocol), + irda_get_protocol_command_length(message->protocol), + message->command); + + text_input_set_header_text(text_input, "Name the key"); + text_input_set_result_callback( + text_input, + IrdaApp::text_input_callback, + app, + app->get_text_store(0), + app->get_text_store_size()); + + view_manager->switch_to(IrdaAppViewManager::ViewType::TextInput); +} + +bool IrdaAppSceneLearnEnterName::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::TextEditDone) { + auto remote_manager = app->get_remote_manager(); + auto receiver = app->get_receiver(); + if(app->get_learn_new_remote()) { + remote_manager->add_remote_with_button( + app->get_text_store(0), receiver->get_last_message()); + } else { + remote_manager->add_button(app->get_text_store(0), receiver->get_last_message()); + } + + app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnDone); + } + return consumed; +} + +void IrdaAppSceneLearnEnterName::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-learn-success.cpp b/applications/irda/scene/irda-app-scene-learn-success.cpp new file mode 100644 index 00000000..12a7f9ad --- /dev/null +++ b/applications/irda/scene/irda-app-scene-learn-success.cpp @@ -0,0 +1,63 @@ +#include "../irda-app.hpp" +#include "irda.h" +#include +#include + +static void dialog_result_callback(DialogExResult result, void* context) { + auto app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::DialogExSelected; + event.payload.dialog_ex_result = result; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + DialogEx* dialog_ex = view_manager->get_dialog_ex(); + + auto receiver = app->get_receiver(); + auto message = receiver->get_last_message(); + + app->set_text_store(0, "%s", irda_get_protocol_name(message->protocol)); + app->set_text_store( + 1, + "A: 0x%0*lX\nC: 0x%0*lX\n", + irda_get_protocol_address_length(message->protocol), + message->address, + irda_get_protocol_command_length(message->protocol), + message->command); + dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 10, AlignCenter, AlignCenter); + dialog_ex_set_text(dialog_ex, app->get_text_store(1), 75, 23, AlignLeft, AlignTop); + dialog_ex_set_left_button_text(dialog_ex, "Retry"); + dialog_ex_set_right_button_text(dialog_ex, "Save"); + dialog_ex_set_icon(dialog_ex, 0, 1, I_DolphinExcited_64x63); + dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); + dialog_ex_set_context(dialog_ex, app); + + view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); +} + +bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::DialogExSelected) { + switch(event->payload.dialog_ex_result) { + case DialogExResultLeft: + app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); + break; + case DialogExResultCenter: + furi_assert(0); + break; + case DialogExResultRight: + app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); + break; + } + } + + return consumed; +} + +void IrdaAppSceneLearnSuccess::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-learn.cpp b/applications/irda/scene/irda-app-scene-learn.cpp new file mode 100644 index 00000000..993575bb --- /dev/null +++ b/applications/irda/scene/irda-app-scene-learn.cpp @@ -0,0 +1,31 @@ +#include "../irda-app.hpp" + +void IrdaAppSceneLearn::on_enter(IrdaApp* app) { + auto view_manager = app->get_view_manager(); + auto receiver = app->get_receiver(); + auto event_queue = view_manager->get_event_queue(); + + receiver->capture_once_start(event_queue); + + auto popup = view_manager->get_popup(); + + popup_set_icon(popup, 0, 32, I_IrdaLearnShort_128x31); + popup_set_text( + popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); + popup_set_callback(popup, NULL); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); +} + +bool IrdaAppSceneLearn::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::IrdaMessageReceived) { + app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnSuccess); + } + + return consumed; +} + +void IrdaAppSceneLearn::on_exit(IrdaApp* app) { +} diff --git a/applications/irda/scene/irda-app-scene-remote-list.cpp b/applications/irda/scene/irda-app-scene-remote-list.cpp new file mode 100644 index 00000000..51afcb48 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-remote-list.cpp @@ -0,0 +1,59 @@ +#include "../irda-app.hpp" + +typedef enum { + SubmenuIndexPlus = -1, +} SubmenuIndex; + +static void submenu_callback(void* context, uint32_t index) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + auto remote_manager = app->get_remote_manager(); + int i = 0; + + remote_names = remote_manager->get_remote_list(); + for(auto& a : remote_names) { + submenu_add_item(submenu, a.c_str(), i++, submenu_callback, app); + } + submenu_add_item( + submenu, " +", SubmenuIndexPlus, submenu_callback, app); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); +} + +bool IrdaAppSceneRemoteList::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::MenuSelected) { + switch(event->payload.menu_index) { + case SubmenuIndexPlus: + app->set_learn_new_remote(true); + app->switch_to_next_scene(IrdaApp::Scene::Learn); + break; + default: + auto remote_manager = app->get_remote_manager(); + remote_manager->set_current_remote(event->payload.menu_index); + app->switch_to_next_scene(IrdaApp::Scene::Remote); + consumed = true; + break; + } + } + + return consumed; +} + +void IrdaAppSceneRemoteList::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_clean(submenu); +} diff --git a/applications/irda/scene/irda-app-scene-remote.cpp b/applications/irda/scene/irda-app-scene-remote.cpp new file mode 100644 index 00000000..ba65ead0 --- /dev/null +++ b/applications/irda/scene/irda-app-scene-remote.cpp @@ -0,0 +1,75 @@ +#include "../irda-app.hpp" +#include "gui/modules/button_menu.h" + +typedef enum { + ButtonIndexPlus = -2, + ButtonIndexEdit = -1, +} ButtonIndex; + +static void button_menu_callback(void* context, int32_t index) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneRemote::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + ButtonMenu* button_menu = view_manager->get_button_menu(); + auto remote_manager = app->get_remote_manager(); + int i = 0; + + buttons_names = remote_manager->get_button_list(); + + i = 0; + for(auto& name : buttons_names) { + button_menu_add_item( + button_menu, name.c_str(), i++, button_menu_callback, ButtonMenuItemTypeCommon, app); + } + + button_menu_add_item( + button_menu, "+", ButtonIndexPlus, button_menu_callback, ButtonMenuItemTypeControl, app); + button_menu_add_item( + button_menu, "Edit", ButtonIndexEdit, button_menu_callback, ButtonMenuItemTypeControl, app); + + app->set_text_store(0, "%s", remote_manager->get_current_remote_name().c_str()); + button_menu_set_header(button_menu, app->get_text_store(0)); + view_manager->switch_to(IrdaAppViewManager::ViewType::ButtonMenu); +} + +bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = true; + + if(event->type == IrdaAppEvent::Type::MenuSelected) { + switch(event->payload.menu_index) { + case ButtonIndexPlus: + app->switch_to_next_scene(IrdaApp::Scene::Learn); + break; + case ButtonIndexEdit: + app->switch_to_next_scene(IrdaApp::Scene::Edit); + break; + default: + auto remote_manager = app->get_remote_manager(); + auto message = remote_manager->get_button_data(event->payload.menu_index); + app->get_receiver()->send_message(message); + break; + } + } else if(event->type == IrdaAppEvent::Type::Back) { + app->search_and_switch_to_previous_scene( + {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + } else { + consumed = false; + } + + return consumed; +} + +void IrdaAppSceneRemote::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + ButtonMenu* button_menu = view_manager->get_button_menu(); + + button_menu_clean(button_menu); +} diff --git a/applications/irda/scene/irda-app-scene-start.cpp b/applications/irda/scene/irda-app-scene-start.cpp new file mode 100644 index 00000000..7efb6ccb --- /dev/null +++ b/applications/irda/scene/irda-app-scene-start.cpp @@ -0,0 +1,59 @@ +#include "../irda-app.hpp" + +typedef enum { + SubmenuIndexUniversalLibrary, + SubmenuIndexLearnNewRemote, + SubmenuIndexSavedRemotes, +} SubmenuIndex; + +static void submenu_callback(void* context, uint32_t index) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneStart::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_add_item( + submenu, "Universal library", SubmenuIndexUniversalLibrary, submenu_callback, app); + submenu_add_item( + submenu, "Learn new remote", SubmenuIndexLearnNewRemote, submenu_callback, app); + submenu_add_item(submenu, "Saved remotes", SubmenuIndexSavedRemotes, submenu_callback, app); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); +} + +bool IrdaAppSceneStart::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::MenuSelected) { + switch(event->payload.menu_index) { + case SubmenuIndexUniversalLibrary: + app->switch_to_next_scene(IrdaApp::Scene::Universal); + break; + case SubmenuIndexLearnNewRemote: + app->set_learn_new_remote(true); + app->switch_to_next_scene(IrdaApp::Scene::Learn); + break; + case SubmenuIndexSavedRemotes: + app->switch_to_next_scene(IrdaApp::Scene::RemoteList); + break; + } + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneStart::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_clean(submenu); +} diff --git a/applications/irda/scene/irda-app-scene-universal.cpp b/applications/irda/scene/irda-app-scene-universal.cpp new file mode 100644 index 00000000..2ab4ae2e --- /dev/null +++ b/applications/irda/scene/irda-app-scene-universal.cpp @@ -0,0 +1,57 @@ +#include "../irda-app.hpp" + +typedef enum { + SubmenuIndexUniversalTV, + SubmenuIndexUniversalAudio, + SubmenuIndexUniversalAirConditioner, +} SubmenuIndex; + +static void submenu_callback(void* context, uint32_t index) { + IrdaApp* app = static_cast(context); + IrdaAppEvent event; + + event.type = IrdaAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void IrdaAppSceneUniversal::on_enter(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_add_item(submenu, "TV's", SubmenuIndexUniversalTV, submenu_callback, app); + submenu_add_item(submenu, "Audio Players", SubmenuIndexUniversalAudio, submenu_callback, app); + submenu_add_item( + submenu, "Air Conditioners", SubmenuIndexUniversalAirConditioner, submenu_callback, app); + + view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); +} + +bool IrdaAppSceneUniversal::on_event(IrdaApp* app, IrdaAppEvent* event) { + bool consumed = false; + + if(event->type == IrdaAppEvent::Type::MenuSelected) { + switch(event->payload.menu_index) { + case SubmenuIndexUniversalTV: + // app->switch_to_next_scene(IrdaApp::Scene::UniversalTV); + break; + case SubmenuIndexUniversalAudio: + // app->switch_to_next_scene(IrdaApp::Scene::UniversalAudio); + break; + case SubmenuIndexUniversalAirConditioner: + // app->switch_to_next_scene(IrdaApp::Scene::UniversalAirConditioner); + break; + } + consumed = true; + } + + return consumed; +} + +void IrdaAppSceneUniversal::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_clean(submenu); +} diff --git a/applications/irda/scene/irda-app-scene.hpp b/applications/irda/scene/irda-app-scene.hpp new file mode 100644 index 00000000..832e4bc0 --- /dev/null +++ b/applications/irda/scene/irda-app-scene.hpp @@ -0,0 +1,130 @@ +#pragma once +#include "../irda-app.hpp" +#include +#include "irda.h" +#include +#include +#include + +class IrdaApp; + +class IrdaAppScene { +public: + virtual void on_enter(IrdaApp* app) = 0; + virtual bool on_event(IrdaApp* app, IrdaAppEvent* event) = 0; + virtual void on_exit(IrdaApp* app) = 0; + virtual ~IrdaAppScene(){}; + +private: +}; + +class IrdaAppSceneStart : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneUniversal : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneLearn : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneLearnSuccess : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneLearnEnterName : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneLearnDone : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneLearnDoneAfter : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneRemote : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +private: + std::vector buttons_names; +}; + +class IrdaAppSceneRemoteList : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; + std::vector remote_names; +}; + +class IrdaAppSceneEdit : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneEditKeySelect : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +private: + std::vector buttons_names; +}; + +class IrdaAppSceneEditRename : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneEditDelete : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneEditRenameDone : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + +class IrdaAppSceneEditDeleteDone : public IrdaAppScene { +public: + void on_enter(IrdaApp* app) final; + bool on_event(IrdaApp* app, IrdaAppEvent* event) final; + void on_exit(IrdaApp* app) final; +}; + diff --git a/applications/irda_monitor/irda_monitor.c b/applications/irda_monitor/irda_monitor.c index 40efd647..3da88f03 100644 --- a/applications/irda_monitor/irda_monitor.c +++ b/applications/irda_monitor/irda_monitor.c @@ -110,9 +110,11 @@ int32_t irda_monitor_app(void* p) { snprintf( irda_monitor->display_text, sizeof(irda_monitor->display_text), - "%s\nA:0x%02lX\nC:0x%02lX\n%s\n", + "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n", irda_get_protocol_name(message->protocol), + irda_get_protocol_address_length(message->protocol), message->address, + irda_get_protocol_command_length(message->protocol), message->command, message->repeat ? " R" : ""); view_port_update(view_port); @@ -124,9 +126,11 @@ int32_t irda_monitor_app(void* p) { if(message || (distance > (IRDA_TIMINGS_SIZE / 2))) { if(message) { printf( - "== %s, A:0x%02lX, C:0x%02lX%s ==\r\n", + "== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n", irda_get_protocol_name(message->protocol), + irda_get_protocol_address_length(message->protocol), message->address, + irda_get_protocol_command_length(message->protocol), message->command, message->repeat ? " R" : ""); } else { diff --git a/applications/tests/irda_decoder/irda_decoder_test.c b/applications/tests/irda_decoder/irda_decoder_test.c index 6305a7e7..f3ba40b7 100644 --- a/applications/tests/irda_decoder/irda_decoder_test.c +++ b/applications/tests/irda_decoder/irda_decoder_test.c @@ -2,6 +2,7 @@ #include "../minunit.h" #include "irda.h" #include "test_data/irda_decoder_nec_test_data.srcdata" +#include "test_data/irda_decoder_necext_test_data.srcdata" #include "test_data/irda_decoder_samsung_test_data.srcdata" #define RUN_DECODER(data, expected) \ @@ -52,10 +53,12 @@ MU_TEST(test_samsung32) { RUN_DECODER(test_samsung32_input1, test_samsung32_expected1); } -MU_TEST(test_mix_nec_samsung32) { +MU_TEST(test_mix) { + RUN_DECODER(test_necext_input1, test_necext_expected1); RUN_DECODER(test_samsung32_input1, test_samsung32_expected1); RUN_DECODER(test_nec_input1, test_nec_expected1); RUN_DECODER(test_samsung32_input1, test_samsung32_expected1); + RUN_DECODER(test_necext_input1, test_necext_expected1); RUN_DECODER(test_nec_input2, test_nec_expected2); } @@ -75,6 +78,11 @@ MU_TEST(test_unexpected_end_in_sequence) { RUN_DECODER(test_nec_input2, test_nec_expected2); } +MU_TEST(test_necext1) { + RUN_DECODER(test_necext_input1, test_necext_expected1); + RUN_DECODER(test_necext_input1, test_necext_expected1); +} + MU_TEST_SUITE(test_irda_decoder) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); @@ -82,7 +90,8 @@ MU_TEST_SUITE(test_irda_decoder) { MU_RUN_TEST(test_nec1); MU_RUN_TEST(test_nec2); MU_RUN_TEST(test_samsung32); - MU_RUN_TEST(test_mix_nec_samsung32); + MU_RUN_TEST(test_necext1); + MU_RUN_TEST(test_mix); } int run_minunit_test_irda_decoder() { diff --git a/applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.srcdata b/applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.srcdata index 69c7626b..9c2977a7 100644 --- a/applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.srcdata +++ b/applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.srcdata @@ -11,9 +11,9 @@ const uint32_t test_nec_input1[] = { 1415838, 9080, 4436, 611, 494, 600, 505, 578, 500, 608, 501, 602, 502, 580, 498, 606, 508, 605, 500, 583, 1633, 608, 1608, 611, 1631, 578, 1638, 602, 1614, 606, 1637, 583, 1633, 607, 1609, 611, 494, 600, 505, 570, 500, 604, 501, 602, 502, 581, 497, 606, 499, 605, 499, 583, 1633, 617, 1608, 611, 1631, 579, 1638, 602}; const IrdaMessage test_nec_expected1[] = { - {IrdaProtocolNEC, 0xFF00, 0, false}, - {IrdaProtocolNEC, 0xFF00, 0, true}, - {IrdaProtocolNEC, 0xFF00, 0, false}, + {IrdaProtocolNEC, 0x00, 0, false}, + {IrdaProtocolNEC, 0x00, 0, true}, + {IrdaProtocolNEC, 0x00, 0, false}, }; const uint32_t test_nec_input2[] = { @@ -124,57 +124,57 @@ const uint32_t test_nec_input2[] = { }; const IrdaMessage test_nec_expected2[] = { - {IrdaProtocolNEC, 0xFF00, 0x02, false}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, false}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x02, true}, - {IrdaProtocolNEC, 0xFF00, 0x06, false}, - {IrdaProtocolNEC, 0xFF00, 0x06, true}, - {IrdaProtocolNEC, 0xFF00, 0x04, false}, - {IrdaProtocolNEC, 0xFF00, 0x04, true}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, true}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, true}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x09, false}, - {IrdaProtocolNEC, 0xFF00, 0x09, false}, - {IrdaProtocolNEC, 0xFF00, 0x09, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x0A, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, true}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, true}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x0A, false}, - {IrdaProtocolNEC, 0xFF00, 0x08, false}, - {IrdaProtocolNEC, 0xFF00, 0x0A, false}, - {IrdaProtocolNEC, 0xFF00, 0x0A, true}, + {IrdaProtocolNEC, 0x00, 0x02, false}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, false}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x02, true}, + {IrdaProtocolNEC, 0x00, 0x06, false}, + {IrdaProtocolNEC, 0x00, 0x06, true}, + {IrdaProtocolNEC, 0x00, 0x04, false}, + {IrdaProtocolNEC, 0x00, 0x04, true}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, true}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, true}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x09, false}, + {IrdaProtocolNEC, 0x00, 0x09, false}, + {IrdaProtocolNEC, 0x00, 0x09, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x0A, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, true}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x08, true}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x0A, false}, + {IrdaProtocolNEC, 0x00, 0x08, false}, + {IrdaProtocolNEC, 0x00, 0x0A, false}, + {IrdaProtocolNEC, 0x00, 0x0A, true}, }; diff --git a/applications/tests/irda_decoder/test_data/irda_decoder_necext_test_data.srcdata b/applications/tests/irda_decoder/test_data/irda_decoder_necext_test_data.srcdata new file mode 100644 index 00000000..73b85de5 --- /dev/null +++ b/applications/tests/irda_decoder/test_data/irda_decoder_necext_test_data.srcdata @@ -0,0 +1,223 @@ +const uint32_t test_necext_input1[] = { +1915384, 8967, 4463, 587, 527, 590, 524, 584, 1647, 590, 524, 583, 531, 586, 527, 590, 524, 583, 1646, 589, 1640, 586, 527, 590, 524, 583, 1647, 590, 1640, 587, 1644, 582, 1647, 589, 524, 583, 531, 586, 1644, 593, 521, 586, 527, 589, 1641, 586, 528, 589, 525, 592, 521, 585, 1644, 592, 522, 585, 1645, 592, 1638, 589, 524, 592, 1637, 588, 1641, 585, 1645, 592, +41082, 8965, 2220, 591, +409594, 8972, 4458, 591, 523, 584, 530, 587, 1642, 584, 529, 588, 526, 591, 522, 583, 530, 587, 1643, 584, 1646, 590, 523, 584, 530, 587, 1643, 584, 1647, 590, 1640, 586, 1643, 583, 531, 586, 527, 589, 1641, 586, 528, 589, 524, 593, 1637, 589, 524, 593, 521, 586, 529, 589, 1641, 585, 528, 589, 1640, 586, 1644, 592, 521, 585, 1645, 592, 1638, 588, 1641, 585, +41088, 8968, 2218, 582, +95791, 8971, 2214, 587, +95787, 8965, 2220, 591, +95783, 8971, 2215, 585, +95787, 8964, 2221, 590, +95783, 8971, 2215, 586, +95788, 8965, 2220, 591, +95783, 8969, 2216, 585, +95789, 8965, 2220, 590, +95782, 8970, 2215, 586, +95788, 9047, 2139, 591, +95782, 8970, 2216, 585, +95788, 8966, 2220, 591, +95782, 8972, 2214, 588, +95786, 8964, 2222, 590, +95784, 8971, 2214, 586, +95787, 8967, 2218, 583, +95791, 8964, 2222, 588, +95785, 8969, 2217, 584, +333740, 8967, 4464, 586, 528, 590, 524, 592, 1637, 589, 525, 592, 521, 586, 528, 589, 525, 593, 1637, 588, 1640, 585, 528, 589, 525, 592, 1638, 589, 1641, 586, 1644, 592, 1638, 588, 525, 592, 522, 585, 1644, 592, 522, 585, 528, 588, 1642, 585, 529, 588, 526, 591, 522, 585, 1645, 591, 522, 584, 1646, 591, 1639, 587, 526, 591, 1639, 588, 1642, 583, 1646, 590, +41082, 8963, 2223, 588, +95785, 8967, 2219, 591, +95782, 8968, 2217, 584, +246369, 8972, 4459, 591, 523, 583, 530, 587, 1643, 583, 530, 587, 527, 590, 523, 584, 530, 586, 1643, 583, 1647, 590, 524, 583, 530, 586, 1643, 583, 1646, 589, 1641, 586, 1644, 583, 531, 586, 528, 589, 1640, 585, 528, 588, 525, 592, 1638, 588, 525, 592, 522, 585, 529, 589, 1641, 584, 529, 588, 1642, 585, 1645, 591, 522, 585, 1645, 590, 1639, 587, 1642, 584, +41090, 8966, 2220, 591, +95782, 8966, 2220, 592, +95782, 8967, 2218, 583, +165604, 9017, 4413, 586, 527, 590, 524, 583, 1647, 589, 523, 582, 531, 586, 528, 589, 525, 593, 1637, 589, 1640, 585, 527, 588, 525, 592, 1638, 589, 1641, 585, 1644, 592, 1638, 588, 525, 591, 523, 585, 1645, 591, 522, 584, 529, 588, 1642, 584, 530, 587, 527, 591, 523, 584, 1646, 591, 523, 584, 1646, 591, 1640, 586, 527, 590, 1640, 586, 1643, 583, 1646, 589, +41084, 8972, 2214, 587, +95787, 8967, 2219, 581, +95792, 8971, 2215, 586, +208929, 9016, 4414, 584, 529, 588, 526, 591, 1638, 588, 525, 591, 522, 584, 529, 587, 526, 591, 1639, 587, 1642, 584, 529, 588, 526, 591, 1638, 587, 1643, 584, 1646, 591, 1639, 587, 526, 590, 523, 584, 1646, 590, 524, 583, 530, 587, 1643, 583, 530, 587, 527, 590, 524, 583, 1647, 590, 524, 583, 1647, 590, 1640, 586, 527, 589, 1640, 586, 1644, 592, 1637, 589, +41085, 8972, 2214, 587, +95787, 8964, 2221, 590, +95784, 8965, 2221, 590, +167378, 8969, 4460, 589, 525, 582, 532, 586, 1644, 592, 521, 586, 528, 589, 524, 592, 522, 585, 1645, 592, 1638, 589, 525, 592, 522, 585, 1644, 591, 1639, 588, 1642, 585, 1645, 591, 522, 585, 529, 587, 1641, 584, 530, 587, 526, 591, 1639, 588, 526, 591, 522, 584, 530, 587, 1643, 584, 530, 587, 1642, 584, 1646, 591, 523, 584, 1647, 590, 1640, 587, 1643, 583, +41090, 9017, 2169, 591, +95781, 8969, 2216, 585, +95788, 8964, 2223, 588, +192781, 8969, 4461, 589, 525, 592, 522, 586, 1644, 592, 521, 586, 528, 589, 525, 592, 522, 585, 1644, 592, 1638, 589, 524, 592, 521, 586, 1645, 591, 1638, 588, 1642, 585, 1645, 590, 522, 584, 530, 587, 1642, 584, 530, 587, 526, 591, 1639, 588, 526, 590, 524, 583, 530, 587, 1643, 584, 530, 587, 1643, 584, 1646, 590, 524, 583, 1646, 590, 1640, 587, 1643, 583, +41090, 8967, 2219, 591, +95782, 8970, 2215, 586, +95788, 8963, 2222, 589, +179978, 8967, 4464, 586, 528, 589, 524, 593, 1637, 588, 525, 592, 522, 585, 529, 589, 525, 592, 1638, 589, 1641, 585, 529, 588, 526, 591, 1638, 588, 1641, 585, 1645, 590, 1639, 587, 527, 590, 523, 584, 1646, 591, 523, 584, 530, 587, 1643, 583, 530, 586, 527, 590, 524, 583, 1646, 590, 523, 584, 1646, 589, 1640, 586, 528, 589, 1640, 586, 1644, 593, 1638, 589, +41084, 8971, 2214, 587, +95787, 8964, 2221, 589, +95785, 8966, 2219, 592, +196616, 8967, 4463, 585, 527, 590, 525, 592, 1637, 589, 525, 592, 521, 586, 528, 589, 524, 592, 1638, 588, 1641, 585, 528, 589, 525, 592, 1637, 589, 1641, 585, 1645, 591, 1638, 588, 526, 591, 522, 585, 1645, 591, 522, 584, 530, 587, 1642, 584, 529, 588, 526, 591, 523, 583, 1645, 590, 523, 584, 1646, 590, 1639, 587, 527, 590, 1639, 586, 1644, 583, 1647, 589, +41084, 8971, 2214, 587, +95787, 8964, 2222, 589, +2112164, 8969, 4462, 588, 525, 592, 522, 585, 1645, 591, 523, 584, 529, 588, 526, 591, 523, 584, 1645, 591, 1639, 587, 527, 590, 524, 583, 1646, 590, 1639, 587, 1643, 584, 1673, 563, 524, 583, 531, 586, 1643, 593, 521, 586, 528, 589, 1641, 585, 528, 589, 525, 592, 521, 585, 1644, 592, 522, 584, 1645, 591, 1639, 588, 526, 591, 1639, 588, 1642, 583, 1646, 590, +41082, 8962, 2223, 588, +95785, 8965, 2220, 591, +95783, 8968, 2217, 583, +164778, 8969, 4462, 588, 525, 591, 522, 585, 1645, 591, 522, 585, 530, 587, 527, 591, 523, 584, 1646, 591, 1639, 588, 526, 591, 523, 583, 1646, 590, 1639, 587, 1643, 584, 1672, 564, 523, 584, 531, 586, 1643, 583, 530, 587, 527, 590, 1639, 587, 527, 589, 524, 583, 531, 586, 1644, 583, 531, 586, 1643, 583, 1647, 590, 525, 582, 1647, 589, 1639, 586, 1644, 593, +41081, 8965, 2220, 590, +95784, 8968, 2217, 583, +95790, 8970, 2215, 586, +161053, 8963, 4468, 592, 521, 586, 528, 589, 1641, 585, 529, 588, 526, 591, 522, 585, 529, 588, 1642, 585, 1645, 591, 523, 584, 530, 587, 1642, 584, 1646, 591, 1639, 586, 1669, 557, 531, 586, 527, 590, 1640, 586, 527, 590, 525, 592, 1638, 589, 525, 592, 522, 585, 528, 588, 1641, 585, 528, 588, 1642, 584, 1645, 591, 523, 584, 1645, 591, 1639, 587, 1643, 583, +41090, 8964, 2221, 590, +95784, 8963, 2222, 589, +95785, 8965, 2220, 590, +139334, 8968, 4463, 587, 527, 590, 523, 584, 1646, 590, 523, 583, 531, 586, 527, 589, 524, 583, 1647, 590, 1640, 586, 527, 590, 525, 592, 1637, 589, 1641, 585, 1644, 592, 1665, 562, 524, 591, 522, 584, 1645, 591, 523, 584, 529, 588, 1642, 584, 529, 587, 527, 590, 523, 584, 1646, 590, 523, 584, 1646, 590, 1639, 587, 527, 589, 1640, 586, 1644, 592, 1637, 589, +41085, 8970, 2217, 584, +95789, 8972, 2213, 586, +95787, 8965, 2221, 590, +141444, 8969, 4461, 589, 525, 592, 522, 584, 1644, 591, 522, 585, 529, 588, 526, 591, 522, 585, 1645, 592, 1638, 587, 526, 591, 523, 584, 1646, 591, 1639, 588, 1642, 583, 1672, 564, 523, 584, 530, 587, 1643, 584, 530, 587, 527, 590, 1640, 587, 527, 590, 524, 584, 530, 587, 1643, 584, 530, 586, 1644, 583, 1647, 589, 524, 583, 1647, 590, 1640, 586, 1645, 592, +41081, 8964, 2222, 589, +95784, 8968, 2218, 583, +95790, 8971, 2214, 586, +154119, 8969, 4462, 588, 526, 591, 522, 585, 1645, 592, 522, 585, 529, 589, 526, 591, 522, 584, 1646, 591, 1639, 588, 526, 591, 523, 583, 1645, 590, 1639, 587, 1642, 584, 1671, 564, 523, 584, 529, 587, 1643, 583, 530, 587, 527, 590, 1639, 587, 526, 590, 523, 583, 530, 586, 1643, 583, 529, 586, 1643, 583, 1646, 590, 524, 583, 1648, 589, 1641, 586, 1644, 592, +41081, 8965, 2220, 590, +95784, 8969, 2216, 585, +95790, 8964, 2221, 590, +147134, 8966, 4464, 586, 528, 589, 525, 593, 1637, 589, 524, 593, 522, 585, 529, 589, 525, 592, 1638, 589, 1641, 586, 528, 589, 525, 591, 1638, 588, 1641, 585, 1645, 592, 1664, 561, 525, 592, 523, 584, 1646, 591, 523, 584, 530, 588, 1642, 585, 526, 587, 527, 590, 523, 584, 1646, 591, 523, 584, 1646, 591, 1639, 586, 526, 590, 1640, 587, 1643, 583, 1646, 590, +41083, 8963, 2223, 587, +95786, 8965, 2221, 590, +95784, 8968, 2217, 584, +158330, 8965, 4465, 585, 529, 588, 526, 590, 1639, 587, 526, 590, 523, 584, 530, 586, 526, 590, 1639, 587, 1643, 583, 530, 587, 527, 590, 1639, 587, 1643, 584, 1647, 590, 1666, 561, 527, 589, 523, 583, 1646, 590, 523, 583, 531, 586, 1643, 583, 530, 586, 527, 590, 524, 582, 1646, 590, 525, 582, 1647, 589, 1640, 586, 528, 589, 1640, 586, 1644, 592, 1638, 589, +41085, 8971, 2214, 586, +95787, 8962, 2223, 588, +95786, 8965, 2222, 589, +206063, 8962, 4467, 591, 521, 585, 529, 588, 1642, 585, 529, 588, 525, 591, 522, 584, 530, 587, 1642, 584, 1646, 591, 523, 584, 529, 588, 1642, 583, 1646, 590, 1640, 587, 1668, 558, 530, 587, 526, 589, 1639, 586, 528, 589, 524, 583, 1647, 589, 524, 593, 521, 585, 528, 589, 1641, 585, 529, 589, 1641, 585, 1645, 592, 522, 585, 1644, 591, 1639, 587, 1642, 584, +41090, 8965, 2221, 590, +95784, 8963, 2223, 588, +95785, 8964, 2222, 589, +183026, 8970, 4460, 590, 524, 583, 531, 586, 1643, 583, 530, 587, 528, 589, 525, 592, 522, 586, 1644, 592, 1637, 588, 525, 591, 522, 585, 1645, 592, 1638, 588, 1641, 585, 1672, 565, 522, 584, 530, 588, 1642, 584, 529, 588, 526, 591, 1639, 587, 527, 590, 523, 584, 530, 587, 1642, 584, 530, 587, 1642, 583, 1647, 590, 524, 583, 1647, 590, 1640, 587, 1643, 582, +41090, 8965, 2221, 590, +95783, 8970, 2216, 584, +95789, 8962, 2223, 587, +184104, 8964, 4467, 583, 530, 587, 527, 590, 1640, 587, 527, 590, 523, 582, 531, 586, 528, 589, 1640, 586, 1644, 593, 521, 586, 528, 589, 1640, 585, 1644, 592, 1638, 589, 1667, 558, 528, 589, 526, 591, 1638, 588, 526, 591, 522, 585, 1645, 591, 522, 585, 530, 587, 527, 591, 1639, 587, 526, 591, 1639, 587, 1642, 584, 530, 587, 1643, 583, 1646, 590, 1639, 587, +41087, 9020, 2166, 584, +95790, 8972, 2213, 587, +95787, 8963, 2222, 589, +169833, 8964, 4465, 583, 529, 587, 527, 590, 1639, 587, 527, 591, 523, 584, 530, 586, 527, 590, 1640, 587, 1643, 583, 531, 587, 527, 590, 1640, 586, 1644, 583, 1647, 590, 1666, 560, 527, 590, 524, 582, 1647, 589, 525, 592, 521, 586, 1644, 592, 521, 586, 528, 589, 526, 592, 1638, 588, 525, 592, 1638, 589, 1641, 585, 528, 589, 1641, 585, 1645, 591, 1638, 588, +41086, 8971, 2215, 585, +95789, 8964, 2222, 588, +95785, 8967, 2218, 583, +185701, 8971, 4460, 590, 523, 584, 530, 587, 1642, 584, 530, 587, 527, 590, 524, 583, 531, 586, 1644, 583, 1647, 590, 524, 592, 521, 585, 1644, 592, 1638, 589, 1641, 585, 1671, 565, 522, 586, 529, 588, 1642, 585, 529, 588, 526, 591, 1638, 588, 525, 590, 523, 584, 530, 587, 1642, 584, 530, 587, 1642, 584, 1646, 590, 524, 583, 1646, 590, 1640, 586, 1643, 583, +41091, 8965, 2222, 589, +95784, 8965, 2221, 589, +95784, 8968, 2217, 583, +146332, 8969, 4461, 669, 445, 591, 522, 584, 1644, 591, 523, 584, 529, 588, 526, 591, 522, 585, 1645, 591, 1638, 587, 527, 590, 524, 584, 1646, 590, 1639, 587, 1642, 583, 1673, 564, 524, 583, 531, 586, 1643, 583, 531, 586, 528, 589, 1641, 585, 528, 589, 525, 592, 522, 585, 1644, 591, 521, 585, 1645, 592, 1638, 588, 525, 592, 1638, 588, 1641, 584, 1646, 591, +41083, 8963, 2222, 589, +95785, 8966, 2220, 592, +261924, 8965, 4465, 585, 529, 588, 525, 592, 1638, 588, 525, 592, 523, 584, 530, 587, 526, 591, 1639, 587, 1642, 583, 529, 587, 527, 590, 1639, 587, 1643, 584, 1646, 590, +}; + +const IrdaMessage test_necext_expected1[] = { + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, false}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, + {IrdaProtocolNECext, 0x7984, 0x12, true}, +}; + diff --git a/assets/icons/Irda/IrdaArrowDown_4x8.png b/assets/icons/Irda/IrdaArrowDown_4x8.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac7bcdbef24ccececa268c20e92946b4f8eed15 GIT binary patch literal 3589 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 E0BFES!TdSQr#iT4Nm<}!zq}(lg z=k8~a&H!Kyj!3h#XMyw;0DDea#{5N0teCr;{I(pVTjSYoF=TG+NdE3he~U}8N8{X% zANZm+Q2Zz&j5j;G`fjy)GG}tFo!uc7wM|AVd7>Z?Fh<9izAY_fRF@Cl9ag*5S3}E(+YqN2m)3+W!r>6D4~4) zuDYQTZ(2+xQUIvurnO}p?lO?8Ona>y`gLw-M17=zbl(!IxJTMgbPSx6k*y(%oD@Q} zivxi2O6RkU17=B+6Jx^@ev>5Lw-*H)L332FsrvljM)hSuVL;lkgV*OhKHeilYZAB@ zTo+Vw4Cr$Q0tQ#S4Eja$kBYqxTZv;ISTo<5cip+!M0JPQ)~&OezSnE?N8?O?8Vpm1 z;v$#$vm5lPL{H1x! z#Y$(eLlgT6I@wyiC2d4z>NIR#Eqz@yB~$|7oOh~se7xlmmsB75ML;l464Mm|m`5(? zURK_k3`qMM!r03IkW#mjXs#^)v}F{H0zmbr?Z+t>Q_nm&BJ@@;*!;;YHRVe$^BJ8?Ln*JQ^n##H zmE^q@ zxg86?cGRAnAL0ygxlkf&7@VcqmaSmLEz&LQew{`1CoTs-rkGWtAEI~KWW3Jk3i`BV zYH}+2Td26id5P#Ir0;H}Af;1E#oH^CAepF}o8Dw(HoNM-gQ;ZtUzNH_b;Imk+8ZvP z{7NPbp5~qwnKpUb)s5k1*3G`X)%A{>)SVOV5jSn!9y*Vfh`v?0(_NzK_OxVQSvxuu zo#@IyGmgn5i*58Yj8S|{kLgYU5iD>JB& zO{*#)kW(lZE5>a*VRpKY7fj1F&(+CYgp=XR!J<|Bs*Y5}RP_gUd^UZ9n`TdmF1}inochpd zw@Ch0xTyG<*Td^n+pljZvM))A1U-P8YfZv^3c5@v@UKd( zFBkkrjaZ7NejMr!Uyk8N!a$G-)|UwDFKR4$F;=^$Ej1|1ud1{B4YReiS!aOL%W0WO zFz(I?%EQ*aukK%qkGWx!^Wsq4zPP6JzVvYJrgVq&WzJQ1Q+KxeqWe3Jeb4b}ooRB9 z2gzB*BU?Nhp3QxHt2Dc`F(jKjC^sUPxTkB+q6?)DIf@E0YnDq3A*`zEn)P z7RF5*SN>Af_2|uFVfNGPfztQnw;x&&FCy-&I_PDqw0J#7@GQQ~En`MbwvOf&XBRgt zBuzq2gq-j^v0Ag!>0ypmUXMPxxxew)P!0E#58M<%_|ZNkWct!n@LJfl3$-_E!Gnle zjmVzJ%*ZWkd8-G1aMu&R*A273D$F}>@)39@FfLGYM)}M`p&%hO;Y{HhTZ}fvZ+f~J zB5o>gDK0AEsYXws)+>HhS-}|$`0HeK%D)oHl|QQTL%&rEwXtm7FAv;oRr5aHwQ*k9iZqHuWZN1M&%3YWznaesooH5T;|C)sP#FEQT4)zB=rib`* z0!e5RZYH}js`7T_8Pz=lB;KRWPs~-X9oos_&8i+cfwb{u^SDt7rIJjH)eB^YzKlxtQI5j6Vrw}bcOfT1d zr(dzV!pTotJ4IrQJwbk@l5*kI@|ea7W1(N36ocw4EJv3)ODdosx3pQuwjX0vB5ROG z-x>`1ZG2wQ*m_nWye8?MbLG8g%cd5PE7sGFnys z;74BSO!XsemPb&HO~fLl>&w{>iORkzRdo}QA1duaonGW6p1N^r%aRRmI*>^v4cx7( zdmu_i?~C7wgww{eqo{q^vss~!Xh-)-Hdu^5`)lxK_^DCnPuJU@4?J%h%sgGOC^C&? zYj?rxf;qZ{s;=Ei( zGsN4>0roO#Ei=gLh*yojRY#tgBJ&HX!h5-8;p1eY*IYI28+h5bU$f$HDWVKv7fD+m zM&3O}ohB@EXFW{HnBN?x4|ek2ocu_yX;!+ zx;1Py;uCY#)KnytYrf*!Q-+Hl6w7wJigf?Uo@U&c99$n+J=}p=>z&)W64y~z#x7Z) z^X`<~g*Rz5sa3bL5%V|KO56J*c$K`W!KNCu%-w6|8_o-zKX6UOubq}nC&M?^Aj{_# zR-=qHxADI!m6$ddrVG`N8H%M701G_TmjJRSWBmze0v69c*+MV@03mgvD~5@2K*4cT zvL5yqMvq0N@!0@iV#cCjaX|zo$d}+xq#(e&r*&Wu5sv_S7&+)W(5wgnL}VD9;1cHO ziVF+E8RNlbrXUj*oG(BoFtH#OnM7g0SqSjox^RB{YZw9s{S9FTA;AAB3gh4mvZB%n zAS1nfx;UsI47A@^54um^7-pygg6Tu|L-hAUpa#17P`Lg+xPdX~p9{>-M#uZX(bl&A zPho~Dn?``32@Gm59mhX7KaF3hXmBe!0n4P)U8z*k?_G2bpfah904fc{|AW}= zL8RcRp^QC$!yO#p_7nyaOTiKBtr1|pfF6;Ehg(=!+FD!N7+P8z!k|zaOA7;iD+>## z#eRzeR+dm>qu*R>DlV8zpfG=P@&Dx-{gM0231k{Svo(QE3?blc=~Oc4?-j#|f35}g zN4Nh(+4zRaz Kv@WysIr~3bh)e$f literal 0 HcmV?d00001 diff --git a/assets/icons/Irda/IrdaLearnShort_128x31.png b/assets/icons/Irda/IrdaLearnShort_128x31.png new file mode 100644 index 0000000000000000000000000000000000000000..783ad0877c4281ffc9373ab39c2db754eae3083f GIT binary patch literal 3952 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Irda/IrdaLearn_128x64.png b/assets/icons/Irda/IrdaLearn_128x64.png new file mode 100644 index 0000000000000000000000000000000000000000..80fe3ce1195ac837e8cc9b7a8761cb4ed06698f3 GIT binary patch literal 4161 zcmaJ@c{o&U8$Y%zWy!u&#)!AYn1vZ;GIpWJGQjt=YY*|y3XlPR; zTe4=0BBAUgyGXKq)BC=?-}lFRedoH)InQ~X`*+{J`}f@Uxt=Sy6Q;sK@kP*em2xo3&1fnx&ZWM1a00j1BSqC^Iu1f0;u98LV40{(I zr+P;KU}d&IlbGWju^Ry4l9;%DePpzdout(6Of%cM3*ACyS*q+v& zQyj>Dc0Oo+c6R;4dc|bsH%PZhZPvge5AL*^^gW|1N3h7{o;+o+ZW4ms`daV0T=>{PwwQ3#{*63HcgU1 zlPvJa`YA~WNCE(FR)`@OC=>=}md$nffZl?{0U4n8&Yl7R;5rwOcEEH$xBg4O#Wn)3 z#$8hjby0@T;Yz-5vJVXm3)AItV6u0z6%hZW~kN*fV8;*qkR%>JRl3699UQN zP{CPxWSJQkP}7p!mU_HPL$WOSt$e`J!qAA)NUir{LxRjfF*m_6aAs((e`m{Ufd&3h@I$|?>jp_-orz0+ z?Nx9J=(7Vn2G^W5`UP{Ggx&_N#jvzD^!H`o#x?2e-6OPf=d6nB?Mm2ajPAC^Fl{I% z^gCyDt3PM~IG3)UR#cOFaq}F($G;dKJK4f(2=`f?RNiWg4BL!7*g2y4__UhT)kTLZ zWjL^9BkKh|!C0v|c|?3F46&$~vMHDtAPlghtZGg?=YQ7XT@yOT#T_H!(B%*4hkk=x zli!&Dh`DPbSgQb#SiKgff0zqsOU)YvfQm1&r|&1pSTqO#fN@U5p+^S%yIx7MYeX_% z?X9Sh;U?@b*59?iMn}wW$5{9tq1&=4268qvm|oszk9g#BcBc=EmAry$3r`M-pN^QO z%j|HG&90TLh!W&AXcCYhC}E;^yT}d7!Ci$&=vvCPp52fuU!D zvzhV|gdvH(ep>%$e18)NadqQrg(*BK;>k&#_uPK^FYYVKUv0?o?PO|-I)|s+@#>T> z*@q2(ekZ^4_!mDWaQ6fn3mLYA_k_XA{ew$8lr>uHFrJ#=z}wM=kFc0eTo|`5PUEFpu1qN?aKmGaXUT$ zABXEWc{aD|?B<1^MByh1qJ+i9NrN15u} zW%p5Jpp32?o3_W5t@)We;H5)wr6BtA>hkKxst&d#d8NCRcb2?kwzf8@4X}IJ%`>s+ z?o6+2LeE7XsXg}I zI0gHRT^X2+mgo12GK%W_GpK`-Ba(3kyAG~c`}DzQHfDro(r0$eqQ4Wqt9++^@7UYA zM|^M35$2H)2Gg(A-c}J$tv3oAEMZpy(s@{ijxJ{rLy$mg*S_&Cai}^9IWcs}3XQT1T_;GxBS{ z#ZN-d_@8k+vtGH+>S?BGb`Ol&)L(aMsIn#41*3~4|7@S)(QVM>z7=%qa@D;m@F2EI zIkYDBSR#E?lp;# zbAjj~Q}5_9MT%US%-VU$gNG1>>R(TmSToNtSJishV~*?Ym9dL)1=n62>Gyoj@ONi> zdgHx`GZ|&!WrbxCftP=nET)PxCH4!>UKy+Ft;1d!_2r)QPS?mBOSeg35{t(N?!1th zm}+my4$yH#I>v`+N8S`qeWEvh5oCyi%36)*`&jOkSh|!EX=PG%=lY$Kk4wvr>D-ve zvP=H1{prYgqn{p8R!99BGz6{ihgk`7H(n@zv$Qkr`ONP4cuq?8=~ z04qIEYUOtLaH8-SYl8YlA@TB?)iLEY-*5d=-jNoaxh42woQSjq^#0+%Q?jQ5%gidx zoZf2;x@~=3Q{H(&I;1lG5w7gf)84W`$HgIVwaH6qw>W zeZTm}qoZXLBlaiGc89F6sC|8(kCnceoi>}!oAzO^I#xeCR_STb{WfYrxJY5~`nbYb z>aowv+d-Zyv27)7Z`yc}1mx_g*5~a0rQ4|7??G8%&MkJEMGs1=KC1ThRDTKx{w8v9 zuhfyx^GP!m&khILdsUj8U-9XhyYMMa-c_NzdLr~wnQ4GkeRf>%-C+LjCiBytOq%z= z!|LiMf>b=}`aUxZeLN$a)|WAx9`KCrbV#JuVEok|gZDy$M{!?nx4#~E-8PsOR=OfE zZN@s>g{k&qLvpD(*xQx!U#{h5?LD*Cw5oJFbRIrR^lL=M*=Ofu9l$QGja^6_%_rq& zXPwZ)zE2xqty*lPc{!hSu5>r*$kvl#&RLY6U2XpMc{0v|A9-g92lUG}2?RAQo%a+qeI_KYo{q(Hq_WkhVgSzuAR^@!dj6$i8>Zd(rD&;Fb z{J+o21PIMp3^)hH1wP!U>f8Lv|5Nm9SjpC7at(Fphf9r1$7@H?c@e?O*;`55?=BB_ znYNmCD<4(99+#!4s30s~xkPGD9L|4|FVXQP)NY$K?fYPIaC2n+c!$GA@50Wtn2zdV zR>3#M{DXprm`3GB#fo+ocJbauQG5US`Lg-)!Ny9(v;!OZTexqXKZ%X`Z>?5!CquS2 zpsSa@t%svkc5_aZa!eBkrZvrt89-o=0Rs}vl?*bc65PpnGJ(W8*G$#{0GIGG&jbA zIVj$XW;z97RN;h}!Vqe*O)QHZn#TAJsw_ zqK+cra1%oVgt3v4iG~RZhSWAjX&U^-n)~=N2|h&f@7NSh?7y+_|BJ;KF~|fajbTHh zdH=2goCl3b^Yx(7K?shZpab?49}+FV_u#KR`YUB)GK1nzCLLwasGvVZ#!&u+fVMUf zu0_^BK(xp(O$d^tNrs?FFfs&*(u8Tj2qXm36%GCyPx>G6{wh+=3?~WtZ!Q=VlBh{Q z!dxM6vKtYC)Y2qE2ymn;gh(c%wFzz{1R90{Lpcge|rr#OEN|LF?O$3J~V z_Te-WgVQx<4RoQLj@yB=urb-*-sTK#Yu|+e0N0ASv4Krs-^|UJCl^7xjD5GZ^J)14 z`;}gGHTq>L`Ln?{$bybIQvY*meV{|7=JxW1sr6xO`6tH}yx~BDJ77pd1KKXf(b$fa zLcE_%ux>(VuGgN;IRIwM<0NETUzvmXe4Ow4d+e8oy{QzOr!8a01Q2w5JcyUR+{OI} z62@J_f4nZ77a&DO$n$W8qiQSDLt(~O^59}TKh`v>bvkBjUe3C8-AvV{x*l?0<<23C zCU+bwB;v}AAW(!3Pj%slo7P3>uiW| zNn*5ocutk^p6i)ea(rzc95!VWc4YC~4qZD^OD>NV1k6R$s_OkD z%gBz;&4`SWxnRT2GfsN%tz-ve0olA~FthP|!#|6jA-NF$n!9x0yfSjzybiiwdfXfZq0$hySqo}Gu zzkRz|3}!Cf5GKnb#y>2^!xC!%WH&B9&7rC9|KoFrang{rV-sM65?BIuho=<2rvUNd zF1|N1_>EMTfgpEmxQ>*D%ryzfTSdJ=$y`bOq&T_v@g3tg?d_wU%D!CP;i=p!fVvQ% i6;=Ha^tp43R~E2RBrB%!7mLJ25h!7fFbR1r`>12&Y1 zG!X<1MNkj{rAQG010o<&eDPkr@BO&z%~~^a=A8ZQ{q67UJ?orz*>5i?p(+6YfTRP) z2FriP@o%u05dWEPsJR6IB&>t2tz8_ftwD4KEg+ao0)VK#T(?ME%95hl;1WsN)2es& zUIzID0M=rQwa9oE$Xo&Nr)6X%`Sx2qFz%5B&`kRB92+&)aGN1(X7HlaM11<{ynLF&a30gb>{5(!z zbp`7ifPA(ESWmdVTp-T-k-oiPT#X12k$_8*w(tYuGOcu50g$mEV7*nYO$g*Dln?8w z8!A0akLO?nfO>9PTgJXF19?u`E7i!avqK}ABMszxR(PeIG6CYF;GB$XEji4DkYoEM z08m}-e7bIIo-{E&Iy@dQLFRpHDp(JlrGd>f=LXlSF9`|*GW$DteTT=!dW7iB0(Zmf zLQ4+-eV#zj;Igklzj(fn#H;A#i!9?+i>-OrU0O`kw@7TW0!wAP#BkFO={>>M$?cTiX1!W`~A z#|7-v%zBJXw$Xf%HX=KDTz^g@eN8+iQW9XFajxJ0Nc16>Tp#;IK=7gzt}6nth@FRA zQr(yg$OIbdvz7oLrEWRVVz&U$mQnN(0IEMJA1p~#a(pfZ05%LgrfB=xs|^e?PlsJ4fvp1AKS^hPk;;&F+F>V@a|VVz7vY2Uc?g3wOY zimj$`j|z%A_k9Xy0>>^S5aK%FQURCV;SXLqYwLF&r>fJeNEpK*JQEQFb(~Rzf?9nZ zo+q|V@M4_YwAi83G7aRDNlErNN1Szu_Ow=oY?YXo_^H$IhV9^#66Xr-XxZh)D1Y?5 zB%B>v=Zof6%{=Wx+oj|Vr(m{8y|^;EUqVwmN#9|kLnx*n11WMVGV78F&T-V4N}{~@ zFe-LGjt$C4u1u0na=x|O-Pd`$SpHGQkh{9CSVrFn0} zZkpqY+0MP6rQwz$LI>Z@f2UX{K|^T6#|+;vnT z`K4?cI?XdJHf`dTyGNLZc{l6UM)%tu(zg$J9=mDh@xbL{srVbk+ufz=9#2Y<*2!M!)qsh5G3!PiMPu?gDG?;R6bX^tf}~1pEQTF>S1^aP3eYSb@D%Oe|g$5 zLS#iP0vhA!W*v4bfE|z=kUM^)O+f1Ty`fpuyPUo4n(dP9F%Th$IpkhXNLfHxY?)DC zbYE?s{|sYBab~KI7f#Q$$kokVKvU35;o?=uD%Yy`s{Zhf&t|U)Q>;nxg_jFblkYq2 z7bxEf7nDBpdU%~0MleG$WReOQZcLbMO(J{>y&BBvoIO2UvY8uHmE0Iq*y&KXoul*R zY~V*kl#*Eho3_QDZTNvS5Nd+=57zH5YAk9Rt=-v{8k!YQ)mibH+1lEoJHYN`znD%y zb?1cU;cMSj_pe@#zhRrxwD%(NVsm<5`cbY(x>NcR`>LmzC(Coe^DWz<=irp?6s5AN*ha`O?m~Y1zr!!oku9$+?T|a&4|cQa-$SqD5hSE z=BACQeyQqu_1C_E@O@^=v-~!@gc~{1`Z2dSySQOK zX##pE;*j^Dm71;24{~(!dSH~6{>B4CHQX3Kw3#XCNBg9Z*>f|&Yth%v*50fI51Q6$ z#rDKz#)_`yt?c>1T}%94H_ZB~IJaNKPvEJ*m_W@5)e{edLWMMhGlg%6B1A5WJlO!< zWTs%XNnFxfgONh3SNf{9OfVP-)XnNtcqx{v;G^~f)~e&UwP$m}nGUf*nONG*Wyi_Z zM`GuO?8)ai8o?@=O3TOOcka@^rT5vj!i{-^xun~}9`#-ctVx(lEWPx2Pk+cGMnoVx zgp4HoF8rMZ8SaqF-&lRoMn(Rn&qC(B$SU06g*ZM zpKRymMVfdcypxU^$6uArxNkmo3S{L1RdyaLraGz1eLbBW?`&IJaJj(sUL|L*$(8Y3 z&$LD3_j`_6{|GwcY#09AK-?L-o0Y6`<+0Yhlc!D32X@E1Bkv%n)ST3uLabzPdd2Ry zu*w~k&H=l3r$~;n#wjn=QqI0y8r513o9|a3$2)cwR$$9rq!b;YCA*^zC?AO8U}`Wv zZwv+l);}+6Z9J)Xv?l4U3+L{GUQU$v+z`0V_KBj_MK`5LJ-9EniWn6<+jWc^#J!)s z<}!lAdVCFG4SY?&p6a@B!+Q5gFT=Fb)E8W8`8S`(1OzP=B8D__n`VWx_vsO%;0x;uMoLM*#qotc#^Q=7XyrgoUJ z_XG2KbjU(NTSeQ;HX#HaF>|2x5xf8Eb!6dUbnbCquIF0W?aJDBI(2t_lc_hYE^aPvF|zdkFy&Qz1+mdEic6 z-Fn0dT%L2L@c z+TDe&3ui+LDfy*`w*Xx))#uEhM}Sb2Eq#r%hfMBmwJ`Zw^Bf4_F+zB1EtQ~Ox@ z+A!wM0ooL4i96$EQqKJ5G_|Lb_xkvckoxYhx8L9D`Ceny@SabrRZ?}{Z{cWHEqsqy zoKcFD_~JO=8=V++XSKF(?X&0)>CeY2*6)$(DMR1=>is&Nc}w%8#Lwofr~Z6>cDT#F z)xKNHPU~`Fu7-x1q-xDqVtdMP@%>`Cj+e2XKUq^@wrm&_)PuCGFu zPS3Bzp|m&iPn9Z6TO8Ak7Ql?eGf03Xk>*bVIZ*I{BrFL}WF2`yG64Xg6T$8{CeFzb zO`uWq@V{*Iq9}Ae8URepqv&`-D2WO3Cj|ymO~Je;bzo31(G={3aDq9}tw}+_m}myc zEqcE@Av%PzvQ&UVmAKF|nlo z()d^FFn1Q61jUlVXyFV3e?J1W{($+~{r^C}4Eb!(&J6zPfr=;F&`4FBWj-|#S77|Z~Uva~|l zA>nXaD@z-=p{*6l7>P8nMHwLN{$L%bVN5)gK>8Cqm>>H$7WIFzXln)u&!jQjX*BYm zDsTy+F==5zG&)G1FDPh-S1^@GiwxWOYmfd;*@najjvx{37&Hp#FOku~|3bjnm;g5- z8R$cdNH9YPf@nyBpolOM1c5Y!8Nu*GeS|*>{2NdFAMyS!QvM7-3Hl!{Xe5GQh)2Nu zA#hRv0fI0xBtY-ZhF!Nt+t_UF%^{Gms~>sSB~@O7}UbdTyA&FGYB0By33S^rt=^oBAM zwA1B{sCW7&n`q8KA04nG^4TIzW3#c9=wunG9+|PFv(8f@%=0j<(KiDvfqK`VoQ;LkP~pt+$%)8>6kbX zX<-M~8@p~sJoOcwUY(QMZ0Sm!;`(j<^!=4nQ)BVTfXGM#%>=1{bxU|bj4qy|IJyGE zEz8t%sm$BMBgvOKQ?83`6TJcf2F(@RMDkzmlN>6E6nVO1w~KIz3_04sXM68L#6h2U o&9w)I*cG{extCw5sjxCoeOv^&cwnyc*Wb>;cE3%zmEXz#06+BqIsgCw literal 0 HcmV?d00001 diff --git a/assets/icons/Irda/IrdaSend_128x64.png b/assets/icons/Irda/IrdaSend_128x64.png new file mode 100644 index 0000000000000000000000000000000000000000..33e2cecb5feec9c19ee486d9ba949eb4abd96cdd GIT binary patch literal 4219 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 mY4b5O|M +#include #include #include "check.h" @@ -11,11 +12,6 @@ extern "C" { // define for test case "link against furi memmgr" #define FURI_MEMMGR_GUARD 1 -void* malloc(size_t size); -void free(void* ptr); -void* realloc(void* ptr, size_t size); -void* calloc(size_t count, size_t size); - size_t memmgr_get_free_heap(void); size_t memmgr_get_minimum_free_heap(void); diff --git a/lib/irda/irda.c b/lib/irda/irda.c index 550caa5c..0d9dfc4b 100644 --- a/lib/irda/irda.c +++ b/lib/irda/irda.c @@ -12,6 +12,7 @@ struct IrdaHandler { typedef struct { IrdaAlloc alloc; IrdaDecode decode; + IrdaReset reset; IrdaFree free; } IrdaDecoders; @@ -24,6 +25,8 @@ typedef struct { const char* name; IrdaDecoders decoder; IrdaEncoders encoder; + uint8_t address_length; + uint8_t command_length; } IrdaProtocolImplementation; @@ -35,9 +38,12 @@ static const IrdaProtocolImplementation irda_protocols[] = { .decoder = { .alloc = irda_decoder_samsung32_alloc, .decode = irda_decoder_samsung32_decode, + .reset = irda_decoder_samsung32_reset, .free = irda_decoder_samsung32_free}, .encoder = { - .encode = irda_encoder_samsung32_encode} + .encode = irda_encoder_samsung32_encode}, + .address_length = 2, + .command_length = 2, }, // #1 { .protocol = IrdaProtocolNEC, @@ -45,9 +51,25 @@ static const IrdaProtocolImplementation irda_protocols[] = { .decoder = { .alloc = irda_decoder_nec_alloc, .decode = irda_decoder_nec_decode, + .reset = irda_decoder_nec_reset, .free = irda_decoder_nec_free}, .encoder = { - .encode = irda_encoder_nec_encode} + .encode = irda_encoder_nec_encode}, + .address_length = 2, + .command_length = 2, + }, + // #2 + { .protocol = IrdaProtocolNECext, + .name = "NECext", + .decoder = { + .alloc = irda_decoder_necext_alloc, + .decode = irda_decoder_nec_decode, + .reset = irda_decoder_nec_reset, + .free = irda_decoder_nec_free}, + .encoder = { + .encode = irda_encoder_necext_encode}, + .address_length = 4, + .command_length = 2, }, }; @@ -93,6 +115,12 @@ void irda_free_decoder(IrdaHandler* handler) { free(handler); } +void irda_reset_decoder(IrdaHandler* handler) { + for (int i = 0; i < COUNT_OF(irda_protocols); ++i) { + irda_protocols[i].decoder.reset(handler->ctx[i]); + } +} + void irda_send(const IrdaMessage* message, int times) { furi_assert(message); @@ -109,3 +137,11 @@ const char* irda_get_protocol_name(IrdaProtocol protocol) { return irda_protocols[protocol].name; } +uint8_t irda_get_protocol_address_length(IrdaProtocol protocol) { + return irda_protocols[protocol].address_length; +} + +uint8_t irda_get_protocol_command_length(IrdaProtocol protocol) { + return irda_protocols[protocol].command_length; +} + diff --git a/lib/irda/irda.h b/lib/irda/irda.h index 260c2a50..42411724 100644 --- a/lib/irda/irda.h +++ b/lib/irda/irda.h @@ -13,6 +13,7 @@ typedef struct IrdaHandler IrdaHandler; typedef enum { IrdaProtocolSamsung32 = 0, IrdaProtocolNEC = 1, + IrdaProtocolNECext = 2, } IrdaProtocol; typedef struct { @@ -50,6 +51,13 @@ const IrdaMessage* irda_decode(IrdaHandler* handler, bool level, uint32_t durati */ void irda_free_decoder(IrdaHandler* handler); +/** + * Reset IRDA decoder. + * + * \param[in] handler - handler to irda decoders. Should be aquired with \c irda_alloc_decoder(). + */ +void irda_reset_decoder(IrdaHandler* handler); + /** * Send message over IRDA. * @@ -66,6 +74,22 @@ void irda_send(const IrdaMessage* message, int times); */ const char* irda_get_protocol_name(IrdaProtocol protocol); +/** + * Get address length by protocol enum. + * + * \param[in] protocol - protocol identifier. + * \return length of address in nibbles. + */ +uint8_t irda_get_protocol_address_length(IrdaProtocol protocol); + +/** + * Get command length by protocol enum. + * + * \param[in] protocol - protocol identifier. + * \return length of command in nibbles. + */ +uint8_t irda_get_protocol_command_length(IrdaProtocol protocol); + #ifdef __cplusplus } #endif diff --git a/lib/irda/irda_common_decoder.c b/lib/irda/irda_common_decoder.c index ce6e7e58..8710e974 100644 --- a/lib/irda/irda_common_decoder.c +++ b/lib/irda/irda_common_decoder.c @@ -1,3 +1,4 @@ +#include "irda_common_decoder_i.h" #include #include #include "irda_i.h" @@ -164,3 +165,12 @@ void irda_common_decoder_free(void* decoder) { free(decoder); } +void irda_common_decoder_reset(void* decoder) { + furi_assert(decoder); + IrdaCommonDecoder* common_decoder = decoder; + + common_decoder->state = IrdaCommonStateWaitPreamble; + common_decoder->timings_cnt = 0; + common_decoder->databit_cnt = 0; +} + diff --git a/lib/irda/irda_common_decoder_i.h b/lib/irda/irda_common_decoder_i.h index 6a25237a..e61f6706 100644 --- a/lib/irda/irda_common_decoder_i.h +++ b/lib/irda/irda_common_decoder_i.h @@ -68,5 +68,6 @@ static inline void shift_left_array(uint32_t *array, uint32_t len, uint32_t shif IrdaMessage* irda_common_decode(IrdaCommonDecoder *decoder, bool level, uint32_t duration); void* irda_common_decoder_alloc(const IrdaCommonProtocolSpec *protocol); void irda_common_decoder_free(void* decoder); +void irda_common_decoder_reset(void* decoder); DecodeStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder); diff --git a/lib/irda/irda_i.h b/lib/irda/irda_i.h index 9f6e9e53..392ca390 100644 --- a/lib/irda/irda_i.h +++ b/lib/irda/irda_i.h @@ -7,6 +7,7 @@ typedef void* (*IrdaAlloc) (void); typedef IrdaMessage* (*IrdaDecode) (void* ctx, bool level, uint32_t duration); +typedef void (*IrdaReset) (void*); typedef void (*IrdaFree) (void*); typedef void (*IrdaEncode)(uint32_t address, uint32_t command, bool repeat); diff --git a/lib/irda/irda_protocol_defs_i.h b/lib/irda/irda_protocol_defs_i.h index a544834d..f469aa0d 100644 --- a/lib/irda/irda_protocol_defs_i.h +++ b/lib/irda/irda_protocol_defs_i.h @@ -36,7 +36,10 @@ #define IRDA_NEC_BIT_TOLERANCE 120 // us void* irda_decoder_nec_alloc(void); +void* irda_decoder_necext_alloc(void); void irda_encoder_nec_encode(uint32_t address, uint32_t command, bool repeat); +void irda_encoder_necext_encode(uint32_t address, uint32_t command, bool repeat); +void irda_decoder_nec_reset(void* decoder); void irda_decoder_nec_free(void* decoder); IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duration); @@ -73,6 +76,7 @@ IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duratio void* irda_decoder_samsung32_alloc(void); void irda_encoder_samsung32_encode(uint32_t address, uint32_t command, bool repeat); +void irda_decoder_samsung32_reset(void* decoder); void irda_decoder_samsung32_free(void* decoder); IrdaMessage* irda_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration); diff --git a/lib/irda/nec/irda_decoder_nec.c b/lib/irda/nec/irda_decoder_nec.c index 506adadd..02ab5c42 100644 --- a/lib/irda/nec/irda_decoder_nec.c +++ b/lib/irda/nec/irda_decoder_nec.c @@ -3,9 +3,12 @@ #include #include "../irda_i.h" + static bool interpret_nec(IrdaCommonDecoder* decoder); +static bool interpret_necext(IrdaCommonDecoder* decoder); static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder); + static const IrdaCommonProtocolSpec protocol_nec = { { IRDA_NEC_PREAMBULE_MARK, @@ -23,15 +26,33 @@ static const IrdaCommonProtocolSpec protocol_nec = { decode_repeat_nec, }; +static const IrdaCommonProtocolSpec protocol_necext = { + { + IRDA_NEC_PREAMBULE_MARK, + IRDA_NEC_PREAMBULE_SPACE, + IRDA_NEC_BIT1_MARK, + IRDA_NEC_BIT1_SPACE, + IRDA_NEC_BIT0_MARK, + IRDA_NEC_BIT0_SPACE, + IRDA_NEC_PREAMBLE_TOLERANCE, + IRDA_NEC_BIT_TOLERANCE, + }, + 32, + irda_common_decode_pdwm, + interpret_necext, + decode_repeat_nec, +}; + static bool interpret_nec(IrdaCommonDecoder* decoder) { furi_assert(decoder); bool result = false; - uint16_t address = decoder->data[0] | (decoder->data[1] << 8); + uint8_t address = decoder->data[0]; + uint8_t address_inverse = decoder->data[1]; uint8_t command = decoder->data[2]; uint8_t command_inverse = decoder->data[3]; - if((command == (uint8_t)~command_inverse)) { + if ((command == (uint8_t) ~command_inverse) && (address == (uint8_t) ~address_inverse)) { decoder->message.command = command; decoder->message.address = address; decoder->message.repeat = false; @@ -41,6 +62,24 @@ static bool interpret_nec(IrdaCommonDecoder* decoder) { return result; } +// Some NEC's extensions allow 16 bit address +static bool interpret_necext(IrdaCommonDecoder* decoder) { + furi_assert(decoder); + + bool result = false; + uint8_t command = decoder->data[2]; + uint8_t command_inverse = decoder->data[3]; + + if(command == (uint8_t)~command_inverse) { + decoder->message.command = command; + decoder->message.address = decoder->data[0] | (decoder->data[1] << 8); + decoder->message.repeat = false; + result = true; + } + + return result; +} + // timings start from Space (delay between message and repeat) static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder) { furi_assert(decoder); @@ -69,6 +108,10 @@ void* irda_decoder_nec_alloc(void) { return irda_common_decoder_alloc(&protocol_nec); } +void* irda_decoder_necext_alloc(void) { + return irda_common_decoder_alloc(&protocol_necext); +} + IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duration) { return irda_common_decode(decoder, level, duration); } @@ -76,3 +119,8 @@ IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duratio void irda_decoder_nec_free(void* decoder) { irda_common_decoder_free(decoder); } + +void irda_decoder_nec_reset(void* decoder) { + irda_common_decoder_reset(decoder); +} + diff --git a/lib/irda/nec/irda_encoder_nec.c b/lib/irda/nec/irda_encoder_nec.c index 5a6ca1d9..079f784e 100644 --- a/lib/irda/nec/irda_encoder_nec.c +++ b/lib/irda/nec/irda_encoder_nec.c @@ -42,3 +42,21 @@ void irda_encoder_nec_encode(uint32_t addr, uint32_t cmd, bool repeat) { } } +// Some NEC's extensions allow 16 bit address +void irda_encoder_necext_encode(uint32_t addr, uint32_t cmd, bool repeat) { + uint16_t address = addr & 0xFFFF; + uint8_t command = cmd & 0xFF; + uint8_t command_inverse = (uint8_t) ~command; + + if (!repeat) { + irda_encode_nec_preamble(); + irda_encode_byte(&encoder_timings, (uint8_t) address); + irda_encode_byte(&encoder_timings, (uint8_t) (address >> 8)); + irda_encode_byte(&encoder_timings, command); + irda_encode_byte(&encoder_timings, command_inverse); + irda_encode_bit(&encoder_timings, 1); + } else { + irda_encode_nec_repeat(); + } +} + diff --git a/lib/irda/samsung/irda_decoder_samsung.c b/lib/irda/samsung/irda_decoder_samsung.c index cebc94c4..e2eb67ef 100644 --- a/lib/irda/samsung/irda_decoder_samsung.c +++ b/lib/irda/samsung/irda_decoder_samsung.c @@ -85,3 +85,7 @@ void irda_decoder_samsung32_free(void* decoder) { irda_common_decoder_free(decoder); } +void irda_decoder_samsung32_reset(void* decoder) { + irda_common_decoder_reset(decoder); +} +