Просмотр исходного кода

[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 <who.just.the.doctor@gmail.com>
Albert Kharisov 4 лет назад
Родитель
Сommit
31c31db479
62 измененных файлов с 2568 добавлено и 375 удалено
  1. 6 0
      .gitignore
  2. 26 0
      applications/gui/canvas.c
  3. 22 0
      applications/gui/canvas.h
  4. 14 8
      applications/gui/elements.c
  5. 12 0
      applications/gui/elements.h
  6. 288 0
      applications/gui/modules/button_menu.c
  7. 73 0
      applications/gui/modules/button_menu.h
  8. 40 16
      applications/gui/modules/submenu.c
  9. 9 8
      applications/gui/modules/submenu.h
  10. 26 0
      applications/irda/irda-app-event.hpp
  11. 46 0
      applications/irda/irda-app-receiver.cpp
  12. 19 0
      applications/irda/irda-app-receiver.hpp
  13. 130 0
      applications/irda/irda-app-remote-manager.cpp
  14. 53 0
      applications/irda/irda-app-remote-manager.hpp
  15. 110 0
      applications/irda/irda-app-view-manager.cpp
  16. 52 0
      applications/irda/irda-app-view-manager.hpp
  17. 156 0
      applications/irda/irda-app.cpp
  18. 108 0
      applications/irda/irda-app.hpp
  19. 0 146
      applications/irda/irda-decoder/irda-decoder-nec.c
  20. 0 39
      applications/irda/irda-decoder/irda-decoder-nec.h
  21. 0 12
      applications/irda/irda-decoder/irda-decoder-types.h
  22. 0 41
      applications/irda/irda-decoder/irda-decoder.c
  23. 0 17
      applications/irda/irda-decoder/irda-decoder.h
  24. 9 0
      applications/irda/irda-runner.cpp
  25. 1 1
      applications/irda/irda_app_old.c
  26. 0 19
      applications/irda/irda_protocols.h
  27. 35 0
      applications/irda/scene/irda-app-scene-edit-delete-done.cpp
  28. 81 0
      applications/irda/scene/irda-app-scene-edit-delete.cpp
  29. 54 0
      applications/irda/scene/irda-app-scene-edit-key-select.cpp
  30. 31 0
      applications/irda/scene/irda-app-scene-edit-rename-done.cpp
  31. 46 0
      applications/irda/scene/irda-app-scene-edit-rename.cpp
  32. 74 0
      applications/irda/scene/irda-app-scene-edit.cpp
  33. 34 0
      applications/irda/scene/irda-app-scene-learn-done-after.cpp
  34. 42 0
      applications/irda/scene/irda-app-scene-learn-done.cpp
  35. 51 0
      applications/irda/scene/irda-app-scene-learn-enter-name.cpp
  36. 63 0
      applications/irda/scene/irda-app-scene-learn-success.cpp
  37. 31 0
      applications/irda/scene/irda-app-scene-learn.cpp
  38. 59 0
      applications/irda/scene/irda-app-scene-remote-list.cpp
  39. 75 0
      applications/irda/scene/irda-app-scene-remote.cpp
  40. 59 0
      applications/irda/scene/irda-app-scene-start.cpp
  41. 57 0
      applications/irda/scene/irda-app-scene-universal.cpp
  42. 130 0
      applications/irda/scene/irda-app-scene.hpp
  43. 6 2
      applications/irda_monitor/irda_monitor.c
  44. 11 2
      applications/tests/irda_decoder/irda_decoder_test.c
  45. 55 55
      applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.srcdata
  46. 223 0
      applications/tests/irda_decoder/test_data/irda_decoder_necext_test_data.srcdata
  47. BIN
      assets/icons/Irda/IrdaArrowDown_4x8.png
  48. BIN
      assets/icons/Irda/IrdaArrowUp_4x8.png
  49. BIN
      assets/icons/Irda/IrdaLearnShort_128x31.png
  50. BIN
      assets/icons/Irda/IrdaLearn_128x64.png
  51. BIN
      assets/icons/Irda/IrdaSendShort_128x34.png
  52. BIN
      assets/icons/Irda/IrdaSend_128x64.png
  53. 1 5
      core/furi/memmgr.h
  54. 38 2
      lib/irda/irda.c
  55. 24 0
      lib/irda/irda.h
  56. 10 0
      lib/irda/irda_common_decoder.c
  57. 1 0
      lib/irda/irda_common_decoder_i.h
  58. 1 0
      lib/irda/irda_i.h
  59. 4 0
      lib/irda/irda_protocol_defs_i.h
  60. 50 2
      lib/irda/nec/irda_decoder_nec.c
  61. 18 0
      lib/irda/nec/irda_encoder_nec.c
  62. 4 0
      lib/irda/samsung/irda_decoder_samsung.c

+ 6 - 0
.gitignore

@@ -1,4 +1,10 @@
 *.swp
 *.swp
+*.gdb_history
+
+
+# LSP
+.cache
+compile_commands.json
 
 
 # JetBrains IDEs
 # JetBrains IDEs
 .idea/
 .idea/

+ 26 - 0
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);
     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) {
 void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
     furi_assert(canvas);
     furi_assert(canvas);
     x += canvas->offset_x;
     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);
     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) {
 void canvas_draw_line(Canvas* canvas, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
     furi_assert(canvas);
     furi_assert(canvas);
     x1 += canvas->offset_x;
     x1 += canvas->offset_x;

+ 22 - 0
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);
 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
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 14 - 8
applications/gui/elements.c

@@ -1,6 +1,8 @@
 #include "elements.h"
 #include "elements.h"
+#include "gui/canvas.h"
 #include <assets_icons.h>
 #include <assets_icons.h>
 #include <gui/icon_i.h>
 #include <gui/icon_i.h>
+#include <m-string.h>
 #include <furi.h>
 #include <furi.h>
 #include "canvas_i.h"
 #include "canvas_i.h"
 #include <string.h>
 #include <string.h>
@@ -44,7 +46,7 @@ void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total) {
     }
     }
     // Position block
     // Position block
     if(total) {
     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));
         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 width,
     uint8_t height) {
     uint8_t height) {
     furi_assert(canvas);
     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) {
 void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) {

+ 12 - 0
applications/gui/elements.h

@@ -106,6 +106,18 @@ void elements_slightly_rounded_frame(
     uint8_t width,
     uint8_t width,
     uint8_t height);
     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
  * Trim string buffer to fit width in pixels
  * @param string - string to trim
  * @param string - string to trim

+ 288 - 0
applications/gui/modules/button_menu.c

@@ -0,0 +1,288 @@
+#include "button_menu.h"
+#include "gui/canvas.h"
+#include "gui/elements.h"
+#include <m-array.h>
+#include <furi.h>
+#include <stdint.h>
+
+#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);
+}

