Przeglądaj źródła

[FL-1758] SubGhz refactoring part 1 (#689)

* SubGhz: refactoring
* WeGet: Add support for outputting formatted lines, events center button pressed, center button released
* Variable Item: slightly changed the display of data on the screen
* SubGhz: add show errors, add show preset, refactoring
* SubGhz: refactoring transmitter
* SubGhz: removed unused modules
* SubGhz: Add FuriHalSubGhzPresetOok270Async setting menu
* SubGhz: fix annotation
* SubGhz: add support Nero Radio

Co-authored-by: あく <alleteam@gmail.com>
Skorpionm 4 lat temu
rodzic
commit
f385340b2e
34 zmienionych plików z 1488 dodań i 796 usunięć
  1. 17 2
      applications/gui/modules/variable-item-list.c
  2. 3 0
      applications/gui/modules/variable-item-list.h
  3. 14 0
      applications/gui/modules/widget.c
  4. 17 0
      applications/gui/modules/widget.h
  5. 12 1
      applications/gui/modules/widget_elements/widget_element_button.c
  6. 11 0
      applications/gui/modules/widget_elements/widget_element_i.h
  7. 67 0
      applications/gui/modules/widget_elements/widget_element_string_multi.c
  8. 3 1
      applications/subghz/scenes/subghz_scene_config.h
  9. 108 35
      applications/subghz/scenes/subghz_scene_receiver.c
  10. 156 0
      applications/subghz/scenes/subghz_scene_receiver_config.c
  11. 163 0
      applications/subghz/scenes/subghz_scene_receiver_info.c
  12. 4 2
      applications/subghz/scenes/subghz_scene_save_name.c
  13. 37 27
      applications/subghz/scenes/subghz_scene_set_type.c
  14. 7 6
      applications/subghz/scenes/subghz_scene_show_error.c
  15. 65 17
      applications/subghz/scenes/subghz_scene_transmitter.c
  16. 71 21
      applications/subghz/subghz.c
  17. 10 3
      applications/subghz/subghz_history.c
  18. 11 1
      applications/subghz/subghz_history.h
  19. 99 32
      applications/subghz/subghz_i.c
  20. 44 9
      applications/subghz/subghz_i.h
  21. 142 521
      applications/subghz/views/subghz_receiver.c
  22. 11 19
      applications/subghz/views/subghz_receiver.h
  23. 36 80
      applications/subghz/views/subghz_transmitter.c
  24. 5 7
      applications/subghz/views/subghz_transmitter.h
  25. 12 1
      lib/subghz/protocols/subghz_protocol.c
  26. 1 1
      lib/subghz/protocols/subghz_protocol_gate_tx.h
  27. 3 3
      lib/subghz/protocols/subghz_protocol_keeloq.c
  28. 1 1
      lib/subghz/protocols/subghz_protocol_keeloq.h
  29. 280 0
      lib/subghz/protocols/subghz_protocol_nero_radio.c
  30. 72 0
      lib/subghz/protocols/subghz_protocol_nero_radio.h
  31. 1 1
      lib/subghz/protocols/subghz_protocol_nero_sketch.h
  32. 1 1
      lib/subghz/protocols/subghz_protocol_nice_flo.h
  33. 1 1
      lib/subghz/protocols/subghz_protocol_princeton.c
  34. 3 3
      lib/subghz/protocols/subghz_protocol_star_line.c

+ 17 - 2
applications/gui/modules/variable-item-list.c

@@ -68,10 +68,10 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) {
                 canvas_draw_str(canvas, 73, item_text_y, "<");
             }
 
-            canvas_draw_str(canvas, 84, item_text_y, string_get_cstr(item->current_value_text));
+            canvas_draw_str(canvas, 80, item_text_y, string_get_cstr(item->current_value_text));
 
             if(item->current_value_index < (item->values_count - 1)) {
-                canvas_draw_str(canvas, 113, item_text_y, ">");
+                canvas_draw_str(canvas, 115, item_text_y, ">");
             }
         }
 
@@ -235,6 +235,21 @@ void variable_item_list_free(VariableItemList* variable_item_list) {
     free(variable_item_list);
 }
 
+void variable_item_list_clean(VariableItemList* variable_item_list) {
+    furi_assert(variable_item_list);
+
+    with_view_model(
+        variable_item_list->view, (VariableItemListModel * model) {
+            VariableItemArray_it_t it;
+            for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it);
+                VariableItemArray_next(it)) {
+                string_clean(VariableItemArray_ref(it)->current_value_text);
+            }
+            VariableItemArray_clean(model->items);
+            return false;
+        });
+}
+
 View* variable_item_list_get_view(VariableItemList* variable_item_list) {
     furi_assert(variable_item_list);
     return variable_item_list->view;

+ 3 - 0
applications/gui/modules/variable-item-list.h

@@ -18,6 +18,9 @@ VariableItemList* variable_item_list_alloc();
  * @param variable_item_list VariableItemList instance
  */
 void variable_item_list_free(VariableItemList* variable_item_list);
+
+void variable_item_list_clean(VariableItemList* variable_item_list);
+
 View* variable_item_list_get_view(VariableItemList* variable_item_list);
 
 /** Add item to VariableItemList

+ 14 - 0
applications/gui/modules/widget.c

@@ -118,6 +118,20 @@ static void widget_add_element(Widget* widget, WidgetElement* element) {
         });
 }
 
+void widget_add_string_multi_element(
+    Widget* widget,
+    uint8_t x,
+    uint8_t y,
+    Align horizontal,
+    Align vertical,
+    Font font,
+    const char* text) {
+    furi_assert(widget);
+    WidgetElement* string_multi_element =
+        widget_element_string_multi_create(x, y, horizontal, vertical, font, text);
+    widget_add_element(widget, string_multi_element);
+}
+
 void widget_add_string_element(
     Widget* widget,
     uint8_t x,

+ 17 - 0
applications/gui/modules/widget.h

@@ -26,6 +26,23 @@ void widget_clear(Widget* widget);
  */
 View* widget_get_view(Widget* widget);
 
+/** Add Multi String Element
+ * @param widget Widget instance
+ * @param x - x coordinate
+ * @param y - y coordinate
+ * @param horizontal - Align instance
+ * @param vertical - Align instance
+ * @param font Font instance
+ */
+void widget_add_string_multi_element(
+    Widget* widget,
+    uint8_t x,
+    uint8_t y,
+    Align horizontal,
+    Align vertical,
+    Font font,
+    const char* text);
+
 /** Add String Element
  * @param widget Widget instance
  * @param x - x coordinate

+ 12 - 1
applications/gui/modules/widget_elements/widget_element_button.c

@@ -30,7 +30,18 @@ static bool gui_button_input(InputEvent* event, WidgetElement* element) {
     GuiButtonModel* model = element->model;
     bool consumed = false;
 
-    if((event->type == InputTypeShort) && model->callback) {
+    if(model->callback == NULL) return consumed;
+
+    if(event->key == InputKeyOk && event->type == InputTypePress &&
+       model->button_type == GuiButtonTypeCenter) {
+        model->callback(GuiButtonTypeCenterPress, model->context);
+        consumed = true;
+    } else if(
+        event->key == InputKeyOk && event->type == InputTypeRelease &&
+        model->button_type == GuiButtonTypeCenter) {
+        model->callback(GuiButtonTypeCenterRelease, model->context);
+        consumed = true;
+    } else if(event->type == InputTypeShort) {
         if((model->button_type == GuiButtonTypeLeft) && (event->key == InputKeyLeft)) {
             model->callback(model->button_type, model->context);
             consumed = true;

+ 11 - 0
applications/gui/modules/widget_elements/widget_element_i.h

@@ -6,6 +6,8 @@ typedef enum {
     GuiButtonTypeLeft,
     GuiButtonTypeCenter,
     GuiButtonTypeRight,
+    GuiButtonTypeCenterPress,
+    GuiButtonTypeCenterRelease,
 } GuiButtonType;
 
 typedef void (*ButtonCallback)(GuiButtonType result, void* context);
@@ -28,6 +30,15 @@ struct WidgetElement {
     Widget* parent;
 };
 
+/* Create multi string element */
+WidgetElement* widget_element_string_multi_create(
+    uint8_t x,
+    uint8_t y,
+    Align horizontal,
+    Align vertical,
+    Font font,
+    const char* text);
+
 /* Create string element */
 WidgetElement* widget_element_string_create(
     uint8_t x,

+ 67 - 0
applications/gui/modules/widget_elements/widget_element_string_multi.c

@@ -0,0 +1,67 @@
+#include "widget_element_i.h"
+#include <m-string.h>
+#include <gui/elements.h>
+
+typedef struct {
+    uint8_t x;
+    uint8_t y;
+    Align horizontal;
+    Align vertical;
+    Font font;
+    string_t text;
+} GuiStringMultiModel;
+
+static void gui_string_multi_draw(Canvas* canvas, WidgetElement* element) {
+    furi_assert(canvas);
+    furi_assert(element);
+    GuiStringMultiModel* model = element->model;
+
+    if(string_size(model->text)) {
+        canvas_set_font(canvas, model->font);
+        elements_multiline_text_aligned(
+            canvas,
+            model->x,
+            model->y,
+            model->horizontal,
+            model->vertical,
+            string_get_cstr(model->text));
+    }
+}
+
+static void gui_string_multi_free(WidgetElement* gui_string) {
+    furi_assert(gui_string);
+
+    GuiStringMultiModel* model = gui_string->model;
+    string_clear(model->text);
+    free(gui_string->model);
+    free(gui_string);
+}
+
+WidgetElement* widget_element_string_multi_create(
+    uint8_t x,
+    uint8_t y,
+    Align horizontal,
+    Align vertical,
+    Font font,
+    const char* text) {
+    furi_assert(text);
+
+    // Allocate and init model
+    GuiStringMultiModel* model = furi_alloc(sizeof(GuiStringMultiModel));
+    model->x = x;
+    model->y = y;
+    model->horizontal = horizontal;
+    model->vertical = vertical;
+    model->font = font;
+    string_init_set_str(model->text, text);
+
+    // Allocate and init Element
+    WidgetElement* gui_string = furi_alloc(sizeof(WidgetElement));
+    gui_string->parent = NULL;
+    gui_string->input = NULL;
+    gui_string->draw = gui_string_multi_draw;
+    gui_string->free = gui_string_multi_free;
+    gui_string->model = model;
+
+    return gui_string;
+}

+ 3 - 1
applications/subghz/scenes/subghz_scene_config.h

@@ -1,10 +1,12 @@
 ADD_SCENE(subghz, start, Start)
 ADD_SCENE(subghz, receiver, Receiver)
+ADD_SCENE(subghz, receiver_config, ReceiverConfig)
+ADD_SCENE(subghz, receiver_info, ReceiverInfo)
 ADD_SCENE(subghz, save_name, SaveName)
 ADD_SCENE(subghz, save_success, SaveSuccess)
 ADD_SCENE(subghz, saved, Saved)
 ADD_SCENE(subghz, transmitter, Transmitter)
-ADD_SCENE(subghz, no_man, NoMan)
+ADD_SCENE(subghz, show_error, ShowError)
 ADD_SCENE(subghz, test, Test)
 ADD_SCENE(subghz, test_static, TestStatic)
 ADD_SCENE(subghz, test_carrier, TestCarrier)

+ 108 - 35
applications/subghz/scenes/subghz_scene_receiver.c

@@ -1,21 +1,102 @@
 #include "../subghz_i.h"
 #include "../views/subghz_receiver.h"
 
+static void subghz_scene_receiver_update_statusbar(void* context) {
+    SubGhz* subghz = context;
+    char frequency_str[20];
+    char preset_str[10];
+    string_t history_stat_str;
+    string_init(history_stat_str);
+    if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) {
+        snprintf(
+            frequency_str,
+            sizeof(frequency_str),
+            "%03ld.%02ld",
+            subghz->txrx->frequency / 1000000 % 1000,
+            subghz->txrx->frequency / 10000 % 100);
+        if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async ||
+           subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) {
+            snprintf(preset_str, sizeof(preset_str), "AM");
+        } else if(subghz->txrx->preset == FuriHalSubGhzPreset2FSKAsync) {
+            snprintf(preset_str, sizeof(preset_str), "FM");
+        } else {
+            furi_check(0);
+        }
+        subghz_receiver_add_data_statusbar(
+            subghz->subghz_receiver, frequency_str, preset_str, string_get_cstr(history_stat_str));
+    } else {
+        subghz_receiver_add_data_statusbar(
+            subghz->subghz_receiver, string_get_cstr(history_stat_str), "", "");
+        subghz->state_notifications = NOTIFICATION_IDLE_STATE;
+    }
+    string_clear(history_stat_str);
+}
+
 void subghz_scene_receiver_callback(SubghzReceverEvent event, void* context) {
     furi_assert(context);
     SubGhz* subghz = context;
     view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 }
 
+void subghz_scene_add_to_history_callback(SubGhzProtocolCommon* parser, void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+    string_t str_buff;
+    string_init(str_buff);
+
+    if(subghz_history_add_to_history(
+           subghz->txrx->history, parser, subghz->txrx->frequency, subghz->txrx->preset)) {
+        subghz_protocol_reset(subghz->txrx->protocol);
+        string_clean(str_buff);
+        subghz_history_get_text_item_menu(
+            subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1);
+        subghz_receiver_add_item_to_menu(
+            subghz->subghz_receiver,
+            string_get_cstr(str_buff),
+            subghz_history_get_type_protocol(
+                subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1));
+        subghz_scene_receiver_update_statusbar(subghz);
+    }
+    string_clear(str_buff);
+}
+
 const void subghz_scene_receiver_on_enter(void* context) {
     SubGhz* subghz = context;
-    SubghzReceiver* subghz_receiver = subghz->subghz_receiver;
 
-    subghz_receiver_set_callback(subghz_receiver, subghz_scene_receiver_callback, subghz);
+    string_t str_buff;
+    string_init(str_buff);
+
+    //Load history to receiver
+    subghz_receiver_exit(subghz->subghz_receiver);
+    for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) {
+        string_clean(str_buff);
+        subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i);
+        subghz_receiver_add_item_to_menu(
+            subghz->subghz_receiver,
+            string_get_cstr(str_buff),
+            subghz_history_get_type_protocol(subghz->txrx->history, i));
+    }
+    string_clear(str_buff);
+    subghz_scene_receiver_update_statusbar(subghz);
+    subghz_receiver_set_callback(subghz->subghz_receiver, subghz_scene_receiver_callback, subghz);
+    subghz_protocol_enable_dump(
+        subghz->txrx->protocol, subghz_scene_add_to_history_callback, subghz);
 
-    subghz_receiver_set_protocol(subghz_receiver, subghz->protocol_result, subghz->protocol);
-    subghz_receiver_set_worker(subghz_receiver, subghz->worker);
     subghz->state_notifications = NOTIFICATION_RX_STATE;
+    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
+        subghz_rx_end(subghz->txrx->worker);
+        //subghz_sleep();
+        subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+    };
+    if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
+        subghz_begin(subghz->txrx->preset);
+        subghz_rx(subghz->txrx->worker, subghz->txrx->frequency);
+        subghz->txrx->txrx_state = SubGhzTxRxStateRx;
+    }
+    if(subghz->txrx->idx_menu_chosen != 0) {
+        subghz_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen);
+    }
+
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewReceiver);
 }
 
@@ -24,51 +105,43 @@ const bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event
 
     if(event.type == SceneManagerEventTypeCustom) {
         switch(event.event) {
-        case SubghzReceverEventSave:
-            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
-            subghz->frequency = subghz_receiver_get_frequency(subghz->subghz_receiver);
-            subghz->preset = subghz_receiver_get_preset(subghz->subghz_receiver);
-            subghz->protocol_result = subghz_receiver_get_protocol(subghz->subghz_receiver);
-            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
-            return true;
-            break;
         case SubghzReceverEventBack:
-            scene_manager_previous_scene(subghz->scene_manager);
+            // Stop CC1101 Rx
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
+                subghz_rx_end(subghz->txrx->worker);
+                subghz_sleep();
+                subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+            };
+            subghz_history_clean(subghz->txrx->history);
+            subghz->txrx->hopper_state = SubGhzHopperStateOFF;
+            subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
+            subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
+            subghz->txrx->idx_menu_chosen = 0;
+            subghz_protocol_enable_dump(subghz->txrx->protocol, NULL, subghz);
+            scene_manager_search_and_switch_to_previous_scene(
+                subghz->scene_manager, SubGhzSceneStart);
             return true;
             break;
-        case SubghzReceverEventSendStart:
-            subghz->state_notifications = NOTIFICATION_TX_STATE;
-            subghz->frequency = subghz_receiver_get_frequency(subghz->subghz_receiver);
-            subghz->preset = subghz_receiver_get_preset(subghz->subghz_receiver);
-            subghz->protocol_result = subghz_receiver_get_protocol(subghz->subghz_receiver);
-            subghz_transmitter_tx_start(subghz);
-            return true;
-            break;
-        case SubghzReceverEventSendStop:
-            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
-            subghz_transmitter_tx_stop(subghz);
-            return true;
-            break;
-        case SubghzReceverEventMain:
-            subghz->state_notifications = NOTIFICATION_RX_STATE;
+        case SubghzReceverEventOK:
+            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
+            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
             return true;
             break;
         case SubghzReceverEventConfig:
             subghz->state_notifications = NOTIFICATION_IDLE_STATE;
-            return true;
-            break;
-        case SubghzReceverEventSendHistoryFull:
-            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
+            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
             return true;
             break;
         default:
             break;
         }
     } else if(event.type == SceneManagerEventTypeTick) {
+        if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
+            subghz_hopper_update(subghz->txrx);
+            subghz_scene_receiver_update_statusbar(subghz);
+        }
+
         switch(subghz->state_notifications) {
-        case NOTIFICATION_TX_STATE:
-            notification_message(subghz->notifications, &sequence_blink_red_10);
-            break;
         case NOTIFICATION_RX_STATE:
             notification_message(subghz->notifications, &sequence_blink_blue_10);
             break;

+ 156 - 0
applications/subghz/scenes/subghz_scene_receiver_config.c

@@ -0,0 +1,156 @@
+#include "../subghz_i.h"
+
+#define PRESET_COUNT 3
+const char* const preset_text[PRESET_COUNT] = {
+    "AM270",
+    "AM650",
+    "FM",
+};
+const uint32_t preset_value[PRESET_COUNT] = {
+    FuriHalSubGhzPresetOok270Async, /** OOK, bandwidth 270kHz, asynchronous */
+    FuriHalSubGhzPresetOok650Async, /** OOK, bandwidth 650kHz, asynchronous */
+    FuriHalSubGhzPreset2FSKAsync, /** FM, asynchronous */
+};
+
+#define HOPPING_COUNT 2
+const char* const hopping_text[HOPPING_COUNT] = {
+    "OFF",
+    "ON",
+};
+const uint32_t hopping_value[HOPPING_COUNT] = {
+    SubGhzHopperStateOFF,
+    SubGhzHopperStateRunnig,
+};
+
+uint8_t subghz_scene_receiver_config_uint32_value_index(
+    const uint32_t value,
+    const uint32_t values[],
+    uint8_t values_count) {
+    int64_t last_value = INT64_MIN;
+    uint8_t index = 0;
+    for(uint8_t i = 0; i < values_count; i++) {
+        if((value >= last_value) && (value <= values[i])) {
+            index = i;
+            break;
+        }
+        last_value = values[i];
+    }
+    return index;
+}
+
+uint8_t subghz_scene_receiver_config_hopper_value_index(
+    const uint32_t value,
+    const uint32_t values[],
+    uint8_t values_count,
+    void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+
+    if(value == values[0]) {
+        return 0;
+    } else {
+        variable_item_set_current_value_text(
+            (VariableItem*)scene_manager_get_scene_state(
+                subghz->scene_manager, SubGhzSceneReceiverConfig),
+            " -----");
+        return 1;
+    }
+}
+
+static void subghz_scene_receiver_config_set_frequency(VariableItem* item) {
+    SubGhz* subghz = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) {
+        variable_item_set_current_value_text(item, subghz_frequencies_text[index]);
+        subghz->txrx->frequency = subghz_frequencies[index];
+    }
+}
+
+static void subghz_scene_receiver_config_set_preset(VariableItem* item) {
+    SubGhz* subghz = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, preset_text[index]);
+    subghz->txrx->preset = preset_value[index];
+}
+
+static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item) {
+    SubGhz* subghz = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, hopping_text[index]);
+    if(hopping_value[index] == SubGhzHopperStateOFF) {
+        variable_item_set_current_value_text(
+            (VariableItem*)scene_manager_get_scene_state(
+                subghz->scene_manager, SubGhzSceneReceiverConfig),
+            subghz_frequencies_text[subghz_frequencies_433_92]);
+        subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
+    } else {
+        variable_item_set_current_value_text(
+            (VariableItem*)scene_manager_get_scene_state(
+                subghz->scene_manager, SubGhzSceneReceiverConfig),
+            " -----");
+    }
+
+    subghz->txrx->hopper_state = hopping_value[index];
+}
+
+void subghz_scene_receiver_config_callback(SubghzReceverEvent event, void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
+}
+
+const void subghz_scene_receiver_config_on_enter(void* context) {
+    SubGhz* subghz = context;
+    VariableItem* item;
+    uint8_t value_index;
+
+    item = variable_item_list_add(
+        subghz->variable_item_list,
+        "Frequency:",
+        subghz_frequencies_count,
+        subghz_scene_receiver_config_set_frequency,
+        subghz);
+    value_index = subghz_scene_receiver_config_uint32_value_index(
+        subghz->txrx->frequency, subghz_frequencies, subghz_frequencies_count);
+    scene_manager_set_scene_state(
+        subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]);
+
+    item = variable_item_list_add(
+        subghz->variable_item_list,
+        "Hopping:",
+        HOPPING_COUNT,
+        subghz_scene_receiver_config_set_hopping_runing,
+        subghz);
+    value_index = subghz_scene_receiver_config_hopper_value_index(
+        subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, hopping_text[value_index]);
+
+    item = variable_item_list_add(
+        subghz->variable_item_list,
+        "Modulation:",
+        PRESET_COUNT,
+        subghz_scene_receiver_config_set_preset,
+        subghz);
+    value_index = subghz_scene_receiver_config_uint32_value_index(
+        subghz->txrx->preset, preset_value, PRESET_COUNT);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, preset_text[value_index]);
+
+    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewVariableItemList);
+}
+
+const bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) {
+    //SubGhz* subghz = context;
+    return false;
+}
+
+const void subghz_scene_receiver_config_on_exit(void* context) {
+    SubGhz* subghz = context;
+    variable_item_list_clean(subghz->variable_item_list);
+}

+ 163 - 0
applications/subghz/scenes/subghz_scene_receiver_info.c

@@ -0,0 +1,163 @@
+#include "../subghz_i.h"
+
+void subghz_scene_receiver_info_callback(GuiButtonType result, void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+    view_dispatcher_send_custom_event(subghz->view_dispatcher, result);
+}
+
+static bool subghz_scene_receiver_info_update_parser(void* context) {
+    SubGhz* subghz = context;
+    subghz->txrx->protocol_result = subghz_protocol_get_by_name(
+        subghz->txrx->protocol,
+        subghz_history_get_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
+
+    if(subghz->txrx->protocol_result->to_load_protocol != NULL) {
+        subghz->txrx->protocol_result->to_load_protocol(
+            subghz->txrx->protocol_result,
+            subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
+        subghz->txrx->frequency =
+            subghz_history_get_frequency(subghz->txrx->history, subghz->txrx->idx_menu_chosen);
+        subghz->txrx->preset =
+            subghz_history_get_preset(subghz->txrx->history, subghz->txrx->idx_menu_chosen);
+        return true;
+    }
+    return false;
+}
+
+const void subghz_scene_receiver_info_on_enter(void* context) {
+    SubGhz* subghz = context;
+
+    if(subghz_scene_receiver_info_update_parser(subghz)) {
+        char buffer_str[16];
+        snprintf(
+            buffer_str,
+            sizeof(buffer_str),
+            "%03ld.%02ld",
+            subghz->txrx->frequency / 1000000 % 1000,
+            subghz->txrx->frequency / 10000 % 100);
+        widget_add_string_element(
+            subghz->widget, 78, 0, AlignLeft, AlignTop, FontSecondary, buffer_str);
+        if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async ||
+           subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) {
+            snprintf(buffer_str, sizeof(buffer_str), "AM");
+        } else if(subghz->txrx->preset == FuriHalSubGhzPreset2FSKAsync) {
+            snprintf(buffer_str, sizeof(buffer_str), "FM");
+        } else {
+            furi_check(0);
+        }
+        widget_add_string_element(
+            subghz->widget, 113, 0, AlignLeft, AlignTop, FontSecondary, buffer_str);
+        string_t text;
+        string_init(text);
+        subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, text);
+        widget_add_string_multi_element(
+            subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text));
+        string_clear(text);
+
+        if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_string &&
+           strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) {
+            widget_add_button_element(
+                subghz->widget,
+                GuiButtonTypeRight,
+                "Save",
+                subghz_scene_receiver_info_callback,
+                subghz);
+            widget_add_button_element(
+                subghz->widget,
+                GuiButtonTypeCenter,
+                "Send",
+                subghz_scene_receiver_info_callback,
+                subghz);
+        }
+
+    } else {
+        widget_add_icon_element(subghz->widget, 32, 12, &I_DolphinFirstStart7_61x51);
+        widget_add_string_element(
+            subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse.");
+    }
+
+    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget);
+}
+
+const bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) {
+    SubGhz* subghz = context;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeCenterPress) {
+            //CC1101 Stop RX -> Start TX
+            subghz->state_notifications = NOTIFICATION_TX_STATE;
+            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
+                subghz->txrx->hopper_state = SubGhzHopperStatePause;
+            }
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
+                subghz_rx_end(subghz->txrx->worker);
+                //subghz_sleep();
+                subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+            }
+            if(!subghz_scene_receiver_info_update_parser(subghz)) {
+                return false;
+            }
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
+                subghz_tx_start(subghz);
+                subghz->txrx->txrx_state = SubGhzTxRxStateTx;
+            }
+            return true;
+        } else if(event.event == GuiButtonTypeCenterRelease) {
+            //CC1101 Stop Tx -> Start RX
+            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+                subghz_tx_stop(subghz);
+                subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+            }
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
+                subghz_begin(subghz->txrx->preset);
+                subghz_rx(subghz->txrx->worker, subghz->txrx->frequency);
+                subghz->txrx->txrx_state = SubGhzTxRxStateRx;
+            }
+            if(subghz->txrx->hopper_state == SubGhzHopperStatePause) {
+                subghz->txrx->hopper_state = SubGhzHopperStateRunnig;
+            }
+            subghz->state_notifications = NOTIFICATION_RX_STATE;
+            return true;
+        } else if(event.event == GuiButtonTypeRight) {
+            //CC1101 Stop RX -> Save
+            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
+            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
+                subghz->txrx->hopper_state = SubGhzHopperStateOFF;
+            }
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
+                subghz_rx_end(subghz->txrx->worker);
+                subghz_sleep();
+                subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+            }
+            if(!subghz_scene_receiver_info_update_parser(subghz)) {
+                return false;
+            }
+            if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_string &&
+               strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) {
+                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
+            }
+            return true;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
+            subghz_hopper_update(subghz->txrx);
+        }
+        switch(subghz->state_notifications) {
+        case NOTIFICATION_TX_STATE:
+            notification_message(subghz->notifications, &sequence_blink_red_10);
+            break;
+        case NOTIFICATION_RX_STATE:
+            notification_message(subghz->notifications, &sequence_blink_blue_10);
+            break;
+        default:
+            break;
+        }
+    }
+    return false;
+}
+
+const void subghz_scene_receiver_info_on_exit(void* context) {
+    SubGhz* subghz = context;
+    widget_clear(subghz->widget);
+}

+ 4 - 2
applications/subghz/scenes/subghz_scene_save_name.c

@@ -35,11 +35,13 @@ const bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent even
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SCENE_SAVE_NAME_CUSTOM_EVENT) {
-            if(subghz_save_protocol_to_file(subghz, subghz->text_store)) {
+            if(strcmp(subghz->text_store, "") &&
+               subghz_save_protocol_to_file(subghz, subghz->text_store)) {
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
                 return true;
             } else {
-                //Error save
+                string_set(subghz->error_str, "No name file");
+                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
                 return true;
             }
         }

+ 37 - 27
applications/subghz/scenes/subghz_scene_set_type.c

@@ -8,15 +8,18 @@ enum SubmenuIndex {
     SubmenuIndexCAME12bit,
     SubmenuIndexCAME24bit,
     SubmenuIndexNeroSketch,
+    SubmenuIndexNeroRadio,
     SubmenuIndexGateTX,
     SubmenuIndexDoorHan,
 };
 
 bool subghz_scene_set_type_submenu_to_find_protocol(void* context, const char* protocol_name) {
     SubGhz* subghz = context;
-    subghz->protocol_result = subghz_protocol_get_by_name(subghz->protocol, protocol_name);
-    if(subghz->protocol_result == NULL) {
-        //show error
+    subghz->txrx->protocol_result =
+        subghz_protocol_get_by_name(subghz->txrx->protocol, protocol_name);
+    if(subghz->txrx->protocol_result == NULL) {
+        string_set(subghz->error_str, "Protocol not found");
+        scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
         return false;
     }
     return true;
@@ -62,6 +65,8 @@ const void subghz_scene_set_type_on_enter(void* context) {
         subghz);
     // submenu_add_item(
     //     subghz->submenu, "Nero Sketch", SubmenuIndexNeroSketch, subghz_scene_set_type_submenu_callback, subghz);
+    // submenu_add_item(
+    //     subghz->submenu, "Nero Radio", SubmenuIndexNeroRadio, subghz_scene_set_type_submenu_callback, subghz);
     submenu_add_item(
         subghz->submenu,
         "Gate TX_433",
@@ -90,81 +95,86 @@ const bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event
         switch(event.event) {
         case SubmenuIndexPricenton:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Princeton")) {
-                subghz->protocol_result->code_last_count_bit = 24;
+                subghz->txrx->protocol_result->code_last_count_bit = 24;
                 key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
-                subghz->protocol_result->code_last_found = key;
+                subghz->txrx->protocol_result->code_last_found = key;
                 generated_protocol = true;
             }
             break;
         case SubmenuIndexNiceFlo12bit:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Nice FLO")) {
-                subghz->protocol_result->code_last_count_bit = 12;
+                subghz->txrx->protocol_result->code_last_count_bit = 12;
                 key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
-                subghz->protocol_result->code_last_found = key;
+                subghz->txrx->protocol_result->code_last_found = key;
                 generated_protocol = true;
             }
             break;
         case SubmenuIndexNiceFlo24bit:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Nice FLO")) {
-                subghz->protocol_result->code_last_count_bit = 24;
+                subghz->txrx->protocol_result->code_last_count_bit = 24;
                 key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
-                subghz->protocol_result->code_last_found = key;
+                subghz->txrx->protocol_result->code_last_found = key;
                 generated_protocol = true;
             }
             break;
         case SubmenuIndexCAME12bit:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME")) {
-                subghz->protocol_result->code_last_count_bit = 12;
+                subghz->txrx->protocol_result->code_last_count_bit = 12;
                 key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
-                subghz->protocol_result->code_last_found = key;
+                subghz->txrx->protocol_result->code_last_found = key;
                 generated_protocol = true;
             }
             break;
         case SubmenuIndexCAME24bit:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME")) {
-                subghz->protocol_result->code_last_count_bit = 24;
+                subghz->txrx->protocol_result->code_last_count_bit = 24;
                 key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
-                subghz->protocol_result->code_last_found = key;
+                subghz->txrx->protocol_result->code_last_found = key;
                 generated_protocol = true;
             }
             break;
         // case SubmenuIndexNeroSketch:
         //     /* code */
         //     break;