+ 73 - 0
applications/gui/modules/button_menu.h

@@ -0,0 +1,73 @@
+#pragma once
+#include <stdint.h>
+#include <gui/view.h>
+
+#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

+ 40 - 16
applications/gui/modules/submenu.c

@@ -1,7 +1,9 @@
 #include "submenu.h"
 #include "submenu.h"
+#include "gui/canvas.h"
 #include <m-array.h>
 #include <m-array.h>
 #include <furi.h>
 #include <furi.h>
 #include <gui/elements.h>
 #include <gui/elements.h>
+#include <stdint.h>
 
 
 struct SubmenuItem {
 struct SubmenuItem {
     const char* label;
     const char* label;
@@ -18,6 +20,7 @@ struct Submenu {
 
 
 typedef struct {
 typedef struct {
     SubmenuItemArray_t items;
     SubmenuItemArray_t items;
+    const char* header;
     uint8_t position;
     uint8_t position;
     uint8_t window_position;
     uint8_t window_position;
 } SubmenuModel;
 } SubmenuModel;
@@ -33,34 +36,39 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) {
     const uint8_t item_width = 123;
     const uint8_t item_width = 123;
 
 
     canvas_clear(canvas);
     canvas_clear(canvas);
-    canvas_set_font(canvas, FontSecondary);
+    canvas_set_font(canvas, FontPrimary);
 
 
     uint8_t position = 0;
     uint8_t position = 0;
     SubmenuItemArray_it_t it;
     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);
     for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
         SubmenuItemArray_next(it)) {
         SubmenuItemArray_next(it)) {
         uint8_t item_position = position - model->window_position;
         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) {
             if(position == model->position) {
                 canvas_set_color(canvas, ColorBlack);
                 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_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 {
             } else {
                 canvas_set_color(canvas, ColorBlack);
                 canvas_set_color(canvas, ColorBlack);
             }
             }
             canvas_draw_str(
             canvas_draw_str(
                 canvas,
                 canvas,
                 6,
                 6,
-                (item_position * item_height) + item_height - 4,
+                y_offset + (item_position * item_height) + item_height - 4,
                 SubmenuItemArray_cref(it)->label);
                 SubmenuItemArray_cref(it)->label);
         }
         }
 
 