+        // case SubmenuIndexNeroRadio:
+        //     /* code */
+        //     break;
         case SubmenuIndexGateTX:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "GateTX")) {
-                subghz->protocol_result->code_last_count_bit = 24;
+                subghz->txrx->protocol_result->code_last_count_bit = 24;
                 key = (key & 0x00F0FFFF) | 0xF << 16; //btn 0xF, 0xC, 0xA, 0x6
-                subghz->protocol_result->code_last_found = subghz_protocol_common_reverse_key(
-                    key, subghz->protocol_result->code_last_count_bit);
+                subghz->txrx->protocol_result->code_last_found =
+                    subghz_protocol_common_reverse_key(
+                        key, subghz->txrx->protocol_result->code_last_count_bit);
                 generated_protocol = true;
             }
             break;
         case SubmenuIndexDoorHan:
             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "KeeLoq")) {
-                subghz->protocol_result->code_last_count_bit = 64;
-                subghz->protocol_result->serial = key & 0x0FFFFFFF;
-                subghz->protocol_result->btn = 0x2; //btn 0x1, 0x2, 0x4, 0x8
-                subghz->protocol_result->cnt = 0x0003;
+                subghz->txrx->protocol_result->code_last_count_bit = 64;
+                subghz->txrx->protocol_result->serial = key & 0x0FFFFFFF;
+                subghz->txrx->protocol_result->btn = 0x2; //btn 0x1, 0x2, 0x4, 0x8
+                subghz->txrx->protocol_result->cnt = 0x0003;
                 if(subghz_protocol_keeloq_set_manufacture_name(
-                       subghz->protocol_result, "DoorHan")) {
-                    subghz->protocol_result->code_last_found =
-                        subghz_protocol_keeloq_gen_key(subghz->protocol_result);
+                       subghz->txrx->protocol_result, "DoorHan")) {
+                    subghz->txrx->protocol_result->code_last_found =
+                        subghz_protocol_keeloq_gen_key(subghz->txrx->protocol_result);
                     generated_protocol = true;
                 } else {
                     generated_protocol = false;
-                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNoMan);
+                    string_set(subghz->error_str, "No manufactory key");
+                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
                 }
             }
             break;
-
         default:
             return false;
             break;
         }
+
         if(generated_protocol) {
-            subghz->frequency = subghz_frequencies[subghz_frequencies_433_92];
-            subghz->preset = FuriHalSubGhzPresetOok650Async;
+            subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
+            subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
             return true;
         }

+ 7 - 6
applications/subghz/scenes/subghz_scene_no_man.c → applications/subghz/scenes/subghz_scene_show_error.c

@@ -2,26 +2,26 @@
 
 #define SCENE_NO_MAN_CUSTOM_EVENT (11UL)
 
-void subghz_scene_no_man_popup_callback(void* context) {
+void subghz_scene_show_error_popup_callback(void* context) {
     SubGhz* subghz = context;
     view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_NO_MAN_CUSTOM_EVENT);
 }
 
-const void subghz_scene_no_man_on_enter(void* context) {
+const void subghz_scene_show_error_on_enter(void* context) {
     SubGhz* subghz = context;
 
     // Setup view
     Popup* popup = subghz->popup;
     popup_set_icon(popup, 32, 12, &I_DolphinFirstStart7_61x51);
-    popup_set_header(popup, "No manufactory key", 13, 8, AlignLeft, AlignBottom);
+    popup_set_header(popup, string_get_cstr(subghz->error_str), 64, 8, AlignCenter, AlignBottom);
     popup_set_timeout(popup, 1500);
     popup_set_context(popup, subghz);
-    popup_set_callback(popup, subghz_scene_no_man_popup_callback);
+    popup_set_callback(popup, subghz_scene_show_error_popup_callback);
     popup_enable_timeout(popup);
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewPopup);
 }
 
-const bool subghz_scene_no_man_on_event(void* context, SceneManagerEvent event) {
+const bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) {
     SubGhz* subghz = context;
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) {
@@ -33,7 +33,7 @@ const bool subghz_scene_no_man_on_event(void* context, SceneManagerEvent event)
     return false;
 }
 
-const void subghz_scene_no_man_on_exit(void* context) {
+const void subghz_scene_show_error_on_exit(void* context) {
     SubGhz* subghz = context;
 
     // Clear view
@@ -45,4 +45,5 @@ const void subghz_scene_no_man_on_exit(void* context) {
     popup_set_context(popup, NULL);
     popup_set_timeout(popup, 0);
     popup_disable_timeout(popup);
+    string_clean(subghz->error_str);
 }

+ 65 - 17
applications/subghz/scenes/subghz_scene_transmitter.c

@@ -1,5 +1,6 @@
 #include "../subghz_i.h"
 #include "../views/subghz_transmitter.h"
+#include <lib/subghz/protocols/subghz_protocol_keeloq.h>
 
 void subghz_scene_transmitter_callback(SubghzTransmitterEvent event, void* context) {
     furi_assert(context);
@@ -7,42 +8,90 @@ void subghz_scene_transmitter_callback(SubghzTransmitterEvent event, void* conte
     view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 }
 
-const void subghz_scene_transmitter_on_enter(void* context) {
+static void subghz_scene_transmitter_update_data_show(void* context) {
     SubGhz* subghz = context;
-    SubghzTransmitter* subghz_transmitter = subghz->subghz_transmitter;
 
-    subghz_transmitter_set_callback(subghz_transmitter, subghz_scene_transmitter_callback, subghz);
-    subghz_transmitter_set_protocol(subghz_transmitter, subghz->protocol_result);
-    subghz_transmitter_set_frequency_preset(subghz_transmitter, subghz->frequency, subghz->preset);
+    if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->get_upload_protocol) {
+        string_t key_str;
+        string_init(key_str);
+        char frequency_str[10];
+        char preset_str[6];
+        uint8_t show_button = 0;
+        subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, key_str);
 
-    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter);
+        if((!strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) &&
+           (!strcmp(
+               subghz_protocol_keeloq_get_manufacture_name(subghz->txrx->protocol_result),
+               "Unknown"))) {
+            show_button = 0;
+        } else {
+            show_button = 1;
+        }
+        snprintf(
+            frequency_str,
+            sizeof(frequency_str),
+            "%03ld.%02ld",
+            subghz->txrx->frequency / 1000000 % 1000,
+            subghz->txrx->frequency / 10000 % 100);
+        if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async ||
+           subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) {
+            snprintf(preset_str, sizeof(preset_str), "AM");
+        } else if(subghz->txrx->preset == FuriHalSubGhzPreset2FSKAsync) {
+            snprintf(preset_str, sizeof(preset_str), "FM");
+        } else {
+            furi_check(0);
+        }
 
+        subghz_transmitter_add_data_to_show(
+            subghz->subghz_transmitter,
+            string_get_cstr(key_str),
+            frequency_str,
+            preset_str,
+            show_button);
+        string_clear(key_str);
+    } else {
+        string_set(subghz->error_str, "Protocol not found");
+        scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
+    }
+}
+
+const void subghz_scene_transmitter_on_enter(void* context) {
+    SubGhz* subghz = context;
+    subghz_transmitter_set_callback(
+        subghz->subghz_transmitter, subghz_scene_transmitter_callback, subghz);
+    subghz_scene_transmitter_update_data_show(subghz);
     subghz->state_notifications = NOTIFICATION_IDLE_STATE;
+    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter);
 }
 
 const bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
     SubGhz* subghz = context;
-
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubghzTransmitterEventSendStart) {
             subghz->state_notifications = NOTIFICATION_TX_STATE;
-            subghz_transmitter_tx_start(subghz);
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
+                subghz_rx_end(subghz->txrx->worker);
+                subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+            }
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
+                subghz_tx_start(subghz);
+                subghz_scene_transmitter_update_data_show(subghz);
+                subghz->txrx->txrx_state = SubGhzTxRxStateTx;
+            }
             return true;
         } else if(event.event == SubghzTransmitterEventSendStop) {
             subghz->state_notifications = NOTIFICATION_IDLE_STATE;
-            subghz_transmitter_tx_stop(subghz);
-            subghz_sleep();
+            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+                subghz_tx_stop(subghz);
+                subghz_sleep();
+                subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+            }
             return true;
         } else if(event.event == SubghzTransmitterEventBack) {
             subghz->state_notifications = NOTIFICATION_IDLE_STATE;
             scene_manager_search_and_switch_to_previous_scene(
                 subghz->scene_manager, SubGhzSceneStart);
             return true;
-        } else if(event.event == SubghzTransmitterEventNoMan) {
-            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
-            scene_manager_search_and_switch_to_previous_scene(
-                subghz->scene_manager, SubGhzSceneNoMan);
-            return true;
         }
     } else if(event.type == SceneManagerEventTypeTick) {
         if(subghz->state_notifications == NOTIFICATION_TX_STATE) {
@@ -55,7 +104,6 @@ const bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent ev
 
 const void subghz_scene_transmitter_on_exit(void* context) {
     SubGhz* subghz = context;
-    SubghzTransmitter* subghz_transmitter = subghz->subghz_transmitter;
-    subghz_transmitter_set_callback(subghz_transmitter, NULL, subghz);
+
     subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 }

+ 71 - 21
applications/subghz/subghz.c

@@ -1,5 +1,22 @@
 #include "subghz_i.h"
 
+const char* const subghz_frequencies_text[] = {
+    "300.00",
+    "315.00",
+    "348.00",
+    "387.00",
+    "433.08",
+    "433.92",
+    "434.78",
+    "438.90",
+    "464.00",
+    "779.00",
+    "868.35",
+    "915.00",
+    "925.00",
+    "928.00",
+};
+
 const uint32_t subghz_frequencies[] = {
     /* 300 - 348 */
     300000000,
@@ -20,7 +37,15 @@ const uint32_t subghz_frequencies[] = {
     928000000,
 };
 
+const uint32_t subghz_hopper_frequencies[] = {
+    315000000,
+    433920000,
+    868350000,
+};
+
 const uint32_t subghz_frequencies_count = sizeof(subghz_frequencies) / sizeof(uint32_t);
+const uint32_t subghz_hopper_frequencies_count =
+    sizeof(subghz_hopper_frequencies) / sizeof(uint32_t);
 const uint32_t subghz_frequencies_433_92 = 5;
 
 bool subghz_custom_event_callback(void* context, uint32_t event) {
@@ -77,11 +102,6 @@ SubGhz* subghz_alloc() {
         SubGhzViewReceiver,
         subghz_receiver_get_view(subghz->subghz_receiver));
 
-    // Dialog
-    subghz->dialog_ex = dialog_ex_alloc();
-    view_dispatcher_add_view(
-        subghz->view_dispatcher, SubGhzViewDialogEx, dialog_ex_get_view(subghz->dialog_ex));
-
     // Popup
     subghz->popup = popup_alloc();
     view_dispatcher_add_view(
@@ -92,6 +112,11 @@ SubGhz* subghz_alloc() {
     view_dispatcher_add_view(
         subghz->view_dispatcher, SubGhzViewTextInput, text_input_get_view(subghz->text_input));
 
+    // Custom Widget
+    subghz->widget = widget_alloc();
+    view_dispatcher_add_view(
+        subghz->view_dispatcher, SubGhzViewWidget, widget_get_view(subghz->widget));
+
     // Transmitter
     subghz->subghz_transmitter = subghz_transmitter_alloc();
     view_dispatcher_add_view(
@@ -99,6 +124,13 @@ SubGhz* subghz_alloc() {
         SubGhzViewTransmitter,
         subghz_transmitter_get_view(subghz->subghz_transmitter));
 
+    // Variable Item List
+    subghz->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        subghz->view_dispatcher,
+        SubGhzViewVariableItemList,
+        variable_item_list_get_view(subghz->variable_item_list));
+
     // Carrier Test Module
     subghz->subghz_test_carrier = subghz_test_carrier_alloc();
     view_dispatcher_add_view(
@@ -120,17 +152,26 @@ SubGhz* subghz_alloc() {
         SubGhzViewStatic,
         subghz_test_static_get_view(subghz->subghz_test_static));
 
-    //init Worker & Protocol
-    subghz->worker = subghz_worker_alloc();
-    subghz->protocol = subghz_protocol_alloc();
+    //init Worker & Protocol & History
+    subghz->txrx = furi_alloc(sizeof(SubGhzTxRx));
+    subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
+    subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
+    subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
+    subghz->txrx->hopper_state = SubGhzHopperStateOFF;
+    subghz->txrx->history = subghz_history_alloc();
+    subghz->txrx->worker = subghz_worker_alloc();
+    subghz->txrx->protocol = subghz_protocol_alloc();
     subghz_worker_set_overrun_callback(
-        subghz->worker, (SubGhzWorkerOverrunCallback)subghz_protocol_reset);
+        subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_protocol_reset);
     subghz_worker_set_pair_callback(
-        subghz->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse);
-    subghz_worker_set_context(subghz->worker, subghz->protocol);
+        subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse);
+    subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->protocol);
 
-    subghz_protocol_load_keeloq_file(subghz->protocol, "/ext/subghz/keeloq_mfcodes");
-    subghz_protocol_load_nice_flor_s_file(subghz->protocol, "/ext/subghz/nice_floor_s_rx");
+    //Init Error_str
+    string_init(subghz->error_str);
+
+    subghz_protocol_load_keeloq_file(subghz->txrx->protocol, "/ext/subghz/keeloq_mfcodes");
+    subghz_protocol_load_nice_flor_s_file(subghz->txrx->protocol, "/ext/subghz/nice_floor_s_rx");
 
     //subghz_protocol_enable_dump_text(subghz->protocol, subghz_text_callback, subghz);
 
@@ -160,18 +201,22 @@ void subghz_free(SubGhz* subghz) {
     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTextInput);
     text_input_free(subghz->text_input);
 
-    // Receiver
+    // Custom Widget
+    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewWidget);
+    widget_free(subghz->widget);
+
+    // Transmitter
     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTransmitter);
     subghz_transmitter_free(subghz->subghz_transmitter);
 
+    // Variable Item List
+    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewVariableItemList);
+    variable_item_list_free(subghz->variable_item_list);
+
     // Submenu
     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu);
     submenu_free(subghz->submenu);
 
-    // DialogEx
-    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewDialogEx);
-    dialog_ex_free(subghz->dialog_ex);
-
     // Popup
     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewPopup);
     popup_free(subghz->popup);
@@ -186,9 +231,14 @@ void subghz_free(SubGhz* subghz) {
     furi_record_close("gui");
     subghz->gui = NULL;
 
-    //Worker & Protocol
-    subghz_protocol_free(subghz->protocol);
-    subghz_worker_free(subghz->worker);
+    //Worker & Protocol & History
+    subghz_protocol_free(subghz->txrx->protocol);
+    subghz_worker_free(subghz->txrx->worker);
+    subghz_history_free(subghz->txrx->history);
+    free(subghz->txrx);
+
+    //Error string
+    string_clear(subghz->error_str);
 
     // Notifications
     furi_record_close("notification");

+ 10 - 3
applications/subghz/subghz_history.c

@@ -128,21 +128,27 @@ void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output,
     }
 }
 
-void subghz_history_add_to_history(SubGhzHistory* instance, void* context) {
+bool subghz_history_add_to_history(
+    SubGhzHistory* instance,
+    void* context,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset) {
     furi_assert(instance);
     furi_assert(context);
     SubGhzProtocolCommon* protocol = context;
 
-    if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return;
+    if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false;
     if((instance->code_last_found == (protocol->code_last_found & 0xFFFF0FFFFFFFFFFF)) &&
        ((millis() - instance->last_update_timestamp) < 500)) {
         instance->last_update_timestamp = millis();
-        return;
+        return false;
     }
 
     instance->code_last_found = protocol->code_last_found & 0xFFFF0FFFFFFFFFFF;
     instance->last_update_timestamp = millis();
 
+    instance->history[instance->last_index_write].real_frequency = frequency;
+    instance->history[instance->last_index_write].preset = preset;
     instance->history[instance->last_index_write].te = 0;
     instance->history[instance->last_index_write].manufacture_name = NULL;
     instance->history[instance->last_index_write].name = protocol->name;
@@ -161,4 +167,5 @@ void subghz_history_add_to_history(SubGhzHistory* instance, void* context) {
     instance->history[instance->last_index_write].type_protocol = protocol->type_protocol;
 
     instance->last_index_write++;
+    return true;
 }

+ 11 - 1
applications/subghz/subghz_history.h

@@ -1,3 +1,4 @@
+
 #pragma once
 
 #include <lib/subghz/protocols/subghz_protocol_common.h>
@@ -94,8 +95,15 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output
  * 
  * @param instance  - SubGhzHistory instance
  * @param context    - SubGhzProtocolCommon context
+ * @param frequency - frequency Hz
+ * @param preset    - FuriHalSubGhzPreset preset
+ * @return bool;
  */
-void subghz_history_add_to_history(SubGhzHistory* instance, void* context);
+bool subghz_history_add_to_history(
+    SubGhzHistory* instance,
+    void* context,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset);
 
 /** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data
  * 
@@ -104,3 +112,5 @@ void subghz_history_add_to_history(SubGhzHistory* instance, void* context);
  * @return SubGhzProtocolCommonLoad*
  */
 SubGhzProtocolCommonLoad* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx);
+
+void subghz_hopper_update(void* context);

+ 99 - 32
applications/subghz/subghz_i.c

@@ -19,6 +19,9 @@ void subghz_begin(FuriHalSubGhzPreset preset) {
 
 uint32_t subghz_rx(void* context, uint32_t frequency) {
     furi_assert(context);
+    if(!furi_hal_subghz_is_frequency_valid(frequency)) {
+        furi_check(0);
+    }
     SubGhzWorker* worker = context;
 
     furi_hal_subghz_idle();
@@ -33,6 +36,9 @@ uint32_t subghz_rx(void* context, uint32_t frequency) {
 }
 
 uint32_t subghz_tx(uint32_t frequency) {
+    if(!furi_hal_subghz_is_frequency_valid(frequency)) {
+        furi_check(0);
+    }
     furi_hal_subghz_idle();
     uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency);
     hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
@@ -53,6 +59,7 @@ void subghz_rx_end(void* context) {
         subghz_worker_stop(worker);
         furi_hal_subghz_stop_async_rx();
     }
+    furi_hal_subghz_idle();
 }
 
 void subghz_sleep(void) {
@@ -66,41 +73,46 @@ void subghz_frequency_preset_to_str(void* context, string_t output) {
         output,
         "Frequency: %d\n"
         "Preset: %d\n",
-        (int)subghz->frequency,
-        (int)subghz->preset);
+        (int)subghz->txrx->frequency,
+        (int)subghz->txrx->preset);
 }
 
-void subghz_transmitter_tx_start(void* context) {
+void subghz_tx_start(void* context) {
+    furi_assert(context);
     SubGhz* subghz = context;
-    subghz->encoder = subghz_protocol_encoder_common_alloc();
-    subghz->encoder->repeat = 200; //max repeat with the button held down
+    subghz->txrx->encoder = subghz_protocol_encoder_common_alloc();
+    subghz->txrx->encoder->repeat = 200; //max repeat with the button held down
     //get upload
-    if(subghz->protocol_result->get_upload_protocol) {
-        if(subghz->protocol_result->get_upload_protocol(subghz->protocol_result, subghz->encoder)) {
-            if(subghz->preset) {
-                subghz_begin(subghz->preset);
+    if(subghz->txrx->protocol_result->get_upload_protocol) {
+        if(subghz->txrx->protocol_result->get_upload_protocol(
+               subghz->txrx->protocol_result, subghz->txrx->encoder)) {
+            if(subghz->txrx->preset) {
+                subghz_begin(subghz->txrx->preset);
             } else {
-                subghz_begin(FuriHalSubGhzPresetOok650Async);
+                subghz_begin(FuriHalSubGhzPresetOok270Async);
             }
-            if(subghz->frequency) {
-                subghz_tx(subghz->frequency);
+            if(subghz->txrx->frequency) {
+                subghz_tx(subghz->txrx->frequency);
             } else {
                 subghz_tx(433920000);
             }
 
             //Start TX
-            furi_hal_subghz_start_async_tx(subghz_protocol_encoder_common_yield, subghz->encoder);
+            furi_hal_subghz_start_async_tx(
+                subghz_protocol_encoder_common_yield, subghz->txrx->encoder);
         }
     }
 }
 
-void subghz_transmitter_tx_stop(void* context) {
+void subghz_tx_stop(void* context) {
+    furi_assert(context);
     SubGhz* subghz = context;
     //Stop TX
     furi_hal_subghz_stop_async_tx();
-    subghz_protocol_encoder_common_free(subghz->encoder);
+    subghz_protocol_encoder_common_free(subghz->txrx->encoder);
+    furi_hal_subghz_idle();
     //if protocol dynamic then we save the last upload
-    if(subghz->protocol_result->type_protocol == TYPE_PROTOCOL_DYNAMIC) {
+    if(subghz->txrx->protocol_result->type_protocol == TYPE_PROTOCOL_DYNAMIC) {
         subghz_save_protocol_to_file(subghz, subghz->text_store);
     }
     notification_message(subghz->notifications, &sequence_reset_red);
@@ -133,7 +145,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
         if(res != 1) {
             break;
         }
-        subghz->frequency = (uint32_t)data;
+        subghz->txrx->frequency = (uint32_t)data;
 
         // Read and parse preset from 2st line
         if(!file_worker_read_until(file_worker, temp_str, '\n')) {
@@ -143,7 +155,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
         if(res != 1) {
             break;
         }
-        subghz->preset = (FuriHalSubGhzPreset)data;
+        subghz->txrx->preset = (FuriHalSubGhzPreset)data;
 
         // Read and parse name protocol from 2st line
         if(!file_worker_read_until(file_worker, temp_str, '\n')) {
@@ -151,13 +163,13 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
         }
         // strlen("Protocol: ") = 10
         string_right(temp_str, 10);
-        subghz->protocol_result =
-            subghz_protocol_get_by_name(subghz->protocol, string_get_cstr(temp_str));
-        if(subghz->protocol_result == NULL) {
+        subghz->txrx->protocol_result =
+            subghz_protocol_get_by_name(subghz->txrx->protocol, string_get_cstr(temp_str));
+        if(subghz->txrx->protocol_result == NULL) {
             break;
         }
-        if(!subghz->protocol_result->to_load_protocol_from_file(
-               file_worker, subghz->protocol_result)) {
+        if(!subghz->txrx->protocol_result->to_load_protocol_from_file(
+               file_worker, subghz->txrx->protocol_result)) {
             break;
         }
         loaded = true;
@@ -175,8 +187,9 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
 }
 
 bool subghz_save_protocol_to_file(void* context, const char* dev_name) {
+    furi_assert(context);
     SubGhz* subghz = context;
-    furi_assert(subghz->protocol_result);
+    furi_assert(subghz->txrx->protocol_result);
     FileWorker* file_worker = file_worker_alloc(false);
     string_t dev_file_name;
     string_init(dev_file_name);
@@ -210,7 +223,7 @@ bool subghz_save_protocol_to_file(void* context, const char* dev_name) {
             break;
         }
         //Get string save
-        subghz->protocol_result->to_save_string(subghz->protocol_result, temp_str);
+        subghz->txrx->protocol_result->to_save_string(subghz->txrx->protocol_result, temp_str);
         // Prepare and write data to file
         if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_size(temp_str))) {
             break;
@@ -276,7 +289,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
         if(sscanf_res != 1) {
             break;
         }
-        subghz->frequency = (uint32_t)data;
+        subghz->txrx->frequency = (uint32_t)data;
 
         // Read and parse preset from 2st line
         if(!file_worker_read_until(file_worker, temp_str, '\n')) {
@@ -286,7 +299,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
         if(sscanf_res != 1) {
             break;
         }
-        subghz->preset = (FuriHalSubGhzPreset)data;
+        subghz->txrx->preset = (FuriHalSubGhzPreset)data;
 
         // Read and parse name protocol from 3st line
         if(!file_worker_read_until(file_worker, temp_str, '\n')) {
@@ -294,13 +307,13 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
         }
         // strlen("Protocol: ") = 10
         string_right(temp_str, 10);
-        subghz->protocol_result =
-            subghz_protocol_get_by_name(subghz->protocol, string_get_cstr(temp_str));
-        if(subghz->protocol_result == NULL) {
+        subghz->txrx->protocol_result =
+            subghz_protocol_get_by_name(subghz->txrx->protocol, string_get_cstr(temp_str));
+        if(subghz->txrx->protocol_result == NULL) {
             break;
         }
-        if(!subghz->protocol_result->to_load_protocol_from_file(
-               file_worker, subghz->protocol_result)) {
+        if(!subghz->txrx->protocol_result->to_load_protocol_from_file(
+               file_worker, subghz->txrx->protocol_result)) {
             break;
         }
         res = true;
@@ -328,3 +341,57 @@ uint32_t subghz_random_serial(void) {
     }
     return (uint32_t)rand();
 }
+
+void subghz_hopper_update(void* context) {
+    furi_assert(context);
+    SubGhzTxRx* txrx = context;
+
+    switch(txrx->hopper_state) {
+    case SubGhzHopperStateOFF:
+        return;
+        break;
+    case SubGhzHopperStatePause:
+        return;
+        break;
+    case SubGhzHopperStateRSSITimeOut:
+        if(txrx->hopper_timeout != 0) {
+            txrx->hopper_timeout--;
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+    float rssi = -127.0f;
+    if(txrx->hopper_state != SubGhzHopperStateRSSITimeOut) {
+        // See RSSI Calculation timings in CC1101 17.3 RSSI
+        rssi = furi_hal_subghz_get_rssi();
+
+        // Stay if RSSI is high enough
+        if(rssi > -90.0f) {
+            txrx->hopper_timeout = 10;
+            txrx->hopper_state = SubGhzHopperStateRSSITimeOut;
+            return;
+        }
+    } else {
+        txrx->hopper_state = SubGhzHopperStateRunnig;
+    }
+
+    // Select next frequency
+    if(txrx->hopper_idx_frequency < subghz_hopper_frequencies_count - 1) {
+        txrx->hopper_idx_frequency++;
+    } else {
+        txrx->hopper_idx_frequency = 0;
+    }
+
+    if(txrx->txrx_state == SubGhzTxRxStateRx) {
+        subghz_rx_end(txrx->worker);
+        txrx->txrx_state = SubGhzTxRxStateIdle;
+    };
+    if(txrx->txrx_state == SubGhzTxRxStateIdle) {
+        subghz_protocol_reset(txrx->protocol);
+        txrx->frequency = subghz_hopper_frequencies[txrx->hopper_idx_frequency];
+        subghz_rx(txrx->worker, txrx->frequency);
+        txrx->txrx_state = SubGhzTxRxStateRx;
+    }
+}

+ 44 - 9
applications/subghz/subghz_i.h

@@ -15,9 +15,9 @@
 #include <notification/notification-messages.h>
 #include <gui/view_dispatcher.h>
 #include <gui/modules/submenu.h>
-#include <gui/modules/dialog_ex.h>
 #include <gui/modules/popup.h>
 #include <gui/modules/text_input.h>
+#include <gui/modules/widget.h>
 
 #include <subghz/scenes/subghz_scene.h>
 
@@ -26,6 +26,8 @@
 #include <lib/subghz/protocols/subghz_protocol_common.h>
 #include "subghz_history.h"
 
+#include <gui/modules/variable-item-list.h>
+
 #define SUBGHZ_TEXT_STORE_SIZE 128
 
 #define NOTIFICATION_STARTING_STATE 0u
@@ -33,48 +35,81 @@
 #define NOTIFICATION_TX_STATE 2u
 #define NOTIFICATION_RX_STATE 3u
 
+extern const char* const subghz_frequencies_text[];
 extern const uint32_t subghz_frequencies[];
+extern const uint32_t subghz_hopper_frequencies[];
 extern const uint32_t subghz_frequencies_count;
+extern const uint32_t subghz_hopper_frequencies_count;
 extern const uint32_t subghz_frequencies_433_92;
 
-struct SubGhz {
-    Gui* gui;
-    NotificationApp* notifications;
+/** SubGhzTxRx state */
+typedef enum {
+    SubGhzTxRxStateIdle,
+    SubGhzTxRxStateRx,
+    SubGhzTxRxStateTx,
+} SubGhzTxRxState;
+
+/** SubGhzHopperState state */
+typedef enum {
+    SubGhzHopperStateOFF,
+    SubGhzHopperStateRunnig,
+    SubGhzHopperStatePause,
+    SubGhzHopperStateRSSITimeOut,
+} SubGhzHopperState;
 
+struct SubGhzTxRx {
     SubGhzWorker* worker;
     SubGhzProtocol* protocol;
     SubGhzProtocolCommon* protocol_result;
     SubGhzProtocolCommonEncoder* encoder;
     uint32_t frequency;
     FuriHalSubGhzPreset preset;
+    SubGhzHistory* history;
+    uint16_t idx_menu_chosen;
+    SubGhzTxRxState txrx_state;
+    //bool hopper_runing;
+    SubGhzHopperState hopper_state;
+    uint8_t hopper_timeout;
+    uint8_t hopper_idx_frequency;
+};
+
+typedef struct SubGhzTxRx SubGhzTxRx;
+
+struct SubGhz {
+    Gui* gui;
+    NotificationApp* notifications;
+
+    SubGhzTxRx* txrx;
 
     SceneManager* scene_manager;
     ViewDispatcher* view_dispatcher;
 
     Submenu* submenu;
-    DialogEx* dialog_ex;
     Popup* popup;
     TextInput* text_input;
+    Widget* widget;
     char text_store[SUBGHZ_TEXT_STORE_SIZE + 1];
     uint8_t state_notifications;
 
     SubghzReceiver* subghz_receiver;
     SubghzTransmitter* subghz_transmitter;
+    VariableItemList* variable_item_list;
 
     SubghzTestStatic* subghz_test_static;
     SubghzTestCarrier* subghz_test_carrier;
     SubghzTestPacket* subghz_test_packet;
+    string_t error_str;
 };
 
 typedef enum {
     SubGhzViewMenu,
 
-    SubGhzViewDialogEx,
     SubGhzViewReceiver,
     SubGhzViewPopup,
     SubGhzViewTextInput,
+    SubGhzViewWidget,
     SubGhzViewTransmitter,
-
+    SubGhzViewVariableItemList,
     SubGhzViewStatic,
     SubGhzViewTestCarrier,
     SubGhzViewTestPacket,
@@ -86,8 +121,8 @@ uint32_t subghz_tx(uint32_t frequency);
 void subghz_idle(void);
 void subghz_rx_end(void* context);
 void subghz_sleep(void);
-void subghz_transmitter_tx_start(void* context);
-void subghz_transmitter_tx_stop(void* context);
+void subghz_tx_start(void* context);
+void subghz_tx_stop(void* context);
 bool subghz_key_load(SubGhz* subghz, const char* file_path);
 bool subghz_save_protocol_to_file(void* context, const char* dev_name);
 bool subghz_load_protocol_from_file(SubGhz* subghz);

+ 142 - 521
applications/subghz/views/subghz_receiver.c

@@ -1,42 +1,32 @@
 #include "subghz_receiver.h"
 #include "../subghz_i.h"
 #include <math.h>
-#include <furi.h>
-#include <furi-hal.h>
+
 #include <input/input.h>
 #include <gui/elements.h>
-#include <notification/notification-messages.h>
-#include <lib/subghz/protocols/subghz_protocol_princeton.h>
-
 #include <assets_icons.h>
+#include <m-string.h>
+#include <m-array.h>
 
 #define FRAME_HEIGHT 12
 #define MAX_LEN_PX 100
 #define MENU_ITEMS 4
 
-#define COUNT_FREQUNCY_HOPPER 3
-const uint32_t subghz_frequencies_hopper[] = {
-    /* 300 - 348 */
-    315000000,
-    /* 387 - 464 */
-    433920000, /* LPD433 mid */
-    /* 779 - 928 */
-    868350000,
-};
+typedef struct {
+    string_t item_str;
+    uint8_t type;
+} SubGhzReceiverMenuItem;
+
+ARRAY_DEF(SubGhzReceiverMenuItemArray, SubGhzReceiverMenuItem, M_POD_OPLIST)
 
-typedef enum {
-    ReceiverSceneStart,
-    ReceiverSceneMain,
-    ReceiverSceneConfig,
-    ReceiverSceneInfo,
-} SubghzReceiverScene;
+#define M_OPL_SubGhzReceiverMenuItemArray_t() \
+    ARRAY_OPLIST(SubGhzReceiverMenuItemArray, M_POD_OPLIST)
 
-typedef enum {
-    SubGhzHopperStateOFF,
-    SubGhzHopperStatePause,
-    SubGhzHopperStateRunnig,
-    SubGhzHopperStateRSSITimeOut,
-} SubGhzHopperState;
+struct SubGhzReceiverHistory {
+    SubGhzReceiverMenuItemArray_t data;
+};
+
+typedef struct SubGhzReceiverHistory SubGhzReceiverHistory;
 
 static const Icon* ReceiverItemIcons[] = {
     [TYPE_PROTOCOL_UNKNOWN] = &I_Quest_7x8,
@@ -48,27 +38,16 @@ struct SubghzReceiver {
     View* view;
     SubghzReceiverCallback callback;
     void* context;
-    SubGhzWorker* worker;
-    SubGhzProtocol* protocol;
-    osTimerId timer;
-    SubGhzHopperState hopper_state;
-    uint8_t hopper_timeout;
-    uint32_t event_key_sequence;
 };
 
 typedef struct {
-    string_t text;
-    uint16_t scene;
-    SubGhzProtocolCommon* protocol_result;
-    SubGhzHistory* history;
-    uint8_t frequency;
-    uint8_t temp_frequency;
-    uint32_t real_frequency;
-
+    string_t frequency_str;
+    string_t preset_str;
+    string_t history_stat_str;
+    SubGhzReceiverHistory* history;
     uint16_t idx;
     uint16_t list_offset;
     uint16_t history_item;
-    bool menu;
 } SubghzReceiverModel;
 
 void subghz_receiver_set_callback(
@@ -81,50 +60,53 @@ void subghz_receiver_set_callback(
     subghz_receiver->context = context;
 }
 
-void subghz_receiver_set_protocol(
-    SubghzReceiver* subghz_receiver,
-    SubGhzProtocolCommon* protocol_result,
-    SubGhzProtocol* protocol) {
+static void subghz_receiver_update_offset(SubghzReceiver* subghz_receiver) {
     furi_assert(subghz_receiver);
+
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            model->protocol_result = protocol_result;
+            size_t history_item = model->history_item;
+            uint16_t bounds = history_item > 3 ? 2 : history_item;
+
+            if(history_item > 3 && model->idx >= history_item - 1) {
+                model->list_offset = model->idx - 3;
+            } else if(model->list_offset < model->idx - bounds) {
+                model->list_offset = CLAMP(model->list_offset + 1, history_item - bounds, 0);
+            } else if(model->list_offset > model->idx - bounds) {
+                model->list_offset = CLAMP(model->idx - 1, history_item - bounds, 0);
+            }
             return true;
         });
-    subghz_receiver->protocol = protocol;
 }
 
-SubGhzProtocolCommon* subghz_receiver_get_protocol(SubghzReceiver* subghz_receiver) {
+void subghz_receiver_add_item_to_menu(
+    SubghzReceiver* subghz_receiver,
+    const char* name,
+    uint8_t type) {
     furi_assert(subghz_receiver);
-    SubGhzProtocolCommon* result = NULL;
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            result = model->protocol_result;
-            return false;
+            SubGhzReceiverMenuItem* item_menu =
+                SubGhzReceiverMenuItemArray_push_raw(model->history->data);
+            string_init_set_str(item_menu->item_str, name);
+            item_menu->type = type;
+            model->history_item++;
+            return true;
         });
-    return result;
-}
-
-void subghz_receiver_set_worker(SubghzReceiver* subghz_receiver, SubGhzWorker* worker) {
-    furi_assert(subghz_receiver);
-    subghz_receiver->worker = worker;
+    subghz_receiver_update_offset(subghz_receiver);
 }
 
-static void subghz_receiver_update_offset(SubghzReceiver* subghz_receiver) {
+void subghz_receiver_add_data_statusbar(
+    SubghzReceiver* subghz_receiver,
+    const char* frequency_str,
+    const char* preset_str,
+    const char* history_stat_str) {
     furi_assert(subghz_receiver);
-
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            size_t history_item = model->history_item;
-            uint16_t bounds = history_item > 3 ? 2 : history_item;
-
-            if(history_item > 3 && model->idx >= history_item - 1) {
-                model->list_offset = model->idx - 3;
-            } else if(model->list_offset < model->idx - bounds) {
-                model->list_offset = CLAMP(model->list_offset + 1, history_item - bounds, 0);
-            } else if(model->list_offset > model->idx - bounds) {
-                model->list_offset = CLAMP(model->idx - 1, history_item - bounds, 0);
-            }
+            string_set(model->frequency_str, frequency_str);
+            string_set(model->preset_str, preset_str);
+            string_set(model->history_stat_str, history_stat_str);
             return true;
         });
 }
@@ -144,462 +126,113 @@ static void subghz_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scroll
 }
 
 void subghz_receiver_draw(Canvas* canvas, SubghzReceiverModel* model) {
-    bool scrollbar = model->history_item > 4;
-    string_t str_buff;
-    char buffer[64];
-    uint32_t frequency;
-    string_init(str_buff);
-
     canvas_clear(canvas);
     canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
 
-    switch(model->scene) {
-    case ReceiverSceneMain:
-        for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
-            size_t idx = CLAMP(i + model->list_offset, model->history_item, 0);
-            subghz_history_get_text_item_menu(model->history, str_buff, idx);
-            elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX);
-            if(model->idx == idx) {
-                subghz_receiver_draw_frame(canvas, i, scrollbar);
-            } else {
-                canvas_set_color(canvas, ColorBlack);
-            }
-            canvas_draw_icon(
-                canvas,
-                1,
-                2 + i * FRAME_HEIGHT,
-                ReceiverItemIcons[subghz_history_get_type_protocol(model->history, idx)]);
-            canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, string_get_cstr(str_buff));
-            string_clean(str_buff);
-        }
-        if(scrollbar) {
-            elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item);
-        }
-        canvas_set_color(canvas, ColorBlack);
-        canvas_set_font(canvas, FontSecondary);
-
-        elements_button_left(canvas, "Config");
-        canvas_draw_line(canvas, 46, 51, 125, 51);
-        if(subghz_history_get_text_space_left(model->history, str_buff)) {
-            canvas_draw_str(canvas, 54, 62, string_get_cstr(str_buff));
-        } else {
-            if((model->real_frequency / 1000 % 10) > 4) {
-                frequency = model->real_frequency + 10000;
-            } else {
-                frequency = model->real_frequency;
-            }
-            snprintf(
-                buffer,
-                sizeof(buffer),
-                "%03ld.%02ld",
-                frequency / 1000000 % 1000,
-                frequency / 10000 % 100);
-            canvas_draw_str(canvas, 44, 62, buffer);
-            canvas_draw_str(canvas, 79, 62, "AM");
-            canvas_draw_str(canvas, 96, 62, string_get_cstr(str_buff));
-        }
-        break;
+    elements_button_left(canvas, "Config");
 
-    case ReceiverSceneStart:
+    canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str));
+    canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str));
+    canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str));
+    if(model->history_item == 0) {
         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
         canvas_set_font(canvas, FontPrimary);
         canvas_draw_str(canvas, 63, 46, "Scanning...");
-        canvas_set_color(canvas, ColorBlack);
-        canvas_set_font(canvas, FontSecondary);
-        elements_button_left(canvas, "Config");
-        if((model->real_frequency / 1000 % 10) > 4) {
-            frequency = model->real_frequency + 10000;
-        } else {
-            frequency = model->real_frequency;
-        }
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "%03ld.%02ld",
-            frequency / 1000000 % 1000,
-            frequency / 10000 % 100);
-        canvas_draw_str(canvas, 44, 62, buffer);
-        canvas_draw_str(canvas, 79, 62, "AM");
-        subghz_history_get_text_space_left(model->history, str_buff);
-        canvas_draw_str(canvas, 96, 62, string_get_cstr(str_buff));
         canvas_draw_line(canvas, 46, 51, 125, 51);
-        break;
-
-    case ReceiverSceneConfig:
-        if(model->frequency < subghz_frequencies_count) {
-            snprintf(
-                buffer,
-                sizeof(buffer),
-                "Frequency:  < %03ld.%03ldMHz >",
-                model->real_frequency / 1000000 % 1000,
-                model->real_frequency / 1000 % 1000);
-            canvas_draw_str(canvas, 0, 8, buffer);
-            canvas_draw_str(canvas, 0, 18, "Frequency Hopping: <OFF>");
-        } else {
-            canvas_draw_str(canvas, 0, 8, "Frequency: < --- >");
-            canvas_draw_str(canvas, 0, 18, "Frequency Hopping: <ON>");
-        }
-        canvas_draw_str(canvas, 0, 28, "Modulation: <AM>");
-
-        elements_button_center(canvas, "Save");
-        break;
-
-    case ReceiverSceneInfo:
-        canvas_set_font(canvas, FontSecondary);
-        elements_multiline_text(canvas, 0, 8, string_get_cstr(model->text));
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "%03ld.%03ld",
-            subghz_history_get_frequency(model->history, model->idx) / 1000000 % 1000,
-            subghz_history_get_frequency(model->history, model->idx) / 1000 % 1000);
-        canvas_draw_str(canvas, 90, 8, buffer);
-        if(model->protocol_result && model->protocol_result->to_save_string &&
-           strcmp(model->protocol_result->name, "KeeLoq")) {
-            elements_button_right(canvas, "Save");
-            elements_button_center(canvas, "Send");
-        }
-        break;
+        return;
+    }
+    canvas_draw_line(canvas, 46, 51, 125, 51);
 
-    default:
+    bool scrollbar = model->history_item > 4;
+    string_t str_buff;
+    string_init(str_buff);
 
-        break;
-    }
+    SubGhzReceiverMenuItem* item_menu;
 
+    for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
+        size_t idx = CLAMP(i + model->list_offset, model->history_item, 0);
+        item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx);
+        string_set(str_buff, item_menu->item_str);
+        elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX);
+        if(model->idx == idx) {
+            subghz_receiver_draw_frame(canvas, i, scrollbar);
+        } else {
+            canvas_set_color(canvas, ColorBlack);
+        }
+        canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]);
+        canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, string_get_cstr(str_buff));
+        string_clean(str_buff);
+    }
+    if(scrollbar) {
+        elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item);
+    }
     string_clear(str_buff);
 }
 