@@ -110,6 +118,7 @@ Submenu* submenu_alloc() {
             SubmenuItemArray_init(model->items);
             SubmenuItemArray_init(model->items);
             model->position = 0;
             model->position = 0;
             model->window_position = 0;
             model->window_position = 0;
+            model->header = NULL;
             return true;
             return true;
         });
         });
 
 
@@ -164,6 +173,7 @@ void submenu_clean(Submenu* submenu) {
             SubmenuItemArray_clean(model->items);
             SubmenuItemArray_clean(model->items);
             model->position = 0;
             model->position = 0;
             model->window_position = 0;
             model->window_position = 0;
+            model->header = NULL;
             return true;
             return true;
         });
         });
 }
 }
@@ -207,15 +217,17 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) {
 void submenu_process_up(Submenu* submenu) {
 void submenu_process_up(Submenu* submenu) {
     with_view_model(
     with_view_model(
         submenu->view, (SubmenuModel * model) {
         submenu->view, (SubmenuModel * model) {
+            uint8_t elements_on_screen = model->header ? 3 : 4;
             if(model->position > 0) {
             if(model->position > 0) {
                 model->position--;
                 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--;
                     model->window_position--;
                 }
                 }
             } else {
             } else {
                 model->position = SubmenuItemArray_size(model->items) - 1;
                 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;
             return true;
@@ -225,10 +237,12 @@ void submenu_process_up(Submenu* submenu) {
 void submenu_process_down(Submenu* submenu) {
 void submenu_process_down(Submenu* submenu) {
     with_view_model(
     with_view_model(
         submenu->view, (SubmenuModel * model) {
         submenu->view, (SubmenuModel * model) {
+            uint8_t elements_on_screen = model->header ? 3 : 4;
             if(model->position < (SubmenuItemArray_size(model->items) - 1)) {
             if(model->position < (SubmenuItemArray_size(model->items) - 1)) {
                 model->position++;
                 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++;
                     model->window_position++;
                 }
                 }
             } else {
             } else {
@@ -254,3 +268,13 @@ void submenu_process_ok(Submenu* submenu) {
         item->callback(item->callback_context, item->index);
         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;
+        });
+}

+ 9 - 8
applications/gui/modules/submenu.h

@@ -16,12 +16,6 @@ typedef void (*SubmenuItemCallback)(void* context, uint32_t index);
  */
  */
 Submenu* submenu_alloc();
 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
  * @brief Deinitialize and free submenu
  * @param submenu - Submenu instance
  * @param submenu - Submenu instance
@@ -59,11 +53,18 @@ void submenu_clean(Submenu* submenu);
 
 
 /**
 /**
  * @brief Set submenu item selector
  * @brief Set submenu item selector
- * @param submenu 
- * @param index 
+ * @param submenu
+ * @param index
  */
  */
 void submenu_set_selected_item(Submenu* submenu, uint32_t 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
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 26 - 0
applications/irda/irda-app-event.hpp

@@ -0,0 +1,26 @@
+#pragma once
+#include <stdint.h>
+#include <irda.h>
+#include <gui/modules/dialog_ex.h>
+
+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;
+};
+

+ 46 - 0
applications/irda/irda-app-receiver.cpp

@@ -0,0 +1,46 @@
+#include "irda-app.hpp"
+#include "irda.h"
+#include <api-hal-irda.h>
+
+void IrdaAppSignalReceiver::irda_rx_callback(void* ctx, bool level, uint32_t duration) {
+    IrdaAppEvent event;
+    const IrdaMessage* irda_message;
+    IrdaAppSignalReceiver* this_ = static_cast<IrdaAppSignalReceiver*>(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);
+}

+ 19 - 0
applications/irda/irda-app-receiver.hpp

@@ -0,0 +1,19 @@
+#include <furi.h>
+#include <irda.h>
+
+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;
+};
+

+ 130 - 0
applications/irda/irda-app-remote-manager.cpp

@@ -0,0 +1,130 @@
+#include "irda-app-remote-manager.hpp"
+#include "furi.h"
+#include <string>
+#include <utility>
+
+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<std::string> IrdaAppRemoteManager::get_button_list(void) const {
+    std::vector<std::string> 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<std::string> IrdaAppRemoteManager::get_remote_list() const {
+    std::vector<std::string> 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();
+}

+ 53 - 0
applications/irda/irda-app-remote-manager.hpp

@@ -0,0 +1,53 @@
+#pragma once
+#include <stdint.h>
+#include <string>
+#include <list>
+#include <vector>
+#include <irda.h>
+
+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<IrdaAppRemoteButton> 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<IrdaAppRemote> remotes;
+public:
+    std::vector<std::string> get_remote_list() const;
+    std::vector<std::string> 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() {};
+};
+

+ 110 - 0
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 <callback-connector.h>
+
+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<Gui*>(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<uint32_t>(IrdaAppViewManager::ViewType::ButtonMenu));
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(IrdaAppViewManager::ViewType::TextInput));
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(IrdaAppViewManager::ViewType::DialogEx));
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(IrdaAppViewManager::ViewType::Submenu));
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(view_type), view);
+}

+ 52 - 0
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 <furi.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#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);
+};
+

+ 156 - 0
applications/irda/irda-app.cpp

@@ -0,0 +1,156 @@
+#include "irda-app.hpp"
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <callback-connector.h>
+
+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<Scene>& 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<IrdaApp*>(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<IrdaApp*>(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;
+}

+ 108 - 0
applications/irda/irda-app.hpp

@@ -0,0 +1,108 @@
+#pragma once
+#include <map>
+#include <irda.h>
+#include <furi.h>
+#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 <forward_list>
+#include <stdint.h>
+
+
+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<Scene>& 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<Scene> previous_scenes_list;
+    Scene current_scene = Scene::Start;
+
+    std::map<Scene, IrdaAppScene*> 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()},
+    };
+};

+ 0 - 146
applications/irda/irda-decoder/irda-decoder-nec.c

@@ -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;
-}

+ 0 - 39
applications/irda/irda-decoder/irda-decoder-nec.h

@@ -1,39 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <stdbool.h>
-#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);

+ 0 - 12
applications/irda/irda-decoder/irda-decoder-types.h

@@ -1,12 +0,0 @@
-#pragma once
-#include <stdint.h>
-
-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;

+ 0 - 41
applications/irda/irda-decoder/irda-decoder.c

@@ -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;
-}

+ 0 - 17
applications/irda/irda-decoder/irda-decoder.h

@@ -1,17 +0,0 @@
-#pragma once
-#include <furi.h>
-#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);

+ 9 - 0
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;
+}