-void subghz_receiver_history_full(void* context) {
-    furi_assert(context);
-    SubghzReceiver* subghz_receiver = context;
-    subghz_receiver->callback(SubghzReceverEventSendHistoryFull, subghz_receiver->context);
-    subghz_receiver->hopper_state = SubGhzHopperStateOFF;
-}
-
 bool subghz_receiver_input(InputEvent* event, void* context) {
     furi_assert(context);
-
-    uint8_t scene = 0;
     SubghzReceiver* subghz_receiver = context;
-    with_view_model(
-        subghz_receiver->view, (SubghzReceiverModel * model) {
-            scene = model->scene;
-            return false;
-        });
-
-    bool can_be_saved = false;
-
-    switch(scene) {
-    case ReceiverSceneMain:
-        if(event->key == InputKeyBack && event->type == InputTypeShort) {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    model->idx = 0;
-                    model->list_offset = 0;
-                    model->history_item = 0;
-                    subghz_history_clean(model->history);
-                    return true;
-                });
-            return false;
-        } else if(
-            event->key == InputKeyUp &&
-            (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    if(model->idx != 0) model->idx--;
-                    return true;
-                });
-        } else if(
-            event->key == InputKeyDown &&
-            (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    if(model->idx != subghz_history_get_item(model->history) - 1) model->idx++;
-                    return true;
-                });
-        } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
-            subghz_receiver->hopper_state = SubGhzHopperStatePause;
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    model->scene = ReceiverSceneConfig;
-                    model->temp_frequency = model->frequency;
-                    return true;
-                });
-            subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context);
-        } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
-            subghz_receiver->event_key_sequence = event->sequence;
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    string_clean(model->text);
-                    model->protocol_result = subghz_protocol_get_by_name(
-                        subghz_receiver->protocol,
-                        subghz_history_get_name(model->history, model->idx));
-                    if(model->protocol_result->to_load_protocol != NULL) {
-                        model->protocol_result->to_load_protocol(
-                            model->protocol_result,
-                            subghz_history_get_raw_data(model->history, model->idx));
-                        model->protocol_result->to_string(model->protocol_result, model->text);
-                        model->scene = ReceiverSceneInfo;
-                    }
-                    return true;
-                });
-        }
-        break;
 
-    case ReceiverSceneInfo:
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        subghz_receiver->callback(SubghzReceverEventBack, subghz_receiver->context);
+    } else if(
+        event->key == InputKeyUp &&
+        (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
         with_view_model(
             subghz_receiver->view, (SubghzReceiverModel * model) {
-                can_be_saved =
-                    (model->protocol_result && model->protocol_result->to_save_string &&
-                     strcmp(model->protocol_result->name, "KeeLoq"));
-                return false;
+                if(model->idx != 0) model->idx--;
+                return true;
             });
-        if(event->key == InputKeyBack && event->type == InputTypeShort) {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    subghz_rx_end(subghz_receiver->worker);
-                    model->real_frequency =
-                        subghz_rx(subghz_receiver->worker, subghz_frequencies[model->frequency]);
-                    subghz_receiver->hopper_state = SubGhzHopperStateRunnig;
-                    model->scene = ReceiverSceneMain;
-                    return true;
-                });
-            subghz_receiver->callback(SubghzReceverEventMain, subghz_receiver->context);
-        } else if(can_be_saved && event->key == InputKeyRight) {
-            subghz_receiver->callback(SubghzReceverEventSave, subghz_receiver->context);
-            return false;
-        } else if(
-            can_be_saved && event->key == InputKeyOk && event->type == InputTypePress &&
-            subghz_receiver->event_key_sequence != event->sequence) {
-            subghz_receiver->hopper_state = SubGhzHopperStatePause;
-            subghz_rx_end(subghz_receiver->worker);
-            subghz_receiver->callback(SubghzReceverEventSendStart, subghz_receiver->context);
-            return true;
-        } else if(
-            can_be_saved && event->key == InputKeyOk && event->type == InputTypeRelease &&
-            subghz_receiver->event_key_sequence != event->sequence) {
-            subghz_receiver->callback(SubghzReceverEventSendStop, subghz_receiver->context);
-            return true;
-        }
-        break;
-
-    case ReceiverSceneConfig:
-        if(event->type != InputTypeShort) return false;
-        if(event->key == InputKeyBack) {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    model->frequency = model->temp_frequency;
-                    model->real_frequency = subghz_frequencies[model->frequency];
-                    subghz_receiver->hopper_state = SubGhzHopperStateRunnig;
-                    if(subghz_history_get_item(model->history) == 0) {
-                        model->scene = ReceiverSceneStart;
-                    } else {
-                        model->scene = ReceiverSceneMain;
-                    }
-                    return true;
-                });
-            subghz_receiver->callback(SubghzReceverEventMain, subghz_receiver->context);
-        } else if(event->key == InputKeyOk) {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    if(model->frequency < subghz_frequencies_count) {
-                        subghz_rx_end(subghz_receiver->worker);
-                        model->real_frequency = subghz_rx(
-                            subghz_receiver->worker, subghz_frequencies[model->frequency]);
-                        subghz_receiver->hopper_state = SubGhzHopperStateOFF;
-                    } else {
-                        osTimerStart(subghz_receiver->timer, 1024 / 10);
-                        subghz_receiver->hopper_state = SubGhzHopperStateRunnig;
-                    }
-                    if(subghz_history_get_item(model->history) == 0) {
-                        model->scene = ReceiverSceneStart;
-                    } else {
-                        model->scene = ReceiverSceneMain;
-                    }
-                    return true;
-                });
-            subghz_receiver->callback(SubghzReceverEventMain, subghz_receiver->context);
-        } else {
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    bool model_updated = false;
-
-                    if(event->key == InputKeyLeft) {
-                        if(model->frequency > 0) model->frequency--;
-                        model_updated = true;
-                    } else if(event->key == InputKeyRight) {
-                        if(model->frequency < subghz_frequencies_count) model->frequency++;
-                        model_updated = true;
-                    }
-                    if(model_updated) {
-                        model->real_frequency = subghz_frequencies[model->frequency];
-                    }
-                    return model_updated;
-                });
-        }
-        break;
-
-    case ReceiverSceneStart:
-        if(event->type != InputTypeShort) return false;
-        if(event->key == InputKeyBack) {
-            return false;
-        } else if(event->key == InputKeyLeft) {
-            subghz_receiver->hopper_state = SubGhzHopperStatePause;
-            with_view_model(
-                subghz_receiver->view, (SubghzReceiverModel * model) {
-                    model->temp_frequency = model->frequency;
-                    model->scene = ReceiverSceneConfig;
-                    return true;
-                });
-            subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context);
-        }
-        break;
-
-    default:
-        break;
-    }
-
-    subghz_receiver_update_offset(subghz_receiver);
-    if(scene != ReceiverSceneInfo) {
+    } else if(
+        event->key == InputKeyDown &&
+        (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
         with_view_model(
             subghz_receiver->view, (SubghzReceiverModel * model) {
-                if(subghz_history_get_text_space_left(model->history, NULL)) {
-                    subghz_receiver_history_full(subghz_receiver);
+                if(model->idx != model->history_item - 1) model->idx++;
+                return true;
+            });
+    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
+        subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context);
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        with_view_model(
+            subghz_receiver->view, (SubghzReceiverModel * model) {
+                if(model->history_item != 0) {
+                    subghz_receiver->callback(SubghzReceverEventOK, subghz_receiver->context);
                 }
                 return false;
             });
     }
 
-    return true;
-}
-
-void subghz_receiver_text_callback(string_t text, void* context) {
-    furi_assert(context);
-    SubghzReceiver* subghz_receiver = context;
-
-    with_view_model(
-        subghz_receiver->view, (SubghzReceiverModel * model) {
-            string_set(model->text, text);
-            model->scene = ReceiverSceneMain;
-            return true;
-        });
-}
-
-void subghz_receiver_protocol_callback(SubGhzProtocolCommon* parser, void* context) {
-    furi_assert(context);
-    SubghzReceiver* subghz_receiver = context;
-
-    with_view_model(
-        subghz_receiver->view, (SubghzReceiverModel * model) {
-            model->protocol_result = parser;
-            subghz_history_set_frequency_preset(
-                model->history,
-                model->history_item,
-                model->real_frequency,
-                FuriHalSubGhzPresetOok650Async);
-            subghz_history_add_to_history(model->history, parser);
-
-            model->history_item = subghz_history_get_item(model->history);
-            model->scene = ReceiverSceneMain;
-            if(subghz_history_get_text_space_left(model->history, NULL)) {
-                subghz_receiver_history_full(subghz_receiver);
-            }
-            return true;
-        });
-    subghz_protocol_reset(subghz_receiver->protocol);
     subghz_receiver_update_offset(subghz_receiver);
-}
 
-static void subghz_receiver_timer_callback(void* context) {
-    furi_assert(context);
-    SubghzReceiver* subghz_receiver = context;
-
-    switch(subghz_receiver->hopper_state) {
-    case SubGhzHopperStatePause:
-        return;
-        break;
-    case SubGhzHopperStateOFF:
-        osTimerStop(subghz_receiver->timer);
-        return;
-        break;
-    case SubGhzHopperStateRSSITimeOut:
-        if(subghz_receiver->hopper_timeout != 0) {
-            subghz_receiver->hopper_timeout--;
-            return;
-        }
-        break;
-    default:
-        break;
-    }
-    float rssi = -127.0f;
-    with_view_model(
-        subghz_receiver->view, (SubghzReceiverModel * model) {
-            if(subghz_receiver->hopper_state != SubGhzHopperStateRSSITimeOut) {
-                // See RSSI Calculation timings in CC1101 17.3 RSSI
-                rssi = furi_hal_subghz_get_rssi();
-
-                // Stay if RSSI is high enough
-                if(rssi > -90.0f) {
-                    subghz_receiver->hopper_timeout = 10;
-                    subghz_receiver->hopper_state = SubGhzHopperStateRSSITimeOut;
-                    return false;
-                }
-            } else {
-                subghz_receiver->hopper_state = SubGhzHopperStateRunnig;
-            }
-
-            // Select next frequency
-            if(model->frequency < COUNT_FREQUNCY_HOPPER - 1) {
-                model->frequency++;
-            } else {
-                model->frequency = 0;
-            }
-
-            // Restart radio
-            furi_hal_subghz_idle();
-            subghz_protocol_reset(subghz_receiver->protocol);
-            model->real_frequency = furi_hal_subghz_set_frequency_and_path(
-                subghz_frequencies_hopper[model->frequency]);
-            furi_hal_subghz_rx();
-
-            return true;
-        });
+    return true;
 }
 
 void subghz_receiver_enter(void* context) {
     furi_assert(context);
-    SubghzReceiver* subghz_receiver = context;
-    //Start CC1101 Rx
-    subghz_begin(FuriHalSubGhzPresetOok650Async);
-    with_view_model(
-        subghz_receiver->view, (SubghzReceiverModel * model) {
-            subghz_rx_end(subghz_receiver->worker);
-            model->frequency = subghz_frequencies_433_92;
-            model->real_frequency =
-                subghz_rx(subghz_receiver->worker, subghz_frequencies[model->frequency]);
-            if(subghz_history_get_item(model->history) == 0) {
-                model->scene = ReceiverSceneStart;
-            } else {
-                model->scene = ReceiverSceneMain;
-            }
-            return true;
-        });
-    subghz_protocol_enable_dump(
-        subghz_receiver->protocol, subghz_receiver_protocol_callback, subghz_receiver);
+    //SubghzReceiver* subghz_receiver = context;
 }
 
 void subghz_receiver_exit(void* context) {
     furi_assert(context);
     SubghzReceiver* subghz_receiver = context;
-    osTimerStop(subghz_receiver->timer);
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            string_clean(model->text);
-            return true;
+            string_clean(model->frequency_str);
+            string_clean(model->preset_str);
+            string_clean(model->history_stat_str);
+                for
+                    M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) {
+                        string_clear(item_menu->item_str);
+                        item_menu->type = 0;
+                    }
+                SubGhzReceiverMenuItemArray_clean(model->history->data);
+                model->idx = 0;
+                model->list_offset = 0;
+                model->history_item = 0;
+                return false;
         });
-    // Stop CC1101 Rx
-    subghz_rx_end(subghz_receiver->worker);
-    subghz_sleep();
 }
 
 SubghzReceiver* subghz_receiver_alloc() {
@@ -616,14 +249,14 @@ SubghzReceiver* subghz_receiver_alloc() {
 
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            string_init(model->text);
-            model->history = subghz_history_alloc();
+            string_init(model->frequency_str);
+            string_init(model->preset_str);
+            string_init(model->history_stat_str);
+            model->history = furi_alloc(sizeof(SubGhzReceiverHistory));
+            SubGhzReceiverMenuItemArray_init(model->history->data);
             return true;
         });
 
-    subghz_receiver->timer =
-        osTimerNew(subghz_receiver_timer_callback, osTimerPeriodic, subghz_receiver, NULL);
-    subghz_receiver->hopper_state = SubGhzHopperStateOFF;
     return subghz_receiver;
 }
 
@@ -632,11 +265,18 @@ void subghz_receiver_free(SubghzReceiver* subghz_receiver) {
 
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            string_clear(model->text);
-            subghz_history_free(model->history);
-            return false;
+            string_clear(model->frequency_str);
+            string_clear(model->preset_str);
+            string_clear(model->history_stat_str);
+                for
+                    M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) {
+                        string_clear(item_menu->item_str);
+                        item_menu->type = 0;
+                    }
+                SubGhzReceiverMenuItemArray_clear(model->history->data);
+                free(model->history);
+                return false;
         });
-    osTimerDelete(subghz_receiver->timer);
     view_free(subghz_receiver->view);
     free(subghz_receiver);
 }
@@ -646,43 +286,24 @@ View* subghz_receiver_get_view(SubghzReceiver* subghz_receiver) {
     return subghz_receiver->view;
 }
 
-uint32_t subghz_receiver_get_frequency(SubghzReceiver* subghz_receiver) {
-    furi_assert(subghz_receiver);
-    uint32_t frequency;
-    with_view_model(
-        subghz_receiver->view, (SubghzReceiverModel * model) {
-            frequency = subghz_history_get_frequency(model->history, model->idx);
-            return false;
-        });
-    return frequency;
-}
-
-FuriHalSubGhzPreset subghz_receiver_get_preset(SubghzReceiver* subghz_receiver) {
+uint16_t subghz_receiver_get_idx_menu(SubghzReceiver* subghz_receiver) {
     furi_assert(subghz_receiver);
-    FuriHalSubGhzPreset preset;
+    uint32_t idx = 0;
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            preset = subghz_history_get_preset(model->history, model->idx);
+            idx = model->idx;
             return false;
         });
-    return preset;
+    return idx;
 }
 
-void subghz_receiver_frequency_preset_to_str(SubghzReceiver* subghz_receiver, string_t output) {
+void subghz_receiver_set_idx_menu(SubghzReceiver* subghz_receiver, uint16_t idx) {
     furi_assert(subghz_receiver);
-    uint32_t frequency;
-    uint32_t preset;
     with_view_model(
         subghz_receiver->view, (SubghzReceiverModel * model) {
-            frequency = subghz_history_get_frequency(model->history, model->idx);
-            preset = (uint32_t)subghz_history_get_preset(model->history, model->idx);
-            return false;
+            model->idx = idx;
+            if(model->idx > 2) model->list_offset = idx - 2;
+            return true;
         });
-
-    string_cat_printf(
-        output,
-        "Frequency: %d\n"
-        "Preset: %d\n",
-        (int)frequency,
-        (int)preset);
-}
+    subghz_receiver_update_offset(subghz_receiver);
+}