+ 1 - 1
applications/irda/irda_app.c → 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);
     osMessageQueueId_t event_queue = osMessageQueueNew(32, sizeof(AppEvent), NULL);
 
 
     State _state;
     State _state;

+ 0 - 19
applications/irda/irda_protocols.h

@@ -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

+ 35 - 0
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) {
+}

+ 81 - 0
applications/irda/scene/irda-app-scene-edit-delete.cpp

@@ -0,0 +1,81 @@
+#include "../irda-app.hpp"
+#include "irda.h"
+#include <string>
+#include <stdio.h>
+
+static void dialog_result_callback(DialogExResult result, void* context) {
+    auto app = static_cast<IrdaApp*>(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) {
+}

+ 54 - 0
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<IrdaApp*>(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);
+}

+ 31 - 0
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) {
+}

+ 46 - 0
applications/irda/scene/irda-app-scene-edit-rename.cpp

@@ -0,0 +1,46 @@
+#include "../irda-app.hpp"
+#include <cstdio>
+
+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) {
+}

+ 74 - 0
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<IrdaApp*>(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);
+}

+ 34 - 0
applications/irda/scene/irda-app-scene-learn-done-after.cpp

@@ -0,0 +1,34 @@
+#include "../irda-app.hpp"
+#include <string>
+#include <stdio.h>
+#include <gui/modules/popup.h>
+
+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) {
+}

+ 42 - 0
applications/irda/scene/irda-app-scene-learn-done.cpp

@@ -0,0 +1,42 @@
+#include "../irda-app.hpp"
+#include <string>
+#include <stdio.h>
+
+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);
+}

+ 51 - 0
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 <callback-connector.h>
+#include <string>
+#include <stdio.h>
+
+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) {
+}

+ 63 - 0
applications/irda/scene/irda-app-scene-learn-success.cpp