+ 11 - 19
applications/subghz/views/subghz_receiver.h

@@ -1,21 +1,11 @@
 #pragma once
 
 #include <gui/view.h>
-#include <lib/subghz/protocols/subghz_protocol_common.h>
-#include <lib/subghz/protocols/subghz_protocol.h>
-#include <lib/subghz/subghz_worker.h>
-#include "../subghz_history.h"
 
 typedef enum {
     SubghzReceverEventOK,
     SubghzReceverEventConfig,
-    SubghzReceverEventMain,
-    SubghzReceverEventSave,
     SubghzReceverEventBack,
-    SubghzReceverEventMore,
-    SubghzReceverEventSendStart,
-    SubghzReceverEventSendStop,
-    SubghzReceverEventSendHistoryFull,
 } SubghzReceverEvent;
 
 typedef struct SubghzReceiver SubghzReceiver;
@@ -33,17 +23,19 @@ void subghz_receiver_free(SubghzReceiver* subghz_receiver);
 
 View* subghz_receiver_get_view(SubghzReceiver* subghz_receiver);
 
-void subghz_receiver_set_protocol(
+void subghz_receiver_add_data_statusbar(
     SubghzReceiver* subghz_receiver,
-    SubGhzProtocolCommon* protocol_result,
-    SubGhzProtocol* protocol);
+    const char* frequency_str,
+    const char* preset_str,
+    const char* history_stat_str);
 
-SubGhzProtocolCommon* subghz_receiver_get_protocol(SubghzReceiver* subghz_receiver);
-
-void subghz_receiver_set_worker(SubghzReceiver* subghz_receiver, SubGhzWorker* worker);
+void subghz_receiver_add_item_to_menu(
+    SubghzReceiver* subghz_receiver,
+    const char* name,
+    uint8_t type);
 
-uint32_t subghz_receiver_get_frequency(SubghzReceiver* subghz_receiver);
+uint16_t subghz_receiver_get_idx_menu(SubghzReceiver* subghz_receiver);
 
-FuriHalSubGhzPreset subghz_receiver_get_preset(SubghzReceiver* subghz_receiver);
+void subghz_receiver_set_idx_menu(SubghzReceiver* subghz_receiver, uint16_t idx);
 
-void subghz_receiver_frequency_preset_to_str(SubghzReceiver* subghz_receiver, string_t output);
+void subghz_receiver_exit(void* context);

+ 36 - 80
applications/subghz/views/subghz_transmitter.c

@@ -1,13 +1,8 @@
 #include "subghz_transmitter.h"
 #include "../subghz_i.h"
 
-#include <math.h>
-#include <furi.h>
-#include <furi-hal.h>
 #include <input/input.h>
 #include <gui/elements.h>
-#include <notification/notification-messages.h>
-#include <lib/subghz/protocols/subghz_protocol_keeloq.h>
 
 struct SubghzTransmitter {
     View* view;
@@ -16,11 +11,10 @@ struct SubghzTransmitter {
 };
 
 typedef struct {
-    string_t text;
-    uint16_t scene;
-    uint32_t real_frequency;
-    FuriHalSubGhzPreset preset;
-    SubGhzProtocolCommon* protocol;
+    string_t frequency_str;
+    string_t preset_str;
+    string_t key_str;
+    uint8_t show_button;
 } SubghzTransmitterModel;
 
 void subghz_transmitter_set_callback(
@@ -33,24 +27,19 @@ void subghz_transmitter_set_callback(
     subghz_transmitter->context = context;
 }
 
-void subghz_transmitter_set_protocol(
+void subghz_transmitter_add_data_to_show(
     SubghzTransmitter* subghz_transmitter,
-    SubGhzProtocolCommon* protocol) {
-    with_view_model(
-        subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            model->protocol = protocol;
-            return true;
-        });
-}
-
-void subghz_transmitter_set_frequency_preset(
-    SubghzTransmitter* subghz_transmitter,
-    uint32_t frequency,
-    FuriHalSubGhzPreset preset) {
+    const char* key_str,
+    const char* frequency_str,
+    const char* preset_str,
+    uint8_t show_button) {
+    furi_assert(subghz_transmitter);
     with_view_model(
         subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            model->real_frequency = frequency;
-            model->preset = preset;
+            string_set(model->key_str, key_str);
+            string_set(model->frequency_str, frequency_str);
+            string_set(model->preset_str, preset_str);
+            model->show_button = show_button;
             return true;
         });
 }
@@ -87,26 +76,13 @@ static void subghz_transmitter_button_right(Canvas* canvas, const char* str) {
 }
 
 void subghz_transmitter_draw(Canvas* canvas, SubghzTransmitterModel* model) {
-    char buffer[64];
     canvas_clear(canvas);
     canvas_set_color(canvas, ColorBlack);
     canvas_set_font(canvas, FontSecondary);
-    elements_multiline_text(canvas, 0, 8, string_get_cstr(model->text));
-    snprintf(
-        buffer,
-        sizeof(buffer),
-        "%03ld.%03ld",
-        model->real_frequency / 1000000 % 1000,
-        model->real_frequency / 1000 % 1000);
-    canvas_draw_str(canvas, 90, 8, buffer);
-
-    if(model->protocol && model->protocol->get_upload_protocol) {
-        if((!strcmp(model->protocol->name, "KeeLoq")) &&
-           (!strcmp(subghz_protocol_keeloq_get_manufacture_name(model->protocol), "Unknown"))) {
-            return;
-        }
-        subghz_transmitter_button_right(canvas, "Send");
-    }
+    elements_multiline_text(canvas, 0, 8, string_get_cstr(model->key_str));
+    canvas_draw_str(canvas, 78, 8, string_get_cstr(model->frequency_str));
+    canvas_draw_str(canvas, 113, 8, string_get_cstr(model->preset_str));
+    if(model->show_button) subghz_transmitter_button_right(canvas, "Send");
 }
 
 bool subghz_transmitter_input(InputEvent* event, void* context) {
@@ -114,26 +90,25 @@ bool subghz_transmitter_input(InputEvent* event, void* context) {
     SubghzTransmitter* subghz_transmitter = context;
     bool can_be_sent = false;
 
-    if(event->key == InputKeyBack) {
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        with_view_model(
+            subghz_transmitter->view, (SubghzTransmitterModel * model) {
+                string_clean(model->frequency_str);
+                string_clean(model->preset_str);
+                string_clean(model->key_str);
+                model->show_button = 0;
+                return false;
+            });
         return false;
     }
 
     with_view_model(
         subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            if(model->protocol && model->protocol->get_upload_protocol) {
-                if((!strcmp(model->protocol->name, "KeeLoq")) &&
-                   (!strcmp(
-                       subghz_protocol_keeloq_get_manufacture_name(model->protocol), "Unknown"))) {
-                    return false;
-                }
+            if(model->show_button) {
                 can_be_sent = true;
             }
-            //can_be_sent = (model->protocol && model->protocol->get_upload_protocol);
-            string_clean(model->text);
-            model->protocol->to_string(model->protocol, model->text);
             return true;
         });
-    //if(event->type != InputTypeShort) return false;
 
     if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) {
         subghz_transmitter->callback(SubghzTransmitterEventSendStart, subghz_transmitter->context);
@@ -146,37 +121,14 @@ bool subghz_transmitter_input(InputEvent* event, void* context) {
     return true;
 }
 
-void subghz_transmitter_text_callback(string_t text, void* context) {
-    furi_assert(context);
-    SubghzTransmitter* subghz_transmitter = context;
-
-    with_view_model(
-        subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            string_set(model->text, text);
-            model->scene = 0;
-            return true;
-        });
-}
-
 void subghz_transmitter_enter(void* context) {
     furi_assert(context);
-    SubghzTransmitter* subghz_transmitter = context;
-    with_view_model(
-        subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            string_clean(model->text);
-            model->protocol->to_string(model->protocol, model->text);
-            return true;
-        });
+    // SubghzTransmitter* subghz_transmitter = context;
 }
 
 void subghz_transmitter_exit(void* context) {
     furi_assert(context);
-    SubghzTransmitter* subghz_transmitter = context;
-    with_view_model(
-        subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            string_clean(model->text);
-            return true;
-        });
+    // SubghzTransmitter* subghz_transmitter = context;
 }
 
 SubghzTransmitter* subghz_transmitter_alloc() {
@@ -194,7 +146,9 @@ SubghzTransmitter* subghz_transmitter_alloc() {
 
     with_view_model(
         subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            string_init(model->text);
+            string_init(model->frequency_str);
+            string_init(model->preset_str);
+            string_init(model->key_str);
             return true;
         });
     return subghz_transmitter;
@@ -205,7 +159,9 @@ void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter) {
 
     with_view_model(
         subghz_transmitter->view, (SubghzTransmitterModel * model) {
-            string_clear(model->text);
+            string_clear(model->frequency_str);
+            string_clear(model->preset_str);
+            string_clear(model->key_str);
             return true;
         });
     view_free(subghz_transmitter->view);

+ 5 - 7
applications/subghz/views/subghz_transmitter.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #include <gui/view.h>
-#include <lib/subghz/protocols/subghz_protocol_common.h>
 
 typedef enum {
     SubghzTransmitterEventSendStart,
@@ -25,10 +24,9 @@ void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter);
 
 View* subghz_transmitter_get_view(SubghzTransmitter* subghz_transmitter);
 
-void subghz_transmitter_set_protocol(
+void subghz_transmitter_add_data_to_show(
     SubghzTransmitter* subghz_transmitter,
-    SubGhzProtocolCommon* protocol);
-void subghz_transmitter_set_frequency_preset(
-    SubghzTransmitter* subghz_transmitter,
-    uint32_t frequency,
-    FuriHalSubGhzPreset preset);
+    const char* key_str,
+    const char* frequency_str,
+    const char* preset_str,
+    uint8_t show_button);

+ 12 - 1
lib/subghz/protocols/subghz_protocol.c

@@ -10,6 +10,7 @@
 #include "subghz_protocol_faac_slh.h"
 #include "subghz_protocol_nero_sketch.h"
 #include "subghz_protocol_star_line.h"
+#include "subghz_protocol_nero_radio.h"
 
 #include "../subghz_keystore.h"
 
@@ -27,6 +28,7 @@ typedef enum {
     SubGhzProtocolTypeFaacSLH,
     SubGhzProtocolTypeNeroSketch,
     SubGhzProtocolTypeStarLine,
+    SubGhzProtocolTypeNeroRadio,
 
     SubGhzProtocolTypeMax,
 } SubGhzProtocolType;
@@ -88,6 +90,8 @@ SubGhzProtocol* subghz_protocol_alloc() {
         (SubGhzProtocolCommon*)subghz_protocol_nero_sketch_alloc();
     instance->protocols[SubGhzProtocolTypeStarLine] =
         (SubGhzProtocolCommon*)subghz_protocol_star_line_alloc(instance->keystore);
+    instance->protocols[SubGhzProtocolTypeNeroRadio] =
+        (SubGhzProtocolCommon*)subghz_protocol_nero_radio_alloc();
 
     return instance;
 }
@@ -113,6 +117,8 @@ void subghz_protocol_free(SubGhzProtocol* instance) {
         (SubGhzProtocolNeroSketch*)instance->protocols[SubGhzProtocolTypeNeroSketch]);
     subghz_protocol_star_line_free(
         (SubGhzProtocolStarLine*)instance->protocols[SubGhzProtocolTypeStarLine]);
+    subghz_protocol_nero_radio_free(
+        (SubGhzProtocolNeroRadio*)instance->protocols[SubGhzProtocolTypeNeroRadio]);
 
     subghz_keystore_free(instance->keystore);
 
@@ -163,7 +169,6 @@ void subghz_protocol_enable_dump(
 }
 
 void subghz_protocol_load_nice_flor_s_file(SubGhzProtocol* instance, const char* file_name) {
-    // subghz_protocol_nice_flor_s_name_file(instance->nice_flor_s, file_name);
     subghz_protocol_nice_flor_s_name_file(
         (SubGhzProtocolNiceFlorS*)instance->protocols[SubGhzProtocolTypeNiceFlorS], file_name);
 }
@@ -191,6 +196,8 @@ void subghz_protocol_reset(SubGhzProtocol* instance) {
         (SubGhzProtocolNeroSketch*)instance->protocols[SubGhzProtocolTypeNeroSketch]);
     subghz_protocol_star_line_reset(
         (SubGhzProtocolStarLine*)instance->protocols[SubGhzProtocolTypeStarLine]);
+    subghz_protocol_nero_radio_reset(
+        (SubGhzProtocolNeroRadio*)instance->protocols[SubGhzProtocolTypeNeroRadio]);
 }
 
 void subghz_protocol_parse(SubGhzProtocol* instance, bool level, uint32_t duration) {
@@ -220,4 +227,8 @@ void subghz_protocol_parse(SubGhzProtocol* instance, bool level, uint32_t durati
         duration);
     subghz_protocol_star_line_parse(
         (SubGhzProtocolStarLine*)instance->protocols[SubGhzProtocolTypeStarLine], level, duration);
+    subghz_protocol_nero_radio_parse(
+        (SubGhzProtocolNeroRadio*)instance->protocols[SubGhzProtocolTypeNeroRadio],
+        level,
+        duration);
 }

+ 1 - 1
lib/subghz/protocols/subghz_protocol_gate_tx.h

@@ -18,7 +18,7 @@ void subghz_protocol_gate_tx_free(SubGhzProtocolGateTX* instance);
 
 /** Get upload protocol
  * 
- * @param instance - SubGhzProtocolCame instance
+ * @param instance - SubGhzProtocolGateTX instance
  * @param encoder - SubGhzProtocolCommonEncoder encoder
  * @return bool
  */

+ 3 - 3
lib/subghz/protocols/subghz_protocol_keeloq.c

@@ -398,9 +398,9 @@ void subghz_protocol_keeloq_to_str(SubGhzProtocolKeeloq* instance, string_t outp
     string_cat_printf(
         output,
         "%s %dbit\r\n"
-        "Key:0x%lX%lX\r\n"
-        "Fix:0x%08lX     Cnt:%04X\r\n"
-        "Hop:0x%08lX     Btn:%02lX\r\n"
+        "Key:%08lX%08lX\r\n"
+        "Fix:0x%08lX    Cnt:%04X\r\n"
+        "Hop:0x%08lX    Btn:%02lX\r\n"
         "MF:%s\r\n"
         "Sn:0x%07lX \r\n",
         instance->common.name,

+ 1 - 1
lib/subghz/protocols/subghz_protocol_keeloq.h

@@ -49,7 +49,7 @@ uint64_t subghz_protocol_keeloq_gen_key(void* context);
 
 /** Get upload protocol
  * 
- * @param instance - SubGhzProtocolCame instance
+ * @param instance - SubGhzProtocolKeeloq instance
  * @param encoder - SubGhzProtocolCommonEncoder encoder
  * @return bool
  */

+ 280 - 0
lib/subghz/protocols/subghz_protocol_nero_radio.c

@@ -0,0 +1,280 @@
+#include "subghz_protocol_nero_radio.h"
+
+struct SubGhzProtocolNeroRadio {
+    SubGhzProtocolCommon common;
+};
+
+SubGhzProtocolNeroRadio* subghz_protocol_nero_radio_alloc(void) {
+    SubGhzProtocolNeroRadio* instance = furi_alloc(sizeof(SubGhzProtocolNeroRadio));
+
+    instance->common.name = "Nero Radio";
+    instance->common.code_min_count_bit_for_found = 55;
+    instance->common.te_short = 200;
+    instance->common.te_long = 400;
+    instance->common.te_delta = 80;
+    instance->common.type_protocol = TYPE_PROTOCOL_STATIC;
+    instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_nero_radio_to_str;
+    instance->common.to_save_string =
+        (SubGhzProtocolCommonGetStrSave)subghz_protocol_nero_radio_to_save_str;
+    instance->common.to_load_protocol_from_file =
+        (SubGhzProtocolCommonLoadFromFile)subghz_protocol_nero_radio_to_load_protocol_from_file;
+    instance->common.to_load_protocol =
+        (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_nero_radio_to_load_protocol;
+    instance->common.get_upload_protocol =
+        (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_nero_radio_send_key;
+
+    return instance;
+}
+
+void subghz_protocol_nero_radio_free(SubGhzProtocolNeroRadio* instance) {
+    furi_assert(instance);
+    free(instance);
+}
+
+bool subghz_protocol_nero_radio_send_key(
+    SubGhzProtocolNeroRadio* instance,
+    SubGhzProtocolCommonEncoder* encoder) {
+    furi_assert(instance);
+    furi_assert(encoder);
+    size_t index = 0;
+    encoder->size_upload = 2 + 47 * 2 + 2 + (instance->common.code_last_count_bit * 2);
+    if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false;
+
+    //Send header
+    encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short);
+    encoder->upload[index++] =
+        level_duration_make(false, (uint32_t)instance->common.te_short * 37);
+    for(uint8_t i = 0; i < 47; i++) {
+        encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short);
+        encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short);
+    }
+
+    //Send start bit
+    encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short * 4);
+    encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short);
+
+    //Send key data
+    for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) {
+        if(bit_read(instance->common.code_last_found, i - 1)) {
+            //send bit 1
+            encoder->upload[index++] =
+                level_duration_make(true, (uint32_t)instance->common.te_long);
+            encoder->upload[index++] =
+                level_duration_make(false, (uint32_t)instance->common.te_short);
+        } else {
+            //send bit 0
+            encoder->upload[index++] =
+                level_duration_make(true, (uint32_t)instance->common.te_short);
+            encoder->upload[index++] =
+                level_duration_make(false, (uint32_t)instance->common.te_long);
+        }
+    }
+
+    return true;
+}
+
+void subghz_protocol_nero_radio_reset(SubGhzProtocolNeroRadio* instance) {
+    instance->common.parser_step = 0;
+}
+
+/** Analysis of received data
+ * 
+ * @param instance SubGhzProtocolNeroRadio instance
+ */
+// void subghz_protocol_nero_radio_check_remote_controller(SubGhzProtocolNeroRadio* instance) {
+//     //пока не понятно с серийником, но код статический
+//     // uint64_t code_found_reverse = subghz_protocol_common_reverse_key(instance->common.code_found, instance->common.code_count_bit);
+//     // uint32_t code_fix = code_found_reverse & 0xFFFFFFFF;
+//     // //uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFF;
+
+//     // instance->common.serial = code_fix & 0xFFFFFFF;
+//     // instance->common.btn = (code_fix >> 28) & 0x0F;
+
+//     //if (instance->common.callback) instance->common.callback((SubGhzProtocolCommon*)instance, instance->common.context);
+
+// }
+
+void subghz_protocol_nero_radio_parse(
+    SubGhzProtocolNeroRadio* instance,
+    bool level,
+    uint32_t duration) {
+    switch(instance->common.parser_step) {
+    case 0:
+        if((level) &&
+           (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) {
+            instance->common.parser_step = 1;
+            instance->common.te_last = duration;
+            instance->common.header_count = 0;
+        } else {
+            instance->common.parser_step = 0;
+        }
+        break;
+    case 1:
+        if(level) {
+            if((DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) ||
+               (DURATION_DIFF(duration, instance->common.te_short * 4) <
+                instance->common.te_delta)) {
+                instance->common.te_last = duration;
+            } else {
+                instance->common.parser_step = 0;
+            }
+        } else if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) {
+            if(DURATION_DIFF(instance->common.te_last, instance->common.te_short) <
+               instance->common.te_delta) {
+                // Found header
+                instance->common.header_count++;
+                break;
+            } else if(
+                DURATION_DIFF(instance->common.te_last, instance->common.te_short * 4) <
+                instance->common.te_delta) {
+                // Found start bit
+                if(instance->common.header_count > 40) {
+                    instance->common.parser_step = 2;
+                    instance->common.code_found = 0;
+                    instance->common.code_count_bit = 0;
+                } else {
+                    instance->common.parser_step = 0;
+                }
+            } else {
+                instance->common.parser_step = 0;
+            }
+        } else {
+            instance->common.parser_step = 0;
+        }
+        break;
+    case 2:
+        if(level) {
+            instance->common.te_last = duration;
+            instance->common.parser_step = 3;
+        } else {
+            instance->common.parser_step = 0;
+        }
+        break;
+    case 3:
+        if(!level) {
+            if(duration >= (instance->common.te_short * 10 + instance->common.te_delta * 2)) {
+                //Found stop bit
+                instance->common.parser_step = 0;
+                if(instance->common.code_count_bit >=
+                   instance->common.code_min_count_bit_for_found) {
+                    instance->common.code_last_found = instance->common.code_found;
+                    instance->common.code_last_count_bit = instance->common.code_count_bit;
+                    if(instance->common.callback)
+                        instance->common.callback(
+                            (SubGhzProtocolCommon*)instance, instance->common.context);
+                }
+                instance->common.code_found = 0;
+                instance->common.code_count_bit = 0;
+                instance->common.parser_step = 0;
+                break;
+            } else if(
+                (DURATION_DIFF(instance->common.te_last, instance->common.te_short) <
+                 instance->common.te_delta) &&
+                (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) {
+                subghz_protocol_common_add_bit(&instance->common, 0);
+                instance->common.parser_step = 2;
+            } else if(
+                (DURATION_DIFF(instance->common.te_last, instance->common.te_long) <
+                 instance->common.te_delta) &&
+                (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) {
+                subghz_protocol_common_add_bit(&instance->common, 1);
+                instance->common.parser_step = 2;
+            } else {
+                instance->common.parser_step = 0;
+            }
+        } else {
+            instance->common.parser_step = 0;
+        }
+        break;
+    }
+}
+
+void subghz_protocol_nero_radio_to_str(SubGhzProtocolNeroRadio* instance, string_t output) {
+    uint32_t code_found_hi = instance->common.code_last_found >> 32;
+    uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff;
+
+    uint64_t code_found_reverse = subghz_protocol_common_reverse_key(
+        instance->common.code_last_found, instance->common.code_last_count_bit);
+
+    uint32_t code_found_reverse_hi = code_found_reverse >> 32;
+    uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff;
+
+    string_cat_printf(
+        output,
+        "%s %dbit\r\n"
+        "Key:0x%lX%08lX\r\n"
+        "Yek:0x%lX%08lX\r\n",
+        instance->common.name,
+        instance->common.code_last_count_bit,
+        code_found_hi,
+        code_found_lo,
+        code_found_reverse_hi,
+        code_found_reverse_lo);
+}
+
+void subghz_protocol_nero_radio_to_save_str(SubGhzProtocolNeroRadio* instance, string_t output) {
+    uint32_t code_found_hi = instance->common.code_last_found >> 32;
+    uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff;
+
+    string_printf(
+        output,
+        "Protocol: %s\n"
+        "Bit: %d\n"
+        "Key: %08lX%08lX\n",
+        instance->common.name,
+        instance->common.code_last_count_bit,
+        code_found_hi,
+        code_found_lo);
+}
+
+bool subghz_protocol_nero_radio_to_load_protocol_from_file(
+    FileWorker* file_worker,
+    SubGhzProtocolNeroRadio* instance) {
+    bool loaded = false;
+    string_t temp_str;
+    string_init(temp_str);
+    int res = 0;
+    int data = 0;
+
+    do {
+        // Read and parse bit data from 2nd line
+        if(!file_worker_read_until(file_worker, temp_str, '\n')) {
+            break;
+        }
+        res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data);
+        if(res != 1) {
+            break;
+        }
+        instance->common.code_last_count_bit = (uint8_t)data;
+
+        // Read and parse key data from 3nd line
+        if(!file_worker_read_until(file_worker, temp_str, '\n')) {
+            break;
+        }
+        // strlen("Key: ") = 5
+        string_right(temp_str, 5);
+
+        uint8_t buf_key[8] = {0};
+        if(!subghz_protocol_common_read_hex(temp_str, buf_key, 8)) {
+            break;
+        }
+
+        for(uint8_t i = 0; i < 8; i++) {
+            instance->common.code_last_found = instance->common.code_last_found << 8 | buf_key[i];
+        }
+
+        loaded = true;
+    } while(0);
+
+    string_clear(temp_str);
+
+    return loaded;
+}
+
+void subghz_decoder_nero_radio_to_load_protocol(SubGhzProtocolNeroRadio* instance, void* context) {
+    furi_assert(context);
+    furi_assert(instance);
+    SubGhzProtocolCommonLoad* data = context;
+    instance->common.code_last_found = data->code_found;
+    instance->common.code_last_count_bit = data->code_count_bit;
+}

+ 72 - 0
lib/subghz/protocols/subghz_protocol_nero_radio.h

@@ -0,0 +1,72 @@
+#pragma once
+
+#include "subghz_protocol_common.h"
+
+typedef struct SubGhzProtocolNeroRadio SubGhzProtocolNeroRadio;
+
+/** Allocate SubGhzProtocolNeroRadio
+ * 
+ * @return SubGhzProtocolNeroRadio* 
+ */
+SubGhzProtocolNeroRadio* subghz_protocol_nero_radio_alloc();
+
+/** Free SubGhzProtocolNeroRadio
+ * 
+ * @param instance 
+ */
+void subghz_protocol_nero_radio_free(SubGhzProtocolNeroRadio* instance);
+
+/** Get upload protocol
+ * 
+ * @param instance - SubGhzProtocolNeroRadio instance
+ * @param encoder - SubGhzProtocolCommonEncoder encoder
+ * @return bool
+ */
+bool subghz_protocol_nero_radio_send_key(SubGhzProtocolNeroRadio* instance, SubGhzProtocolCommonEncoder* encoder);
+
+/** Reset internal state
+ * @param instance - SubGhzProtocolNeroRadio instance
+ */
+void subghz_protocol_nero_radio_reset(SubGhzProtocolNeroRadio* instance);
+
+/** Analysis of received data
+ * 
+ * @param instance SubGhzProtocolNeroRadio instance
+ */
+void subghz_protocol_nero_radio_check_remote_controller(SubGhzProtocolNeroRadio* instance);
+
+/** Parse accepted duration
+ * 
+ * @param instance - SubGhzProtocolNeroRadio instance
+ * @param data - LevelDuration level_duration
+ */
+void subghz_protocol_nero_radio_parse(SubGhzProtocolNeroRadio* instance, bool level, uint32_t duration);
+
+/** Outputting information from the parser
+ * 
+ * @param instance - SubGhzProtocolNeroRadio* instance
+ * @param output   - output string
+ */
+void subghz_protocol_nero_radio_to_str(SubGhzProtocolNeroRadio* instance, string_t output);
+
+/** Get a string to save the protocol
+ * 
+ * @param instance  - SubGhzProtocolNeroRadio instance
+ * @param output    - the resulting string
+ */
+void subghz_protocol_nero_radio_to_save_str(SubGhzProtocolNeroRadio* instance, string_t output);
+
+/** Loading protocol from file
+ * 
+ * @param file_worker - FileWorker file_worker
+ * @param instance - SubGhzProtocolNeroRadio instance
+ * @return bool
+ */
+bool subghz_protocol_nero_radio_to_load_protocol_from_file(FileWorker* file_worker, SubGhzProtocolNeroRadio* instance);
+
+/** Loading protocol from bin data
+ * 
+ * @param instance - SubGhzProtocolNeroRadio instance
+ * @param context - SubGhzProtocolCommonLoad context
+ */
+void subghz_decoder_nero_radio_to_load_protocol(SubGhzProtocolNeroRadio* instance, void* context);

+ 1 - 1
lib/subghz/protocols/subghz_protocol_nero_sketch.h

@@ -18,7 +18,7 @@ void subghz_protocol_nero_sketch_free(SubGhzProtocolNeroSketch* instance);
 
 /** Get upload protocol
  * 
- * @param instance - SubGhzProtocolCame instance
+ * @param instance - SubGhzProtocolNeroSketch instance
  * @param encoder - SubGhzProtocolCommonEncoder encoder
  * @return bool
  */

+ 1 - 1
lib/subghz/protocols/subghz_protocol_nice_flo.h

@@ -18,7 +18,7 @@ void subghz_protocol_nice_flo_free(SubGhzProtocolNiceFlo* instance);
 
 /** Get upload protocol
  * 
- * @param instance - SubGhzProtocolCame instance
+ * @param instance - SubGhzProtocolNiceFlo instance
  * @param encoder - SubGhzProtocolCommonEncoder encoder
  * @return bool
  */

+ 1 - 1
lib/subghz/protocols/subghz_protocol_princeton.c

@@ -172,7 +172,7 @@ void subghz_decoder_princeton_parse(
         if(!level) {
             if(duration >= (instance->common.te_short * 10 + instance->common.te_delta)) {
                 instance->common.parser_step = 1;
-                if(instance->common.code_count_bit >=
+                if(instance->common.code_count_bit ==
                    instance->common.code_min_count_bit_for_found) {
                     if(instance->common.code_last_found == instance->common.code_found) {
                         //instance->te = (instance->te+instance->common.te_last)/2; //Option 1 TE averaging

+ 3 - 3
lib/subghz/protocols/subghz_protocol_star_line.c

@@ -282,9 +282,9 @@ void subghz_protocol_star_line_to_str(SubGhzProtocolStarLine* instance, string_t
     string_cat_printf(
         output,
         "%s %dbit\r\n"
-        "Key:0x%lX%lX\r\n"
-        "Fix:0x%08lX     Cnt:%04X\r\n"
-        "Hop:0x%08lX     Btn:%02lX\r\n"
+        "Key:%08lX%08lX\r\n"
+        "Fix:0x%08lX    Cnt:%04X\r\n"
+        "Hop:0x%08lX    Btn:%02lX\r\n"
         "MF:%s\r\n"
         "Sn:0x%07lX \r\n",
         instance->common.name,