@@ -0,0 +1,63 @@
+#include "../irda-app.hpp"
+#include "irda.h"
+#include <string>
+#include <stdio.h>
+
+static void dialog_result_callback(DialogExResult result, void* context) {
+    auto app = static_cast<IrdaApp*>(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) {
+}

+ 31 - 0
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) {
+}

+ 59 - 0
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<IrdaApp*>(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);
+}

+ 75 - 0
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<IrdaApp*>(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);
+}

+ 59 - 0
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<IrdaApp*>(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);
+}

+ 57 - 0
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<IrdaApp*>(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);
+}

+ 130 - 0
applications/irda/scene/irda-app-scene.hpp

@@ -0,0 +1,130 @@
+#pragma once
+#include "../irda-app.hpp"
+#include <api-hal-irda.h>
+#include "irda.h"
+#include <gui/elements.h>
+#include <vector>
+#include <string>
+
+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<std::string> 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<std::string> 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<std::string> 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;
+};
+

+ 6 - 2
applications/irda_monitor/irda_monitor.c

@@ -110,9 +110,11 @@ int32_t irda_monitor_app(void* p) {
                 snprintf(
                 snprintf(
                     irda_monitor->display_text,
                     irda_monitor->display_text,
                     sizeof(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_name(message->protocol),
+                    irda_get_protocol_address_length(message->protocol),
                     message->address,
                     message->address,
+                    irda_get_protocol_command_length(message->protocol),
                     message->command,
                     message->command,
                     message->repeat ? " R" : "");
                     message->repeat ? " R" : "");
                 view_port_update(view_port);
                 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 || (distance > (IRDA_TIMINGS_SIZE / 2))) {
                 if(message) {
                 if(message) {
                     printf(
                     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_name(message->protocol),
+                        irda_get_protocol_address_length(message->protocol),
                         message->address,
                         message->address,
+                        irda_get_protocol_command_length(message->protocol),
                         message->command,
                         message->command,
                         message->repeat ? " R" : "");
                         message->repeat ? " R" : "");
                 } else {
                 } else {

+ 11 - 2
applications/tests/irda_decoder/irda_decoder_test.c

@@ -2,6 +2,7 @@
 #include "../minunit.h"
 #include "../minunit.h"
 #include "irda.h"
 #include "irda.h"
 #include "test_data/irda_decoder_nec_test_data.srcdata"
 #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"
 #include "test_data/irda_decoder_samsung_test_data.srcdata"
 
 
 #define RUN_DECODER(data, expected) \
 #define RUN_DECODER(data, expected) \
@@ -52,10 +53,12 @@ MU_TEST(test_samsung32) {
     RUN_DECODER(test_samsung32_input1, test_samsung32_expected1);
     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_samsung32_input1, test_samsung32_expected1);
     RUN_DECODER(test_nec_input1, test_nec_expected1);
     RUN_DECODER(test_nec_input1, test_nec_expected1);
     RUN_DECODER(test_samsung32_input1, test_samsung32_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);
     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);
     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_TEST_SUITE(test_irda_decoder) {
     MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
     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_nec1);
     MU_RUN_TEST(test_nec2);
     MU_RUN_TEST(test_nec2);
     MU_RUN_TEST(test_samsung32);
     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() {
 int run_minunit_test_irda_decoder() {

+ 55 - 55
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};
 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[] = {
 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[] = {
 const uint32_t test_nec_input2[] = {
@@ -124,57 +124,57 @@ const uint32_t test_nec_input2[] = {
 };
 };
 
 
 const IrdaMessage test_nec_expected2[] = {
 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},
 };
 };
 
 

+ 223 - 0
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},
+};
+

BIN
assets/icons/Irda/IrdaArrowDown_4x8.png


BIN
assets/icons/Irda/IrdaArrowUp_4x8.png


BIN
assets/icons/Irda/IrdaLearnShort_128x31.png


BIN
assets/icons/Irda/IrdaLearn_128x64.png


BIN
assets/icons/Irda/IrdaSendShort_128x34.png


BIN
assets/icons/Irda/IrdaSend_128x64.png


+ 1 - 5
core/furi/memmgr.h

@@ -1,6 +1,7 @@
 #pragma once
 #pragma once
 
 
 #include <stddef.h>
 #include <stddef.h>
+#include <stdlib.h>
 #include <string.h>
 #include <string.h>
 #include "check.h"
 #include "check.h"
 
 
@@ -11,11 +12,6 @@ extern "C" {
 // define for test case "link against furi memmgr"
 // define for test case "link against furi memmgr"
 #define FURI_MEMMGR_GUARD 1
 #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_free_heap(void);
 size_t memmgr_get_minimum_free_heap(void);
 size_t memmgr_get_minimum_free_heap(void);
 
 

+ 38 - 2
lib/irda/irda.c

@@ -12,6 +12,7 @@ struct IrdaHandler {
 typedef struct {
 typedef struct {
     IrdaAlloc alloc;
     IrdaAlloc alloc;
     IrdaDecode decode;
     IrdaDecode decode;
+    IrdaReset reset;
     IrdaFree free;
     IrdaFree free;
 } IrdaDecoders;
 } IrdaDecoders;
 
 
@@ -24,6 +25,8 @@ typedef struct {
     const char* name;
     const char* name;
     IrdaDecoders decoder;
     IrdaDecoders decoder;
     IrdaEncoders encoder;
     IrdaEncoders encoder;
+    uint8_t address_length;
+    uint8_t command_length;
 } IrdaProtocolImplementation;
 } IrdaProtocolImplementation;
 
 
 
 
@@ -35,9 +38,12 @@ static const IrdaProtocolImplementation irda_protocols[] = {
       .decoder = {
       .decoder = {
           .alloc = irda_decoder_samsung32_alloc,
           .alloc = irda_decoder_samsung32_alloc,
           .decode = irda_decoder_samsung32_decode,
           .decode = irda_decoder_samsung32_decode,
+          .reset = irda_decoder_samsung32_reset,
           .free = irda_decoder_samsung32_free},
           .free = irda_decoder_samsung32_free},
       .encoder = {
       .encoder = {
-          .encode = irda_encoder_samsung32_encode}
+          .encode = irda_encoder_samsung32_encode},
+      .address_length = 2,
+      .command_length = 2,
     },
     },
     // #1
     // #1
     { .protocol = IrdaProtocolNEC,
     { .protocol = IrdaProtocolNEC,
@@ -45,9 +51,25 @@ static const IrdaProtocolImplementation irda_protocols[] = {
       .decoder = {
       .decoder = {
           .alloc = irda_decoder_nec_alloc,
           .alloc = irda_decoder_nec_alloc,
           .decode = irda_decoder_nec_decode,
           .decode = irda_decoder_nec_decode,
+          .reset = irda_decoder_nec_reset,
           .free = irda_decoder_nec_free},
           .free = irda_decoder_nec_free},
       .encoder = {
       .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);
     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) {
 void irda_send(const IrdaMessage* message, int times) {
     furi_assert(message);
     furi_assert(message);
 
 
@@ -109,3 +137,11 @@ const char* irda_get_protocol_name(IrdaProtocol protocol) {
     return irda_protocols[protocol].name;
     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;
+}
+

+ 24 - 0
lib/irda/irda.h

@@ -13,6 +13,7 @@ typedef struct IrdaHandler IrdaHandler;
 typedef enum {
 typedef enum {
     IrdaProtocolSamsung32 = 0,
     IrdaProtocolSamsung32 = 0,
     IrdaProtocolNEC = 1,
     IrdaProtocolNEC = 1,
+    IrdaProtocolNECext = 2,
 } IrdaProtocol;
 } IrdaProtocol;
 
 
 typedef struct {
 typedef struct {
@@ -50,6 +51,13 @@ const IrdaMessage* irda_decode(IrdaHandler* handler, bool level, uint32_t durati
  */
  */
 void irda_free_decoder(IrdaHandler* handler);
 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.
  * Send message over IRDA.
  *
  *
@@ -66,6 +74,22 @@ void irda_send(const IrdaMessage* message, int times);
  */
  */
 const char* irda_get_protocol_name(IrdaProtocol protocol);
 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
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 10 - 0
lib/irda/irda_common_decoder.c

@@ -1,3 +1,4 @@
+#include "irda_common_decoder_i.h"
 #include <stdbool.h>
 #include <stdbool.h>
 #include <furi.h>
 #include <furi.h>
 #include "irda_i.h"
 #include "irda_i.h"
@@ -164,3 +165,12 @@ void irda_common_decoder_free(void* decoder) {
     free(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;
+}
+

+ 1 - 0
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);
 IrdaMessage* irda_common_decode(IrdaCommonDecoder *decoder, bool level, uint32_t duration);
 void* irda_common_decoder_alloc(const IrdaCommonProtocolSpec *protocol);
 void* irda_common_decoder_alloc(const IrdaCommonProtocolSpec *protocol);
 void irda_common_decoder_free(void* decoder);
 void irda_common_decoder_free(void* decoder);
+void irda_common_decoder_reset(void* decoder);
 DecodeStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder);
 DecodeStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder);
 
 

+ 1 - 0
lib/irda/irda_i.h

@@ -7,6 +7,7 @@
 
 
 typedef void* (*IrdaAlloc) (void);
 typedef void* (*IrdaAlloc) (void);
 typedef IrdaMessage* (*IrdaDecode) (void* ctx, bool level, uint32_t duration);
 typedef IrdaMessage* (*IrdaDecode) (void* ctx, bool level, uint32_t duration);
+typedef void (*IrdaReset) (void*);
 typedef void (*IrdaFree) (void*);
 typedef void (*IrdaFree) (void*);
 
 
 typedef void (*IrdaEncode)(uint32_t address, uint32_t command, bool repeat);
 typedef void (*IrdaEncode)(uint32_t address, uint32_t command, bool repeat);

+ 4 - 0
lib/irda/irda_protocol_defs_i.h

@@ -36,7 +36,10 @@
 #define IRDA_NEC_BIT_TOLERANCE          120     // us
 #define IRDA_NEC_BIT_TOLERANCE          120     // us
 
 
 void* irda_decoder_nec_alloc(void);
 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_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);
 void irda_decoder_nec_free(void* decoder);
 IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duration);
 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_decoder_samsung32_alloc(void);
 void irda_encoder_samsung32_encode(uint32_t address, uint32_t command, bool repeat);
 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);
 void irda_decoder_samsung32_free(void* decoder);
 IrdaMessage* irda_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration);
 IrdaMessage* irda_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration);
 
 

+ 50 - 2
lib/irda/nec/irda_decoder_nec.c

@@ -3,9 +3,12 @@
 #include <furi.h>
 #include <furi.h>
 #include "../irda_i.h"
 #include "../irda_i.h"
 
 
+
 static bool interpret_nec(IrdaCommonDecoder* decoder);
 static bool interpret_nec(IrdaCommonDecoder* decoder);
+static bool interpret_necext(IrdaCommonDecoder* decoder);
 static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder);
 static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder);
 
 
+
 static const IrdaCommonProtocolSpec protocol_nec = {
 static const IrdaCommonProtocolSpec protocol_nec = {
     {
     {
         IRDA_NEC_PREAMBULE_MARK,
         IRDA_NEC_PREAMBULE_MARK,
@@ -23,15 +26,33 @@ static const IrdaCommonProtocolSpec protocol_nec = {
     decode_repeat_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) {
 static bool interpret_nec(IrdaCommonDecoder* decoder) {
     furi_assert(decoder);
     furi_assert(decoder);
 
 
     bool result = false;
     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 = decoder->data[2];
     uint8_t command_inverse = decoder->data[3];
     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.command = command;
         decoder->message.address = address;
         decoder->message.address = address;
         decoder->message.repeat = false;
         decoder->message.repeat = false;
@@ -41,6 +62,24 @@ static bool interpret_nec(IrdaCommonDecoder* decoder) {
     return result;
     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)
 // timings start from Space (delay between message and repeat)
 static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder) {
 static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder) {
     furi_assert(decoder);
     furi_assert(decoder);
@@ -69,6 +108,10 @@ void* irda_decoder_nec_alloc(void) {
     return irda_common_decoder_alloc(&protocol_nec);
     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) {
 IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duration) {
     return irda_common_decode(decoder, level, 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) {
 void irda_decoder_nec_free(void* decoder) {
     irda_common_decoder_free(decoder);
     irda_common_decoder_free(decoder);
 }
 }
+
+void irda_decoder_nec_reset(void* decoder) {
+    irda_common_decoder_reset(decoder);
+}
+

+ 18 - 0
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();
+    }
+}
+

+ 4 - 0
lib/irda/samsung/irda_decoder_samsung.c

@@ -85,3 +85,7 @@ void irda_decoder_samsung32_free(void* decoder) {
     irda_common_decoder_free(decoder);
     irda_common_decoder_free(decoder);
 }
 }
 
 
+void irda_decoder_samsung32_reset(void* decoder) {
+    irda_common_decoder_reset(decoder);
+}
+