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

Merge pull request #8 from leedave/feature/subghz_like_meal_pager

Feature/subghz like meal pager
David Lee 1 год назад
Родитель
Сommit
ff257849af
42 измененных файлов с 2199 добавлено и 900 удалено
  1. 6 2
      README.md
  2. 2 10
      application.fam
  3. 2 4
      docs/README.md
  4. 9 1
      docs/changelog.md
  5. 53 0
      helpers/subghz/subghz.c
  6. 9 0
      helpers/subghz/subghz.h
  7. 14 0
      helpers/subghz/subghz_error_type.h
  8. 260 0
      helpers/subghz/subghz_i.c
  9. 83 0
      helpers/subghz/subghz_i.h
  10. 630 0
      helpers/subghz/subghz_txrx.c
  11. 336 0
      helpers/subghz/subghz_txrx.h
  12. 29 0
      helpers/subghz/subghz_txrx_i.h
  13. 12 14
      helpers/subghz/subghz_types.h
  14. 2 0
      helpers/xremote_custom_event.h
  15. 271 262
      models/cross/xremote_cross_remote.c
  16. 28 0
      models/cross/xremote_cross_remote.h
  17. 286 240
      models/cross/xremote_cross_remote_item.c
  18. 36 0
      models/cross/xremote_cross_remote_item.h
  19. 0 28
      models/cross/xremote_remote.h
  20. 0 31
      models/cross/xremote_remote_item.h
  21. 0 55
      models/subghz/subghz_i.h
  22. 9 155
      models/subghz/xremote_sg_remote.c
  23. 10 9
      models/subghz/xremote_sg_remote.h
  24. 3 31
      scenes/xremote_scene_create.c
  25. 2 2
      scenes/xremote_scene_create_add.c
  26. 2 2
      scenes/xremote_scene_edit_item.c
  27. 2 1
      scenes/xremote_scene_ir_remote.c
  28. 1 0
      scenes/xremote_scene_menu.c
  29. 1 1
      scenes/xremote_scene_pause_set.c
  30. 4 2
      scenes/xremote_scene_save_remote.c
  31. 3 3
      scenes/xremote_scene_save_remote_item.c
  32. 14 14
      scenes/xremote_scene_settings.c
  33. 1 1
      scenes/xremote_scene_sg_list.c
  34. 53 24
      scenes/xremote_scene_transmit.c
  35. 1 1
      scenes/xremote_scene_xr_list.c
  36. 1 1
      scenes/xremote_scene_xr_list_edit.c
  37. 2 2
      scenes/xremote_scene_xr_list_edit_item.c
  38. 1 1
      views/xremote_pause_set.c
  39. 2 0
      views/xremote_transmit.c
  40. 8 1
      xremote.c
  41. 8 1
      xremote.h
  42. 3 1
      xremote_i.h

+ 6 - 2
README.md

@@ -2,9 +2,9 @@
 
 
 ## Current State
 ## Current State
 - Infrared working
 - Infrared working
+- SubGhz working
 - Pause working
 - Pause working
-- SubGhz in Development
-- Edit/Rename/Delete features in development
+- IR Timing features in development
 
 
 ## What this is?
 ## What this is?
 This app combines commands used in IR and SubGhz into playlists that can be run with one click
 This app combines commands used in IR and SubGhz into playlists that can be run with one click
@@ -30,6 +30,10 @@ Wouldn't it be nicer to simply click one button and let everything happen? This
 - Add pauses, becaue target systems are not always fast enough for multiple commands<br>
 - Add pauses, becaue target systems are not always fast enough for multiple commands<br>
 - Run file containing chained IR & SubGhz commands<br>
 - Run file containing chained IR & SubGhz commands<br>
 
 
+### Limitations
+SubGhz commands will stop working if you move/rename/delete the original files on your Flipper. This is because 
+of how the Flippers SubGhz worker expects data. 
+
 ## How to install on Flipper Zero
 ## How to install on Flipper Zero
 - If you do not have one, download a firmware<br>
 - If you do not have one, download a firmware<br>
 - Plug your Flipper Zero in via USB. <br>
 - Plug your Flipper Zero in via USB. <br>

+ 2 - 10
application.fam

@@ -3,20 +3,12 @@ App(
     name="Cross Remote",
     name="Cross Remote",
     apptype=FlipperAppType.EXTERNAL,
     apptype=FlipperAppType.EXTERNAL,
     entry_point="xremote_app",
     entry_point="xremote_app",
-    cdefines=["APP_XREMOTE"],
-    requires=[
-        "gui",
-        "storage",
-        "dialogs",
-    ],
     stack_size=3 * 1024,
     stack_size=3 * 1024,
-    order=10,
-    fap_libs=["assets"],
     fap_icon="icons/xremote_10px.png",
     fap_icon="icons/xremote_10px.png",
     fap_icon_assets="icons",
     fap_icon_assets="icons",
-    fap_version="1.1",
+    fap_version="2.0",
     fap_category="Infrared",
     fap_category="Infrared",
     fap_author="Leedave",
     fap_author="Leedave",
     fap_description="One-Click, sends multiple commands",
     fap_description="One-Click, sends multiple commands",
-    fap_weburl="https://github.com/leedave/Leeds-Flipper-Zero-Applications"
+    fap_weburl="https://github.com/leedave/flipper-zero-cross-remote"
 )
 )

+ 2 - 4
docs/README.md

@@ -1,18 +1,16 @@
 ## Cross Remote
 ## Cross Remote
 
 
-This app combines your IR commands into a playlist that can be run with one click. Pauses can be set inbetween, as IR devices typically are not so fast. 
+This app combines your IR and SubGhz commands into a playlist that can be run with one click. Pauses can be set inbetween, as IR/SubGhz devices typically are not so fast. 
 
 
 ## Features
 ## Features
 - Read out commands you recorded in the IR app
 - Read out commands you recorded in the IR app
+- Read out commands you saved as .sub files
 - Combine commands to a chain/playlist 
 - Combine commands to a chain/playlist 
 - Add pauses inbetween commands 
 - Add pauses inbetween commands 
 - Save your command chain / playlist to flipper
 - Save your command chain / playlist to flipper
 - Disable LED effects if not wanted
 - Disable LED effects if not wanted
 - Configure duration of IR Signals
 - Configure duration of IR Signals
 
 
-## In Development
-- Ability to also add SubGhz commands 
-
 ## What good is this?
 ## What good is this?
 
 
 Example what you command chain (playlist) could do with one click
 Example what you command chain (playlist) could do with one click

+ 9 - 1
docs/changelog.md

@@ -1,5 +1,13 @@
+## v2.0
+- SubGhz Functionality added
+- Slight change in Storage format to enalbe individual IR timings later (feature request)
+- Fixed more memory leaks
+- Fixed error where Flipper crashes after deleting a command chain
+- Updated Manifest for better CFW support
+- Updated readme for new inputs
+
 ## v1.1
 ## v1.1
-- Patched Memory Leak in storage
+- Patched memory leak in storage
 
 
 ## v1.0
 ## v1.0
 - Added option to change IR Signal Timing
 - Added option to change IR Signal Timing

+ 53 - 0
helpers/subghz/subghz.c

@@ -0,0 +1,53 @@
+/* Reduced variant of the Flipper Zero SubGhz Class */
+
+#include "subghz_i.h"
+#include "../../helpers/xremote_custom_event.h"
+#include "../../helpers/xremote_led.h"
+
+SubGhz* subghz_alloc() {
+    SubGhz* subghz = malloc(sizeof(SubGhz));
+
+    subghz->file_path = furi_string_alloc();
+    subghz->txrx = subghz_txrx_alloc();
+
+    return subghz;
+}
+
+void subghz_free(SubGhz* subghz) {
+    subghz_txrx_free(subghz->txrx);
+    furi_string_free(subghz->file_path);
+
+    // The rest
+    free(subghz);
+}
+
+void subghz_scene_transmit_callback_end_tx(void* context) {
+    furi_assert(context);
+    FURI_LOG_D(TAG, "callback end");
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, XRemoteCustomEventViewTransmitterSendStop);
+
+    //app->state_notifications = SubGhzNotificationStateIDLE;
+    //subghz_txrx_stop(app->subghz->txrx);
+    //app->transmitting = false;
+    //xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
+    xremote_cross_remote_set_transmitting(app->cross_remote, XRemoteTransmittingStopSubghz);
+}
+
+void subghz_send(void* context, const char* path) {
+    XRemote* app = context;
+
+    subghz_load_protocol_from_file(app->subghz, path);
+    FURI_LOG_D(TAG, "Starting Transmission");
+    subghz_txrx_tx_start(
+        app->subghz->txrx,
+        subghz_txrx_get_fff_data(app->subghz->txrx)); //Seems like it must be done this way
+
+    FURI_LOG_D(TAG, "setting sugbhz raw file encoder worker callback");
+    subghz_txrx_set_raw_file_encoder_worker_callback_end(
+        app->subghz->txrx, subghz_scene_transmit_callback_end_tx, app);
+    app->state_notifications = SubGhzNotificationStateTx;
+
+    FURI_LOG_D(TAG, "Finished Transmitting");
+}

+ 9 - 0
helpers/subghz/subghz.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "subghz_i.h"
+
+typedef struct SubGhz SubGhz;
+
+SubGhz* subghz_alloc();
+void subghz_free(SubGhz* subghz);
+void subghz_send(void* context, const char* path);

+ 14 - 0
helpers/subghz/subghz_error_type.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzErrorType */
+typedef enum {
+    SubGhzErrorTypeNoError = 0, /** There are no errors */
+    SubGhzErrorTypeParseFile =
+        1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */
+    SubGhzErrorTypeOnlyRX =
+        2, /** Transmission on this frequency is blocked by regional settings */
+    SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */
+} SubGhzErrorType;

+ 260 - 0
helpers/subghz/subghz_i.c

@@ -0,0 +1,260 @@
+#include "subghz_i.h"
+
+#include "subghz/types.h"
+#include <math.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <flipper_format/flipper_format.h>
+
+#include <flipper_format/flipper_format_i.h>
+#include <lib/toolbox/stream/stream.h>
+#include <lib/subghz/protocols/raw.h>
+
+//#define TAG "SubGhz"
+/*
+void subghz_set_default_preset(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz_txrx_set_preset(
+        subghz->txrx,
+        "AM650",
+        subghz_setting_get_default_frequency(subghz_txrx_get_setting(subghz->txrx)),
+        NULL,
+        0);
+}*/
+
+/*bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
+    switch(subghz_txrx_tx_start(subghz->txrx, flipper_format)) {
+    case SubGhzTxRxStartTxStateErrorParserOthers:
+        dialog_message_show_storage_error(
+            subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        break;
+    case SubGhzTxRxStartTxStateErrorOnlyRx:
+        FURI_LOG_D(TAG, 'Cannot send, only RX possible');
+        break;
+
+    default:
+        return true;
+        break;
+    }
+    return false;
+}*/
+
+bool subghz_key_load(SubGhz* subghz, const char* file_path) { //, bool show_dialog) {
+    furi_assert(subghz);
+    furi_assert(file_path);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
+    Stream* fff_data_stream =
+        flipper_format_get_raw_stream(subghz_txrx_get_fff_data(subghz->txrx));
+
+    SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t temp_data32;
+
+    do {
+        stream_clean(fff_data_stream);
+        if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
+            FURI_LOG_E(TAG, "Error open file %s", file_path);
+            break;
+        }
+
+        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
+            FURI_LOG_E(TAG, "Missing or incorrect header");
+            break;
+        }
+
+        if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) ||
+            (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) &&
+           temp_data32 == SUBGHZ_KEY_FILE_VERSION) {
+        } else {
+            FURI_LOG_E(TAG, "Type or version mismatch");
+            break;
+        }
+
+        //Load frequency
+        if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing Frequency");
+            break;
+        }
+
+        if(!subghz_txrx_radio_device_is_frequecy_valid(subghz->txrx, temp_data32)) {
+            FURI_LOG_E(TAG, "Frequency not supported");
+            break;
+        }
+
+        //Load preset
+        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Preset");
+            break;
+        }
+
+        furi_string_set_str(
+            temp_str, subghz_txrx_get_preset_name(subghz->txrx, furi_string_get_cstr(temp_str)));
+        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
+            break;
+        }
+        SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+
+        if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) {
+            //TODO FL-3551: add Custom_preset_module
+            //delete preset if it already exists
+            subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str));
+            //load custom preset from file
+            if(!subghz_setting_load_custom_preset(
+                   setting, furi_string_get_cstr(temp_str), fff_data_file)) {
+                FURI_LOG_E(TAG, "Missing Custom preset");
+                break;
+            }
+        }
+        size_t preset_index =
+            subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str));
+        subghz_txrx_set_preset(
+            subghz->txrx,
+            furi_string_get_cstr(temp_str),
+            temp_data32,
+            subghz_setting_get_preset_data(setting, preset_index),
+            subghz_setting_get_preset_data_size(setting, preset_index));
+
+        //Load protocol
+        if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
+        if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
+            //if RAW
+            subghz->load_type_file = SubGhzLoadTypeFileRaw;
+            subghz_protocol_raw_gen_fff_data(
+                fff_data, file_path, subghz_txrx_radio_device_get_name(subghz->txrx));
+        } else {
+            subghz->load_type_file = SubGhzLoadTypeFileKey;
+            stream_copy_full(
+                flipper_format_get_raw_stream(fff_data_file),
+                flipper_format_get_raw_stream(fff_data));
+        }
+
+        if(subghz_txrx_load_decoder_by_name_protocol(
+               subghz->txrx, furi_string_get_cstr(temp_str))) {
+            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
+                subghz_txrx_get_decoder(subghz->txrx), fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
+                break;
+            }
+        } else {
+            FURI_LOG_E(TAG, "Protocol not found");
+            break;
+        }
+
+        load_key_state = SubGhzLoadKeyStateOK;
+    } while(0);
+
+    furi_string_free(temp_str);
+    flipper_format_free(fff_data_file);
+    furi_record_close(RECORD_STORAGE);
+
+    switch(load_key_state) {
+    case SubGhzLoadKeyStateParseErr:
+        //if(show_dialog) {
+        //    dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
+        //}
+        return false;
+    case SubGhzLoadKeyStateProtocolDescriptionErr:
+        //if(show_dialog) {
+        //    dialog_message_show_storage_error(
+        //        subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        //}
+        return false;
+
+    case SubGhzLoadKeyStateOK:
+        return true;
+
+    default:
+        furi_crash("SubGhz: Unknown load_key_state.");
+        return false;
+    }
+    return false;
+}
+
+/*SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->load_type_file;
+}*/
+
+bool subghz_load_protocol_from_file(SubGhz* subghz, const char* path) {
+    furi_assert(subghz);
+
+    //FuriString* file_path = furi_string_alloc();
+
+    bool res = false;
+
+    /*DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
+    browser_options.base_path = SUBGHZ_APP_FOLDER;
+
+    // Input events and views are managed by file_select
+    bool res = dialog_file_browser_show(
+        subghz->dialogs, subghz->file_path, subghz->file_path, &browser_options);
+
+    if(res) {*/
+    //res = subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), true);
+    res = subghz_key_load(subghz, path); //, true);
+    //}
+
+    //furi_string_free(file_path);
+
+    return res;
+}
+
+/*bool subghz_file_available(SubGhz* subghz) {
+    furi_assert(subghz);
+    bool ret = true;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    FS_Error fs_result =
+        storage_common_stat(storage, furi_string_get_cstr(subghz->file_path), NULL);
+
+    if(fs_result != FSE_OK) {
+        dialog_message_show_storage_error(subghz->dialogs, "File not available\n file/directory");
+        ret = false;
+    }
+
+    furi_record_close(RECORD_STORAGE);
+    return ret;
+}*/
+
+/*bool subghz_path_is_file(FuriString* path) {
+    return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION);
+}*/
+
+/*void subghz_lock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOn;
+}*/
+
+/*void subghz_unlock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOff;
+}*/
+
+/*bool subghz_is_locked(SubGhz* subghz) {
+    furi_assert(subghz);
+    return (subghz->lock == SubGhzLockOn);
+}*/
+
+/*void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state) {
+    furi_assert(subghz);
+    subghz->rx_key_state = state;
+}*/
+
+/*SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->rx_key_state;
+}*/

+ 83 - 0
helpers/subghz/subghz_i.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include "subghz_types.h"
+#include "subghz_error_type.h"
+#include <lib/subghz/types.h>
+#include "subghz.h"
+#include "../xremote_storage.h"
+
+/*#include "views/receiver.h"
+#include "views/transmitter.h"
+#include "views/subghz_frequency_analyzer.h"
+#include "views/subghz_read_raw.h"
+
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/widget.h>
+
+#include <subghz/scenes/subghz_scene.h>
+
+#include "subghz_history.h"
+
+#include <gui/modules/variable_item_list.h>
+#include <lib/toolbox/path.h>
+
+#include "rpc/rpc_app.h"
+
+#include "helpers/subghz_threshold_rssi.h"
+
+
+*/
+#include "subghz_txrx.h"
+
+#define SUBGHZ_MAX_LEN_NAME 64
+
+typedef struct SubGhz SubGhz;
+
+struct SubGhz {
+    SubGhzTxRx* txrx;
+    FuriString* file_path;
+    //FuriString* file_path_tmp;
+    //char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; // just left it in to make the object not empty
+    //SubGhzNotificationState state_notifications;
+
+    /*SubGhzViewReceiver* subghz_receiver;
+    SubGhzViewTransmitter* subghz_transmitter;
+
+    SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
+    SubGhzReadRAW* subghz_read_raw;*/
+
+    //SubGhzProtocolFlag filter;
+    //FuriString* error_str;
+    //SubGhzLock lock;
+    //SubGhzThresholdRssi* threshold_rssi;
+    //SubGhzRxKeyState rx_key_state;
+    //SubGhzHistory* history;
+    SubGhzLoadTypeFile load_type_file;
+    //void* rpc_ctx;
+};
+
+//void subghz_set_default_preset(SubGhz* subghz);
+//void subghz_blink_start(SubGhz* subghz);
+//void subghz_blink_stop(SubGhz* subghz);
+
+//bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
+//void subghz_dialog_message_show_only_rx(SubGhz* subghz);
+
+bool subghz_key_load(SubGhz* subghz, const char* file_path); //, bool show_dialog);
+bool subghz_load_protocol_from_file(SubGhz* subghz, const char* path);
+//bool subghz_file_available(SubGhz* subghz);
+//SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz);
+
+//void subghz_lock(SubGhz* subghz);
+//void subghz_unlock(SubGhz* subghz);
+//bool subghz_is_locked(SubGhz* subghz);
+
+//void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state);
+//SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz);

+ 630 - 0
helpers/subghz/subghz_txrx.c

@@ -0,0 +1,630 @@
+#include "subghz_txrx_i.h"
+
+#include <lib/subghz/protocols/protocol_items.h>
+#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
+#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
+
+#define TAG "SubGhz"
+
+static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) {
+    UNUSED(instance);
+    uint8_t attempts = 5;
+    while(--attempts > 0) {
+        if(furi_hal_power_enable_otg()) break;
+    }
+    if(attempts == 0) {
+        if(furi_hal_power_get_usb_voltage() < 4.5f) {
+            FURI_LOG_E(
+                TAG,
+                "Error power otg enable. BQ2589 check otg fault = %d",
+                furi_hal_power_check_otg_fault() ? 1 : 0);
+        }
+    }
+}
+
+static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
+    UNUSED(instance);
+    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
+}
+
+SubGhzTxRx* subghz_txrx_alloc() {
+    SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
+    instance->setting = subghz_setting_alloc();
+    subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
+
+    instance->preset = malloc(sizeof(SubGhzRadioPreset));
+    instance->preset->name = furi_string_alloc();
+    subghz_txrx_set_preset(
+        instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0);
+
+    instance->txrx_state = SubGhzTxRxStateSleep;
+
+    subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
+    subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
+
+    instance->worker = subghz_worker_alloc();
+    instance->fff_data = flipper_format_string_alloc();
+
+    instance->environment = subghz_environment_alloc();
+    instance->is_database_loaded =
+        subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME);
+    subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME);
+    subghz_environment_set_came_atomo_rainbow_table_file_name(
+        instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
+    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
+        instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
+    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
+        instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
+    subghz_environment_set_protocol_registry(
+        instance->environment, (void*)&subghz_protocol_registry);
+    instance->receiver = subghz_receiver_alloc_init(instance->environment);
+
+    subghz_worker_set_overrun_callback(
+        instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
+    subghz_worker_set_pair_callback(
+        instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
+    subghz_worker_set_context(instance->worker, instance->receiver);
+
+    //set default device External
+    subghz_devices_init();
+    instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
+    instance->radio_device_type =
+        subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
+
+    FURI_LOG_D(TAG, "completed TXRX alloc");
+
+    return instance;
+}
+
+void subghz_txrx_free(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    FURI_LOG_D(TAG, "freeing TXRX");
+
+    if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
+        subghz_txrx_radio_device_power_off(instance);
+        subghz_devices_end(instance->radio_device);
+    }
+
+    subghz_devices_deinit();
+
+    subghz_worker_free(instance->worker);
+    subghz_receiver_free(instance->receiver);
+    subghz_environment_free(instance->environment);
+    flipper_format_free(instance->fff_data);
+    furi_string_free(instance->preset->name);
+    subghz_setting_free(instance->setting);
+
+    free(instance->preset);
+    free(instance);
+}
+
+/*bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->is_database_loaded;
+}*/
+
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size) {
+    furi_assert(instance);
+    furi_string_set(instance->preset->name, preset_name);
+    SubGhzRadioPreset* preset = instance->preset;
+    preset->frequency = frequency;
+    preset->data = preset_data;
+    preset->data_size = preset_data_size;
+}
+
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) {
+    UNUSED(instance);
+    const char* preset_name = "";
+    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
+        preset_name = "AM270";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
+        preset_name = "AM650";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
+        preset_name = "FM238";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
+        preset_name = "FM476";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
+        preset_name = "CUSTOM";
+    } else {
+        FURI_LOG_E(TAG, "Unknown preset");
+    }
+    return preset_name;
+}
+
+/*SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return *instance->preset;
+}*/
+
+/*void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation) {
+    furi_assert(instance);
+    SubGhzRadioPreset* preset = instance->preset;
+    if(frequency != NULL) {
+        furi_string_printf(
+            frequency,
+            "%03ld.%02ld",
+            preset->frequency / 1000000 % 1000,
+            preset->frequency / 10000 % 100);
+    }
+    if(modulation != NULL) {
+        furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name));
+    }
+}*/
+
+static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) {
+    furi_assert(instance);
+    subghz_devices_reset(instance->radio_device);
+    subghz_devices_idle(instance->radio_device);
+    subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+    FURI_LOG_D(TAG, "completed subghz_txrx_begin");
+}
+
+/*static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+
+    furi_assert(
+        instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep);
+
+    subghz_devices_idle(instance->radio_device);
+
+    uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency);
+    subghz_devices_flush_rx(instance->radio_device);
+    subghz_txrx_speaker_on(instance);
+
+    subghz_devices_start_async_rx(
+        instance->radio_device, subghz_worker_rx_callback, instance->worker);
+    subghz_worker_start(instance->worker);
+    instance->txrx_state = SubGhzTxRxStateRx;
+    return value;
+}*/
+
+static void subghz_txrx_idle(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->txrx_state != SubGhzTxRxStateSleep) {
+        subghz_devices_idle(instance->radio_device);
+        subghz_txrx_speaker_off(instance);
+        instance->txrx_state = SubGhzTxRxStateIDLE;
+    }
+    FURI_LOG_D(TAG, "completed subghz_txrx_idle");
+}
+
+/*static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateRx);
+
+    if(subghz_worker_is_running(instance->worker)) {
+        subghz_worker_stop(instance->worker);
+        subghz_devices_stop_async_rx(instance->radio_device);
+    }
+    subghz_devices_idle(instance->radio_device);
+    subghz_txrx_speaker_off(instance);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+}*/
+
+/*void subghz_txrx_sleep(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_devices_sleep(instance->radio_device);
+    instance->txrx_state = SubGhzTxRxStateSleep;
+}*/
+
+static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
+    subghz_devices_idle(instance->radio_device);
+    subghz_devices_set_frequency(instance->radio_device, frequency);
+
+    bool ret = subghz_devices_set_tx(instance->radio_device);
+    if(ret) {
+        subghz_txrx_speaker_on(instance);
+        instance->txrx_state = SubGhzTxRxStateTx;
+    }
+
+    FURI_LOG_D(TAG, "completed subghz_txrx_tx");
+    return ret;
+}
+
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) {
+    furi_assert(instance);
+    furi_assert(flipper_format);
+
+    subghz_txrx_stop(instance);
+
+    SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t repeat = 200;
+
+    FURI_LOG_D(TAG, "starting loop in subghz_txrx_tx_start");
+    do {
+        FURI_LOG_D(TAG, "looping");
+        if(!flipper_format_rewind(flipper_format)) {
+            FURI_LOG_E(TAG, "Rewind error");
+            break;
+        }
+        if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+        if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
+            FURI_LOG_E(TAG, "Unable Repeat");
+            break;
+        }
+        //FURI_LOG_D(TAG, "File loaded");
+        ret = SubGhzTxRxStartTxStateOk;
+
+        SubGhzRadioPreset* preset = instance->preset;
+        instance->transmitter =
+            subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
+
+        if(instance->transmitter) {
+            FURI_LOG_D(TAG, "transmitter found");
+            if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
+               SubGhzProtocolStatusOk) {
+                //if (false) {
+                FURI_LOG_D(TAG, "deserialization");
+                if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
+                    FURI_LOG_D(TAG, "got preset name");
+                    subghz_txrx_begin(
+                        instance,
+                        subghz_setting_get_preset_data_by_name(
+                            instance->setting, furi_string_get_cstr(preset->name)));
+                    FURI_LOG_D(TAG, "loaded preset by name");
+                    if(preset->frequency) {
+                        if(!subghz_txrx_tx(instance, preset->frequency)) {
+                            FURI_LOG_E(TAG, "Only Rx");
+                            ret = SubGhzTxRxStartTxStateErrorOnlyRx;
+                        }
+                        FURI_LOG_D(TAG, "got frequency");
+                    } else {
+                        ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                    }
+
+                } else {
+                    FURI_LOG_E(
+                        TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name));
+                    ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                }
+
+                if(ret == SubGhzTxRxStartTxStateOk) {
+                    //Start TX
+                    FURI_LOG_D(TAG, "starting Async TX");
+                    subghz_devices_start_async_tx(
+                        instance->radio_device, subghz_transmitter_yield, instance->transmitter);
+                }
+            } else {
+                FURI_LOG_D(TAG, "no deserialization");
+                ret = SubGhzTxRxStartTxStateErrorParserOthers;
+            }
+        } else {
+            ret = SubGhzTxRxStartTxStateErrorParserOthers;
+        }
+        if(ret != SubGhzTxRxStartTxStateOk) {
+            FURI_LOG_D(TAG, "state not ok");
+            subghz_transmitter_free(instance->transmitter); // Crashes here
+            if(instance->txrx_state != SubGhzTxRxStateIDLE) {
+                subghz_txrx_idle(instance);
+            }
+        }
+    } while(false);
+    furi_string_free(temp_str);
+    return ret;
+}
+
+/*void subghz_txrx_rx_start(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_txrx_stop(instance);
+    subghz_txrx_begin(
+        instance,
+        subghz_setting_get_preset_data_by_name(
+            subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name)));
+    subghz_txrx_rx(instance, instance->preset->frequency);
+}*/
+
+/*void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->need_save_callback = callback;
+    instance->need_save_context = context;
+}*/
+
+static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateTx);
+    //Stop TX
+    subghz_devices_stop_async_tx(instance->radio_device);
+    subghz_transmitter_stop(instance->transmitter);
+    subghz_transmitter_free(instance->transmitter);
+
+    //if protocol dynamic then we save the last upload
+    /*if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+        if(instance->need_save_callback) {
+            instance->need_save_callback(instance->need_save_context);
+        }
+    }*/
+    subghz_txrx_idle(instance);
+    subghz_txrx_speaker_off(instance);
+}
+
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->fff_data;
+}
+
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->setting;
+}
+
+void subghz_txrx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->txrx_state) {
+    case SubGhzTxRxStateTx:
+        subghz_txrx_tx_stop(instance);
+        subghz_txrx_speaker_unmute(instance);
+        break;
+    case SubGhzTxRxStateRx:
+        //subghz_txrx_rx_end(instance);
+        //subghz_txrx_speaker_mute(instance);
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*void subghz_txrx_hopper_update(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->hopper_state) {
+    case SubGhzHopperStateOFF:
+    case SubGhzHopperStatePause:
+        return;
+    case SubGhzHopperStateRSSITimeOut:
+        if(instance->hopper_timeout != 0) {
+            instance->hopper_timeout--;
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+    float rssi = -127.0f;
+    if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) {
+        // See RSSI Calculation timings in CC1101 17.3 RSSI
+        rssi = subghz_devices_get_rssi(instance->radio_device);
+
+        // Stay if RSSI is high enough
+        if(rssi > -90.0f) {
+            instance->hopper_timeout = 10;
+            instance->hopper_state = SubGhzHopperStateRSSITimeOut;
+            return;
+        }
+    } else {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+    // Select next frequency
+    if(instance->hopper_idx_frequency <
+       subghz_setting_get_hopper_frequency_count(instance->setting) - 1) {
+        instance->hopper_idx_frequency++;
+    } else {
+        instance->hopper_idx_frequency = 0;
+    }
+
+    if(instance->txrx_state == SubGhzTxRxStateRx) {
+        subghz_txrx_rx_end(instance);
+    };
+    if(instance->txrx_state == SubGhzTxRxStateIDLE) {
+        subghz_receiver_reset(instance->receiver);
+        instance->preset->frequency =
+            subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency);
+        subghz_txrx_rx(instance, instance->preset->frequency);
+    }
+}*/
+
+/*SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->hopper_state;
+}*/
+
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) {
+    furi_assert(instance);
+    instance->hopper_state = state;
+}
+
+/*void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStatePause) {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+}*/
+
+/*void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStateRunnig) {
+        instance->hopper_state = SubGhzHopperStatePause;
+    }
+}*/
+
+void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_acquire(30)) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
+        } else {
+            instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_txrx_speaker_off(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state != SubGhzSpeakerStateDisable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
+            furi_hal_speaker_release();
+            if(instance->speaker_state == SubGhzSpeakerStateShutdown)
+                instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+/*void subghz_txrx_speaker_mute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
+        }
+    }
+}*/
+
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
+        }
+    }
+}
+
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) {
+    furi_assert(instance);
+    instance->speaker_state = state;
+}
+
+/*SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->speaker_state;
+}*/
+
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) {
+    furi_assert(instance);
+    furi_assert(name_protocol);
+    bool res = false;
+    instance->decoder_result =
+        subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol);
+    if(instance->decoder_result) {
+        res = true;
+    }
+    return res;
+}
+
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->decoder_result;
+}
+
+/*bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return (
+        (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
+        SubGhzProtocolFlag_Save);
+}*/
+
+/*bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) {
+    furi_assert(instance);
+    const SubGhzProtocol* protocol = instance->decoder_result->protocol;
+    if(check_type) {
+        return (
+            ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+            protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic);
+    }
+    return (
+        ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+        protocol->encoder->deserialize);
+}*/
+
+/*void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) {
+    furi_assert(instance);
+    subghz_receiver_set_filter(instance->receiver, filter);
+}*/
+
+/*void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context) {
+    subghz_receiver_set_rx_callback(instance->receiver, callback, context);
+}*/
+
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context) {
+    subghz_protocol_raw_file_encoder_worker_set_callback_end(
+        (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter),
+        callback,
+        context);
+}
+
+bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) {
+    furi_assert(instance);
+
+    bool is_connect = false;
+    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
+
+    if(!is_otg_enabled) {
+        subghz_txrx_radio_device_power_on(instance);
+    }
+
+    const SubGhzDevice* device = subghz_devices_get_by_name(name);
+    if(device) {
+        is_connect = subghz_devices_is_connect(device);
+    }
+
+    if(!is_otg_enabled) {
+        subghz_txrx_radio_device_power_off(instance);
+    }
+    return is_connect;
+}
+
+SubGhzRadioDeviceType
+    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) {
+    furi_assert(instance);
+
+    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
+       subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
+        subghz_txrx_radio_device_power_on(instance);
+        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
+        subghz_devices_begin(instance->radio_device);
+        instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101;
+    } else {
+        subghz_txrx_radio_device_power_off(instance);
+        if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
+            subghz_devices_end(instance->radio_device);
+        }
+        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+        instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
+    }
+
+    return instance->radio_device_type;
+}
+
+/*SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->radio_device_type;
+}*/
+
+/*float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_rssi(instance->radio_device);
+}*/
+
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_name(instance->radio_device);
+}
+
+bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    return subghz_devices_is_frequency_valid(instance->radio_device, frequency);
+}

+ 336 - 0
helpers/subghz/subghz_txrx.h

@@ -0,0 +1,336 @@
+#pragma once
+
+#include "subghz_types.h"
+
+#include <lib/subghz/subghz_worker.h>
+#include <lib/subghz/subghz_setting.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/protocols/raw.h>
+#include <lib/subghz/devices/devices.h>
+
+typedef struct SubGhzTxRx SubGhzTxRx;
+
+typedef void (*SubGhzTxRxNeedSaveCallback)(void* context);
+
+typedef enum {
+    SubGhzTxRxStartTxStateOk,
+    SubGhzTxRxStartTxStateErrorOnlyRx,
+    SubGhzTxRxStartTxStateErrorParserOthers,
+} SubGhzTxRxStartTxState;
+
+/**
+ * Allocate SubGhzTxRx
+ * 
+ * @return SubGhzTxRx* pointer to SubGhzTxRx
+ */
+SubGhzTxRx* subghz_txrx_alloc();
+
+/**
+ * Free SubGhzTxRx
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_free(SubGhzTxRx* instance);
+
+/**
+ * Check if the database is loaded
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return bool True if the database is loaded
+ */
+//bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance);
+
+/**
+ * Set preset 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset_name Name of preset
+ * @param frequency Frequency in Hz
+ * @param preset_data Data of preset
+ * @param preset_data_size Size of preset data
+ */
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size);
+
+/**
+ * Get name of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset String of preset 
+ * @return const char*  Name of preset
+ */
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset);
+
+/**
+ * Get of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzRadioPreset Preset
+ */
+//SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance);
+
+/**
+ * Get string frequency and modulation
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param frequency Pointer to a string frequency
+ * @param modulation Pointer to a string modulation
+ */
+/*void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation);*/
+
+/**
+ * Start TX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param flipper_format Pointer to a FlipperFormat
+ * @return SubGhzTxRxStartTxState 
+ */
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format);
+
+/**
+ * Start RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_rx_start(SubGhzTxRx* instance);
+
+/**
+ * Stop TX/RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_stop(SubGhzTxRx* instance);
+
+/**
+ * Set sleep mode CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_sleep(SubGhzTxRx* instance);
+
+/**
+ * Update frequency CC1101 in automatic mode (hopper)
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_update(SubGhzTxRx* instance);
+
+/**
+ * Get state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzHopperState 
+ */
+//SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance);
+
+/**
+ * Set state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param state State hopper
+ */
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state);
+
+/**
+ * Unpause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
+
+/**
+ * Set pause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
+
+/**
+ * Speaker on
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_on(SubGhzTxRx* instance);
+
+/**
+ * Speaker off
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_off(SubGhzTxRx* instance);
+
+/**
+ * Speaker mute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+//void subghz_txrx_speaker_mute(SubGhzTxRx* instance);
+
+/**
+ * Speaker unmute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance);
+
+/**
+ * Set state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @param state State speaker
+ */
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state);
+
+/**
+ * Get state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return SubGhzSpeakerState 
+ */
+//SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance);
+
+/**
+ * load decoder by name protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_protocol Name protocol
+ * @return bool True if the decoder is loaded 
+ */
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol);
+
+/**
+ * Get decoder
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase
+ */
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
+
+/**
+ * Set callback for save data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for save data
+ * @param context Context for callback
+ */
+/*void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context);*/
+
+/**
+ * Get pointer to a load data key
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return FlipperFormat* 
+ */
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance);
+
+/**
+ * Get pointer to a SugGhzSetting
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzSetting* 
+ */
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to save this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to save this protocol
+ */
+//bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to send this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to send this protocol
+ */
+//bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type);
+
+/**
+ * Set filter, what types of decoder to use 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param filter Filter
+ */
+//void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter);
+
+/**
+ * Set callback for receive data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for receive data
+ * @param context Context for callback
+ */
+/*void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context);*/
+
+/**
+ * Set callback for Raw decoder, end of data transfer  
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for Raw decoder, end of data transfer 
+ * @param context Context for callback
+ */
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context);
+
+/* Checking if an external radio device is connected
+* 
+* @param instance Pointer to a SubGhzTxRx
+* @param name Name of external radio device
+* @return bool True if is connected to the external radio device
+*/
+bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name);
+
+/* Set the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @param radio_device_type Radio device type
+* @return SubGhzRadioDeviceType Type of installed radio device
+*/
+SubGhzRadioDeviceType
+    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type);
+
+/* Get the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return SubGhzRadioDeviceType Type of installed radio device
+*/
+//SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance);
+
+/* Get RSSI the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return float RSSI
+*/
+//float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance);
+
+/* Get name the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return const char* Name of installed radio device
+*/
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance);
+
+/* Get get intelligence whether frequency the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return bool True if the frequency is valid
+*/
+bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency);

+ 29 - 0
helpers/subghz/subghz_txrx_i.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "subghz_txrx.h"
+
+struct SubGhzTxRx {
+    SubGhzWorker* worker;
+
+    SubGhzEnvironment* environment;
+    SubGhzReceiver* receiver;
+    SubGhzTransmitter* transmitter;
+    SubGhzProtocolDecoderBase* decoder_result;
+    FlipperFormat* fff_data;
+
+    SubGhzRadioPreset* preset;
+    SubGhzSetting* setting;
+
+    uint8_t hopper_timeout;
+    uint8_t hopper_idx_frequency;
+    bool is_database_loaded;
+    SubGhzHopperState hopper_state;
+
+    SubGhzTxRxState txrx_state;
+    SubGhzSpeakerState speaker_state;
+    const SubGhzDevice* radio_device;
+    SubGhzRadioDeviceType radio_device_type;
+
+    SubGhzTxRxNeedSaveCallback need_save_callback;
+    void* need_save_context;
+};

+ 12 - 14
models/subghz/subghz_types.h → helpers/subghz/subghz_types.h

@@ -35,6 +35,13 @@ typedef enum {
     SubGhzSpeakerStateEnable,
     SubGhzSpeakerStateEnable,
 } SubGhzSpeakerState;
 } SubGhzSpeakerState;
 
 
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeAuto,
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
 /** SubGhzRxKeyState state */
 /** SubGhzRxKeyState state */
 typedef enum {
 typedef enum {
     SubGhzRxKeyStateIDLE,
     SubGhzRxKeyStateIDLE,
@@ -62,18 +69,9 @@ typedef enum {
     SubGhzLockOn,
     SubGhzLockOn,
 } SubGhzLock;
 } SubGhzLock;
 
 
+/** SubGhz load type file */
 typedef enum {
 typedef enum {
-    SubGhzViewIdMenu,
-    SubGhzViewIdReceiver,
-    SubGhzViewIdPopup,
-    SubGhzViewIdTextInput,
-    SubGhzViewIdWidget,
-    SubGhzViewIdTransmitter,
-    SubGhzViewIdVariableItemList,
-    SubGhzViewIdFrequencyAnalyzer,
-    SubGhzViewIdReadRAW,
-
-    SubGhzViewIdStatic,
-    SubGhzViewIdTestCarrier,
-    SubGhzViewIdTestPacket,
-} SubGhzViewId;
+    SubGhzLoadTypeFileNoLoad,
+    SubGhzLoadTypeFileKey,
+    SubGhzLoadTypeFileRaw,
+} SubGhzLoadTypeFile;

+ 2 - 0
helpers/xremote_custom_event.h

@@ -50,6 +50,8 @@ typedef enum {
     XRemoteCustomEventPauseSetUp,
     XRemoteCustomEventPauseSetUp,
     XRemoteCustomEventPauseSetDown,
     XRemoteCustomEventPauseSetDown,
     XRemoteCustomEventPauseSetOk,
     XRemoteCustomEventPauseSetOk,
+
+    XRemoteCustomEventViewTransmitterSendStop,
 } XRemoteCustomEvent;
 } XRemoteCustomEvent;
 
 
 static inline uint32_t xremote_custom_menu_event_pack(uint16_t type, int16_t value) {
 static inline uint32_t xremote_custom_menu_event_pack(uint16_t type, int16_t value) {

+ 271 - 262
models/cross/xremote_remote.c → models/cross/xremote_cross_remote.c

@@ -1,262 +1,271 @@
-#include "xremote_remote.h"
-
-ARRAY_DEF(CrossRemoteItemArray, CrossRemoteItem*, M_PTR_OPLIST);
-
-struct CrossRemote {
-    FuriString* name;
-    FuriString* path;
-    CrossRemoteItemArray_t items;
-    int transmitting;
-};
-
-static void cross_remote_clear_items(CrossRemote* remote) {
-    CrossRemoteItemArray_it_t it;
-    for(CrossRemoteItemArray_it(it, remote->items); !CrossRemoteItemArray_end_p(it);
-        CrossRemoteItemArray_next(it)) {
-        xremote_remote_item_free(*CrossRemoteItemArray_cref(it));
-    }
-    CrossRemoteItemArray_reset(remote->items);
-}
-
-static void cross_remote_find_vacant_remote_name(FuriString* name, const char* path) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-
-    FuriString* base_path;
-    base_path = furi_string_alloc_set(path);
-
-    if(furi_string_end_with(base_path, XREMOTE_APP_EXTENSION)) {
-        size_t filename_start = furi_string_search_rchar(base_path, '/');
-        furi_string_left(base_path, filename_start);
-    }
-
-    furi_string_printf(
-        base_path, "%s/%s%s", path, furi_string_get_cstr(name), XREMOTE_APP_EXTENSION);
-
-    FS_Error status = storage_common_stat(storage, furi_string_get_cstr(base_path), NULL);
-
-    if(status == FSE_OK) {
-        // If name is taken, try another name2, name3 etc
-        size_t dot = furi_string_search_rchar(base_path, '.');
-        furi_string_left(base_path, dot);
-
-        FuriString* path_temp;
-        path_temp = furi_string_alloc();
-
-        uint32_t i = 1;
-        do {
-            furi_string_printf(
-                path_temp, "%s%lu%s", furi_string_get_cstr(base_path), ++i, XREMOTE_APP_EXTENSION);
-            status = storage_common_stat(storage, furi_string_get_cstr(path_temp), NULL);
-        } while(status == FSE_OK);
-
-        furi_string_free(path_temp);
-
-        if(status == FSE_NOT_EXIST) {
-            furi_string_cat_printf(name, "%lu", i);
-        }
-    }
-
-    furi_string_free(base_path);
-    furi_record_close(RECORD_STORAGE);
-}
-
-CrossRemote* cross_remote_alloc() {
-    CrossRemote* remote = malloc(sizeof(CrossRemote));
-    CrossRemoteItemArray_init(remote->items);
-    remote->name = furi_string_alloc();
-    remote->path = furi_string_alloc();
-    remote->transmitting = 0;
-    return remote;
-}
-
-void cross_remote_set_transmitting(CrossRemote* remote, int status) {
-    remote->transmitting = status;
-}
-
-int cross_remote_get_transmitting(CrossRemote* remote) {
-    return remote->transmitting;
-}
-
-void cross_remote_free(CrossRemote* remote) {
-    furi_string_free(remote->name);
-    furi_string_free(remote->path);
-    cross_remote_clear_items(remote);
-    CrossRemoteItemArray_clear(remote->items);
-    free(remote);
-}
-
-const char* cross_remote_get_name(CrossRemote* remote) {
-    return furi_string_get_cstr(remote->name);
-}
-
-bool cross_remote_add_ir_item(CrossRemote* remote, const char* name, InfraredSignal* signal) {
-    CrossRemoteItem* item = xremote_remote_item_alloc();
-    xremote_remote_item_set_type(item, XRemoteRemoteItemTypeInfrared);
-    xremote_remote_item_set_name(item, name);
-    xremote_remote_item_set_ir_signal(item, signal);
-    CrossRemoteItemArray_push_back(remote->items, item);
-    return true;
-}
-
-bool cross_remote_add_pause(CrossRemote* remote, int time) {
-    CrossRemoteItem* item = xremote_remote_item_alloc();
-    xremote_remote_item_set_type(item, XRemoteRemoteItemTypePause);
-    char name[9];
-    snprintf(name, 9, CROSS_REMOTE_PAUSE_NAME, time);
-    xremote_remote_item_set_name(item, name);
-    xremote_remote_item_set_time(item, time);
-    CrossRemoteItemArray_push_back(remote->items, item);
-    return true;
-}
-
-bool cross_remote_add_subghz(CrossRemote* remote, SubGhzRemote* subghz) {
-    UNUSED(subghz);
-    CrossRemoteItem* item = xremote_remote_item_alloc();
-    xremote_remote_item_set_type(item, XRemoteRemoteItemTypeSubGhz);
-    xremote_remote_item_set_name(item, xremote_sg_remote_get_name(subghz));
-    xremote_remote_item_set_sg_signal(item, subghz);
-
-    CrossRemoteItemArray_push_back(remote->items, item);
-    return true;
-}
-
-size_t cross_remote_get_item_count(CrossRemote* remote) {
-    return CrossRemoteItemArray_size(remote->items);
-}
-
-CrossRemoteItem* cross_remote_get_item(CrossRemote* remote, size_t index) {
-    furi_assert(index < CrossRemoteItemArray_size(remote->items));
-    return *CrossRemoteItemArray_get(remote->items, index);
-}
-
-void cross_remote_remove_item(CrossRemote* remote, size_t index) {
-    CrossRemoteItemArray_erase(remote->items, index);
-}
-
-void cross_remote_rename_item(CrossRemote* remote, size_t index, const char* name) {
-    CrossRemoteItem* item = cross_remote_get_item(remote, index);
-    xremote_remote_item_set_name(item, name);
-}
-
-bool cross_remote_load(CrossRemote* remote, FuriString* path) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
-    FuriString* buf;
-    buf = furi_string_alloc();
-
-    FURI_LOG_I(TAG, "loading file: \'%s\'", furi_string_get_cstr(path));
-    bool success = false;
-    do {
-        // File not found
-        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
-        uint32_t version;
-        // Read Version & Type
-        if(!flipper_format_read_header(ff, buf, &version)) break;
-        if(!furi_string_equal(buf, XREMOTE_FILE_TYPE) || (version != XREMOTE_FILE_VERSION)) break;
-
-        // Init Remote
-        path_extract_filename(path, buf, true);
-        cross_remote_clear_items(remote);
-        cross_remote_set_name(remote, furi_string_get_cstr(buf));
-        cross_remote_set_path(remote, furi_string_get_cstr(path));
-        // Load Items
-        for(bool can_read = true; can_read;) {
-            CrossRemoteItem* item = xremote_remote_item_alloc();
-            can_read = xremote_remote_item_read(item, ff);
-            if(can_read) {
-                CrossRemoteItemArray_push_back(remote->items, item);
-            } else {
-                xremote_remote_item_free(item);
-            }
-        }
-        success = true;
-    } while(false);
-
-    furi_string_free(buf);
-    flipper_format_free(ff);
-    furi_record_close(RECORD_STORAGE);
-    return success;
-}
-
-void cross_remote_set_name(CrossRemote* remote, const char* name) {
-    furi_string_set(remote->name, name);
-}
-
-void cross_remote_set_path(CrossRemote* remote, const char* path) {
-    furi_string_set(remote->path, path);
-}
-
-bool cross_remote_save_new(CrossRemote* remote, const char* name) {
-    FuriString *new_name, *new_path;
-    new_name = furi_string_alloc_set(name);
-    new_path = furi_string_alloc_set(XREMOTE_APP_FOLDER);
-
-    cross_remote_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path));
-    furi_string_cat_printf(
-        new_path, "/%s%s", furi_string_get_cstr(new_name), XREMOTE_APP_EXTENSION);
-
-    cross_remote_set_name(remote, furi_string_get_cstr(new_name));
-    cross_remote_set_path(remote, furi_string_get_cstr(new_path));
-
-    furi_string_free(new_name);
-    furi_string_free(new_path);
-    return cross_remote_store(remote);
-}
-
-void cross_remote_reset(CrossRemote* remote) {
-    furi_string_reset(remote->name);
-    furi_string_reset(remote->path);
-    CrossRemoteItemArray_clear(remote->items);
-    remote->transmitting = 0;
-}
-
-bool cross_remote_delete(CrossRemote* remote) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path));
-
-    cross_remote_reset(remote);
-
-    furi_record_close(RECORD_STORAGE);
-    return (status == FSE_OK || status == FSE_NOT_EXIST);
-}
-
-bool cross_remote_store(CrossRemote* remote) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* ff = flipper_format_file_alloc(storage);
-    const char* path = furi_string_get_cstr(remote->path);
-
-    FURI_LOG_I(TAG, "Storing file: \'%s\'", path);
-
-    bool success = flipper_format_file_open_always(ff, path) &&
-                   flipper_format_write_header_cstr(ff, XREMOTE_FILE_TYPE, XREMOTE_FILE_VERSION);
-
-    // save Items
-    if(success) {
-        CrossRemoteItemArray_it_t it;
-        for(CrossRemoteItemArray_it(it, remote->items); !CrossRemoteItemArray_end_p(it);
-            CrossRemoteItemArray_next(it)) {
-            CrossRemoteItem* item = *CrossRemoteItemArray_cref(it);
-            success = false;
-            if(item->type == XRemoteRemoteItemTypeInfrared) {
-                success = xremote_ir_signal_save(
-                    xremote_remote_item_get_ir_signal(item),
-                    ff,
-                    xremote_remote_item_get_name(item));
-            } else if(item->type == XRemoteRemoteItemTypePause) {
-                success = xremote_pause_save(ff, item->time, xremote_remote_item_get_name(item));
-            } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
-                success = xremote_sg_signal_save(
-                    xremote_remote_item_get_sg_signal(item),
-                    ff,
-                    xremote_remote_item_get_name(item));
-            }
-            if(!success) {
-                break;
-            }
-        }
-    }
-
-    flipper_format_free(ff);
-    furi_record_close(RECORD_STORAGE);
-    return success;
-}
+#include "xremote_cross_remote.h"
+
+ARRAY_DEF(CrossRemoteItemArray, CrossRemoteItem*, M_PTR_OPLIST);
+
+struct CrossRemote {
+    FuriString* name;
+    FuriString* path;
+    CrossRemoteItemArray_t items;
+    int transmitting;
+};
+
+static void xremote_cross_remote_clear_items(CrossRemote* remote) {
+    CrossRemoteItemArray_it_t it;
+    for(CrossRemoteItemArray_it(it, remote->items); !CrossRemoteItemArray_end_p(it);
+        CrossRemoteItemArray_next(it)) {
+        xremote_cross_remote_item_free(*CrossRemoteItemArray_cref(it));
+    }
+    CrossRemoteItemArray_reset(remote->items);
+}
+
+static void xremote_cross_remote_find_vacant_remote_name(FuriString* name, const char* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    FuriString* base_path;
+    base_path = furi_string_alloc_set(path);
+
+    if(furi_string_end_with(base_path, XREMOTE_APP_EXTENSION)) {
+        size_t filename_start = furi_string_search_rchar(base_path, '/');
+        furi_string_left(base_path, filename_start);
+    }
+
+    furi_string_printf(
+        base_path, "%s/%s%s", path, furi_string_get_cstr(name), XREMOTE_APP_EXTENSION);
+
+    FS_Error status = storage_common_stat(storage, furi_string_get_cstr(base_path), NULL);
+
+    if(status == FSE_OK) {
+        // If name is taken, try another name2, name3 etc
+        size_t dot = furi_string_search_rchar(base_path, '.');
+        furi_string_left(base_path, dot);
+
+        FuriString* path_temp;
+        path_temp = furi_string_alloc();
+
+        uint32_t i = 1;
+        do {
+            furi_string_printf(
+                path_temp, "%s%lu%s", furi_string_get_cstr(base_path), ++i, XREMOTE_APP_EXTENSION);
+            status = storage_common_stat(storage, furi_string_get_cstr(path_temp), NULL);
+        } while(status == FSE_OK);
+
+        furi_string_free(path_temp);
+
+        if(status == FSE_NOT_EXIST) {
+            furi_string_cat_printf(name, "%lu", i);
+        }
+    }
+
+    furi_string_free(base_path);
+    furi_record_close(RECORD_STORAGE);
+}
+
+CrossRemote* xremote_cross_remote_alloc() {
+    CrossRemote* remote = malloc(sizeof(CrossRemote));
+    CrossRemoteItemArray_init(remote->items);
+    remote->name = furi_string_alloc();
+    remote->path = furi_string_alloc();
+    remote->transmitting = 0;
+    return remote;
+}
+
+void xremote_cross_remote_set_transmitting(CrossRemote* remote, int status) {
+    remote->transmitting = status;
+}
+
+int xremote_cross_remote_get_transmitting(CrossRemote* remote) {
+    return remote->transmitting;
+}
+
+void xremote_cross_remote_free(CrossRemote* remote) {
+    furi_string_free(remote->name);
+    furi_string_free(remote->path);
+    xremote_cross_remote_clear_items(remote);
+    CrossRemoteItemArray_clear(remote->items);
+    free(remote);
+}
+
+const char* xremote_cross_remote_get_name(CrossRemote* remote) {
+    return furi_string_get_cstr(remote->name);
+}
+
+bool xremote_cross_remote_add_ir_item(
+    CrossRemote* remote,
+    const char* name,
+    InfraredSignal* signal,
+    uint32_t timing) {
+    CrossRemoteItem* item = xremote_cross_remote_item_alloc();
+    xremote_cross_remote_item_set_type(item, XRemoteRemoteItemTypeInfrared);
+    xremote_cross_remote_item_set_name(item, name);
+    xremote_cross_remote_item_set_time(item, timing);
+    xremote_cross_remote_item_set_ir_signal(item, signal);
+    CrossRemoteItemArray_push_back(remote->items, item);
+    return true;
+}
+
+bool xremote_cross_remote_add_pause(CrossRemote* remote, int time) {
+    CrossRemoteItem* item = xremote_cross_remote_item_alloc();
+    xremote_cross_remote_item_set_type(item, XRemoteRemoteItemTypePause);
+    char name[9];
+    snprintf(name, 9, CROSS_REMOTE_PAUSE_NAME, time);
+    xremote_cross_remote_item_set_name(item, name);
+    xremote_cross_remote_item_set_time(item, time);
+    CrossRemoteItemArray_push_back(remote->items, item);
+    return true;
+}
+
+bool xremote_cross_remote_add_subghz(CrossRemote* remote, SubGhzRemote* subghz) {
+    CrossRemoteItem* item = xremote_cross_remote_item_alloc();
+    xremote_cross_remote_item_set_type(item, XRemoteRemoteItemTypeSubGhz);
+    xremote_cross_remote_item_set_name(item, xremote_sg_remote_get_name(subghz));
+    xremote_cross_remote_item_set_filename(item, xremote_sg_remote_get_filename(subghz));
+    FURI_LOG_D(TAG, "add subghz: %s", xremote_sg_remote_get_filename(subghz));
+    xremote_cross_remote_item_set_sg_signal(item, subghz);
+
+    CrossRemoteItemArray_push_back(remote->items, item);
+    return true;
+}
+
+size_t xremote_cross_remote_get_item_count(CrossRemote* remote) {
+    return CrossRemoteItemArray_size(remote->items);
+}
+
+CrossRemoteItem* xremote_cross_remote_get_item(CrossRemote* remote, size_t index) {
+    furi_assert(index < CrossRemoteItemArray_size(remote->items));
+    return *CrossRemoteItemArray_get(remote->items, index);
+}
+
+void xremote_cross_remote_remove_item(CrossRemote* remote, size_t index) {
+    CrossRemoteItemArray_erase(remote->items, index);
+}
+
+void xremote_cross_remote_rename_item(CrossRemote* remote, size_t index, const char* name) {
+    CrossRemoteItem* item = xremote_cross_remote_get_item(remote, index);
+    xremote_cross_remote_item_set_name(item, name);
+}
+
+static void xremote_cross_remote_set_name(CrossRemote* remote, const char* name) {
+    furi_string_set(remote->name, name);
+}
+
+static void xremote_cross_remote_set_path(CrossRemote* remote, const char* path) {
+    furi_string_set(remote->path, path);
+}
+
+bool xremote_cross_remote_load(CrossRemote* remote, FuriString* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
+    FuriString* buf;
+    buf = furi_string_alloc();
+
+    FURI_LOG_I(TAG, "loading file: \'%s\'", furi_string_get_cstr(path));
+    bool success = false;
+    do {
+        // File not found
+        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
+        uint32_t version;
+        // Read Version & Type
+        if(!flipper_format_read_header(ff, buf, &version)) break;
+        if(!furi_string_equal(buf, XREMOTE_FILE_TYPE) || (version != XREMOTE_FILE_VERSION)) break;
+
+        // Init Remote
+        path_extract_filename(path, buf, true);
+        xremote_cross_remote_clear_items(remote);
+        xremote_cross_remote_set_name(remote, furi_string_get_cstr(buf));
+        xremote_cross_remote_set_path(remote, furi_string_get_cstr(path));
+        // Load Items
+        for(bool can_read = true; can_read;) {
+            CrossRemoteItem* item = xremote_cross_remote_item_alloc();
+            can_read = xremote_cross_remote_item_read(item, ff);
+            if(can_read) {
+                CrossRemoteItemArray_push_back(remote->items, item);
+            } else {
+                xremote_cross_remote_item_free(item);
+            }
+        }
+        success = true;
+    } while(false);
+
+    furi_string_free(buf);
+    flipper_format_buffered_file_close(ff);
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}
+
+static bool xremote_cross_remote_store(CrossRemote* remote) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    const char* path = furi_string_get_cstr(remote->path);
+
+    FURI_LOG_I(TAG, "Storing file: \'%s\'", path);
+
+    bool success = flipper_format_file_open_always(ff, path) &&
+                   flipper_format_write_header_cstr(ff, XREMOTE_FILE_TYPE, XREMOTE_FILE_VERSION);
+
+    // save Items
+    if(success) {
+        CrossRemoteItemArray_it_t it;
+        for(CrossRemoteItemArray_it(it, remote->items); !CrossRemoteItemArray_end_p(it);
+            CrossRemoteItemArray_next(it)) {
+            CrossRemoteItem* item = *CrossRemoteItemArray_cref(it);
+            success = false;
+            if(item->type == XRemoteRemoteItemTypeInfrared) {
+                success = xremote_cross_remote_item_ir_signal_save(
+                    xremote_cross_remote_item_get_ir_signal(item),
+                    ff,
+                    xremote_cross_remote_item_get_name(item),
+                    xremote_cross_remote_item_get_time(item));
+            } else if(item->type == XRemoteRemoteItemTypePause) {
+                success = xremote_cross_remote_item_pause_save(
+                    ff, item->time, xremote_cross_remote_item_get_name(item));
+            } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
+                success = xremote_cross_remote_item_sg_signal_save(
+                    xremote_cross_remote_item_get_sg_signal(item),
+                    ff,
+                    xremote_cross_remote_item_get_name(item),
+                    xremote_cross_remote_item_get_filename(item));
+            }
+            if(!success) {
+                break;
+            }
+        }
+    }
+
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}
+
+bool xremote_cross_remote_save_new(CrossRemote* remote, const char* name) {
+    FuriString *new_name, *new_path;
+    new_name = furi_string_alloc_set(name);
+    new_path = furi_string_alloc_set(XREMOTE_APP_FOLDER);
+
+    xremote_cross_remote_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path));
+    furi_string_cat_printf(
+        new_path, "/%s%s", furi_string_get_cstr(new_name), XREMOTE_APP_EXTENSION);
+
+    xremote_cross_remote_set_name(remote, furi_string_get_cstr(new_name));
+    xremote_cross_remote_set_path(remote, furi_string_get_cstr(new_path));
+
+    furi_string_free(new_name);
+    furi_string_free(new_path);
+    return xremote_cross_remote_store(remote);
+}
+
+static void xremote_cross_remote_reset(CrossRemote* remote) {
+    furi_string_reset(remote->name);
+    furi_string_reset(remote->path);
+    xremote_cross_remote_clear_items(remote);
+    remote->transmitting = 0;
+}
+
+bool xremote_cross_remote_delete(CrossRemote* remote) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path));
+
+    furi_record_close(RECORD_STORAGE);
+    xremote_cross_remote_reset(remote);
+    return (status == FSE_OK || status == FSE_NOT_EXIST);
+}

+ 28 - 0
models/cross/xremote_cross_remote.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include "xremote_cross_remote_item.h"
+#include "../../xremote_i.h"
+
+#define CROSS_REMOTE_PAUSE_NAME "Pause %ds"
+
+CrossRemote* xremote_cross_remote_alloc();
+
+void xremote_cross_remote_free(CrossRemote* cross_remote);
+bool xremote_cross_remote_load(CrossRemote* cross_remote, FuriString* path);
+const char* xremote_cross_remote_get_name(CrossRemote* remote);
+void xremote_cross_remote_set_transmitting(CrossRemote* remote, int status);
+int xremote_cross_remote_get_transmitting(CrossRemote* remote);
+bool xremote_cross_remote_add_pause(CrossRemote* remote, int time);
+bool xremote_cross_remote_add_ir_item(
+    CrossRemote* remote,
+    const char* name,
+    InfraredSignal* signal,
+    uint32_t timing);
+bool xremote_cross_remote_add_subghz(CrossRemote* remote, SubGhzRemote* subghz);
+void xremote_cross_remote_remove_item(CrossRemote* remote, size_t index);
+void xremote_cross_remote_rename_item(CrossRemote* remote, size_t index, const char* name);
+size_t xremote_cross_remote_get_item_count(CrossRemote* remote);
+CrossRemoteItem* xremote_cross_remote_get_item(CrossRemote* remote, size_t index);
+
+bool xremote_cross_remote_save_new(CrossRemote* remote, const char* name);
+bool xremote_cross_remote_delete(CrossRemote* remote);

+ 286 - 240
models/cross/xremote_remote_item.c → models/cross/xremote_cross_remote_item.c

@@ -1,240 +1,286 @@
-#include "xremote_remote_item.h"
-
-CrossRemoteItem* xremote_remote_item_alloc() {
-    CrossRemoteItem* item = malloc(sizeof(CrossRemoteItem));
-    item->name = furi_string_alloc();
-    item->time = 0;
-    item->type = 0;
-    item->ir_signal = xremote_ir_signal_alloc();
-    item->sg_signal = xremote_sg_remote_alloc();
-
-    return item;
-}
-
-static inline bool xremote_ir_signal_save_message(InfraredMessage* message, FlipperFormat* ff) {
-    const char* protocol_name = infrared_get_protocol_name(message->protocol);
-    return flipper_format_write_string_cstr(ff, "type", "parsed") &&
-           flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
-           flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) &&
-           flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
-}
-
-static inline bool xremote_ir_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) {
-    furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
-    return flipper_format_write_string_cstr(ff, "type", "raw") &&
-           flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
-           flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) &&
-           flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size);
-}
-
-static inline bool xremote_sg_signal_save_data(SubGhzRemote* remote, FlipperFormat* ff) {
-    UNUSED(remote);
-    UNUSED(ff);
-    return true;
-    /*return flipper_format_write_uint32(ff, "frequency", xremote_sg_remote_get_frequency(remote)) &&
-           flipper_format_write_string_cstr(ff, "preset", xremote_sg_remote_get_preset(remote));*/
-}
-
-static bool xremote_ir_signal_is_message_valid(InfraredMessage* message) {
-    if(!infrared_is_protocol_valid(message->protocol)) {
-        FURI_LOG_E(TAG, "Unknown protocol");
-        return false;
-    }
-
-    uint32_t address_length = infrared_get_protocol_address_length(message->protocol);
-    uint32_t address_mask = (1UL << address_length) - 1;
-
-    if(message->address != (message->address & address_mask)) {
-        FURI_LOG_E(
-            TAG,
-            "Address is out of range (mask 0x%08lX): 0x%lX\r\n",
-            address_mask,
-            message->address);
-        return false;
-    }
-
-    uint32_t command_length = infrared_get_protocol_command_length(message->protocol);
-    uint32_t command_mask = (1UL << command_length) - 1;
-
-    if(message->command != (message->command & command_mask)) {
-        FURI_LOG_E(
-            TAG,
-            "Command is out of range (mask 0x%08lX): 0x%lX\r\n",
-            command_mask,
-            message->command);
-        return false;
-    }
-
-    return true;
-}
-
-bool xremote_remote_item_read(CrossRemoteItem* item, FlipperFormat* ff) {
-    FuriString* type = furi_string_alloc();
-    bool success = false;
-    do {
-        if(!flipper_format_read_string(ff, "remote_type", type)) break;
-        if(furi_string_equal(type, "IR")) {
-            success = xremote_remote_item_read_ir(item, ff);
-        } else if(furi_string_equal(type, "PAUSE")) {
-            success = xremote_remote_item_read_pause(item, ff);
-        } else {
-            break;
-        }
-        success = true;
-    } while(false);
-    return success;
-}
-
-bool xremote_remote_item_read_ir(CrossRemoteItem* item, FlipperFormat* ff) {
-    FuriString* buf;
-    bool success = false;
-    buf = furi_string_alloc();
-    item->type = XRemoteRemoteItemTypeInfrared;
-    item->time = 0;
-
-    do {
-        if(!flipper_format_read_string(ff, "name", item->name)) break;
-        if(!flipper_format_read_string(ff, "type", buf)) break;
-        if(furi_string_equal(buf, "raw")) {
-            if(!xremote_remote_item_read_ir_signal_raw(item, ff)) break;
-        } else if(furi_string_equal(buf, "parsed")) {
-            if(!xremote_remote_item_read_message(item, ff)) break;
-        } else {
-            break;
-        }
-        success = true;
-    } while(false);
-    furi_string_free(buf);
-
-    return success;
-}
-
-bool xremote_remote_item_read_pause(CrossRemoteItem* item, FlipperFormat* ff) {
-    bool success = false;
-    item->type = XRemoteRemoteItemTypePause;
-
-    do {
-        if(!flipper_format_read_string(ff, "name", item->name)) break;
-        if(!flipper_format_read_int32(ff, "time", &item->time, 1)) break;
-        success = true;
-    } while(false);
-
-    return success;
-}
-
-bool xremote_remote_item_read_ir_signal_raw(CrossRemoteItem* item, FlipperFormat* ff) {
-    uint32_t timings_size, frequency;
-    float duty_cycle;
-
-    bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) &&
-                   flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) &&
-                   flipper_format_get_value_count(ff, "data", &timings_size);
-
-    if(!success || timings_size > MAX_TIMINGS_AMOUNT) {
-        return false;
-    }
-
-    uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
-    success = flipper_format_read_uint32(ff, "data", timings, timings_size);
-
-    if(success) {
-        xremote_ir_signal_set_raw_signal(
-            item->ir_signal, timings, timings_size, frequency, duty_cycle);
-    }
-
-    free(timings);
-
-    return success;
-}
-
-bool xremote_remote_item_read_message(CrossRemoteItem* item, FlipperFormat* ff) {
-    FuriString* buf = furi_string_alloc();
-    bool success = false;
-
-    do {
-        if(!flipper_format_read_string(ff, "protocol", buf)) break;
-        InfraredMessage message;
-        message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf));
-        success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) &&
-                  flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) &&
-                  xremote_ir_signal_is_message_valid(&message);
-
-        if(!success) break;
-
-        xremote_ir_signal_set_message(item->ir_signal, &message);
-    } while(false);
-
-    furi_string_free(buf);
-    return success;
-}
-
-void xremote_remote_item_free(CrossRemoteItem* item) {
-    furi_string_free(item->name);
-    //Determine type before free
-    //xremote_ir_signal_free(item->ir_signal);
-    //xremote_sg_remote_free(item->sg_signal);
-    free(item);
-}
-
-void xremote_remote_item_set_type(CrossRemoteItem* item, int type) {
-    item->type = type;
-}
-
-void xremote_remote_item_set_name(CrossRemoteItem* item, const char* name) {
-    furi_string_set(item->name, name);
-}
-
-void xremote_remote_item_set_time(CrossRemoteItem* item, int32_t time) {
-    item->time = time;
-}
-
-void xremote_remote_item_set_ir_signal(CrossRemoteItem* item, InfraredSignal* signal) {
-    xremote_ir_signal_set_signal(item->ir_signal, signal);
-}
-
-void xremote_remote_item_set_sg_signal(CrossRemoteItem* item, SubGhzRemote* subghz) {
-    item->sg_signal = subghz;
-}
-
-const char* xremote_remote_item_get_name(CrossRemoteItem* item) {
-    return furi_string_get_cstr(item->name);
-}
-
-InfraredSignal* xremote_remote_item_get_ir_signal(CrossRemoteItem* item) {
-    return item->ir_signal;
-}
-
-SubGhzRemote* xremote_remote_item_get_sg_signal(CrossRemoteItem* item) {
-    return item->sg_signal;
-}
-
-bool xremote_ir_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
-    if(!flipper_format_write_comment_cstr(ff, "") ||
-       !flipper_format_write_string_cstr(ff, "remote_type", "IR") ||
-       !flipper_format_write_string_cstr(ff, "name", name)) {
-        return false;
-    } else if(signal->is_raw) {
-        return xremote_ir_signal_save_raw(&signal->payload.raw, ff);
-    } else {
-        return xremote_ir_signal_save_message(&signal->payload.message, ff);
-    }
-}
-
-bool xremote_pause_save(FlipperFormat* ff, int32_t time, const char* name) {
-    if(!flipper_format_write_comment_cstr(ff, "") ||
-       !flipper_format_write_string_cstr(ff, "remote_type", "PAUSE") ||
-       !flipper_format_write_string_cstr(ff, "name", name) ||
-       !flipper_format_write_int32(ff, "time", &time, 1)) {
-        return false;
-    }
-    return true;
-}
-
-bool xremote_sg_signal_save(SubGhzRemote* remote, FlipperFormat* ff, const char* name) {
-    if(!flipper_format_write_comment_cstr(ff, "") ||
-       !flipper_format_write_string_cstr(ff, "remote_type", "SG") ||
-       !flipper_format_write_string_cstr(ff, "name", name)) {
-        return false;
-    }
-    return xremote_sg_signal_save_data(remote, ff);
-}
+#include "xremote_cross_remote_item.h"
+
+CrossRemoteItem* xremote_cross_remote_item_alloc() {
+    CrossRemoteItem* item = malloc(sizeof(CrossRemoteItem));
+    item->name = furi_string_alloc();
+    item->filename = furi_string_alloc();
+    item->time = 0;
+    item->type = 0;
+    item->ir_signal = xremote_ir_signal_alloc();
+    item->sg_signal = xremote_sg_remote_alloc();
+
+    return item;
+}
+
+static inline bool xremote_ir_signal_save_message(InfraredMessage* message, FlipperFormat* ff) {
+    const char* protocol_name = infrared_get_protocol_name(message->protocol);
+    return flipper_format_write_string_cstr(ff, "type", "parsed") &&
+           flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
+           flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) &&
+           flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
+}
+
+static inline bool xremote_ir_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) {
+    furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
+    return flipper_format_write_string_cstr(ff, "type", "raw") &&
+           flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
+           flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) &&
+           flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size);
+}
+
+static inline bool xremote_sg_signal_save_data(SubGhzRemote* remote, FlipperFormat* ff) {
+    UNUSED(remote);
+    UNUSED(ff);
+    return true;
+}
+
+static bool xremote_ir_signal_is_message_valid(InfraredMessage* message) {
+    if(!infrared_is_protocol_valid(message->protocol)) {
+        FURI_LOG_E(TAG, "Unknown protocol");
+        return false;
+    }
+
+    uint32_t address_length = infrared_get_protocol_address_length(message->protocol);
+    uint32_t address_mask = (1UL << address_length) - 1;
+
+    if(message->address != (message->address & address_mask)) {
+        FURI_LOG_E(
+            TAG,
+            "Address is out of range (mask 0x%08lX): 0x%lX\r\n",
+            address_mask,
+            message->address);
+        return false;
+    }
+
+    uint32_t command_length = infrared_get_protocol_command_length(message->protocol);
+    uint32_t command_mask = (1UL << command_length) - 1;
+
+    if(message->command != (message->command & command_mask)) {
+        FURI_LOG_E(
+            TAG,
+            "Command is out of range (mask 0x%08lX): 0x%lX\r\n",
+            command_mask,
+            message->command);
+        return false;
+    }
+
+    return true;
+}
+
+static bool xremote_cross_remote_item_read_sg(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* buf;
+    bool success = false;
+    buf = furi_string_alloc();
+    item->type = XRemoteRemoteItemTypeSubGhz;
+    item->time = 0;
+    do {
+        success = flipper_format_read_string(ff, "name", item->name) &&
+                  flipper_format_read_string(ff, "filename", item->filename);
+        if(!success) break;
+
+    } while(false);
+    furi_string_free(buf);
+    FURI_LOG_D(TAG, "final name: %s", furi_string_get_cstr(item->name));
+    FURI_LOG_D(TAG, "final filename: %s", furi_string_get_cstr(item->filename));
+
+    return success;
+}
+
+static bool
+    xremote_cross_remote_item_read_ir_signal_raw(CrossRemoteItem* item, FlipperFormat* ff) {
+    uint32_t timings_size, frequency;
+    float duty_cycle;
+
+    bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) &&
+                   flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) &&
+                   flipper_format_get_value_count(ff, "data", &timings_size);
+
+    if(!success || timings_size > MAX_TIMINGS_AMOUNT) {
+        return false;
+    }
+
+    uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
+    success = flipper_format_read_uint32(ff, "data", timings, timings_size);
+
+    if(success) {
+        xremote_ir_signal_set_raw_signal(
+            item->ir_signal, timings, timings_size, frequency, duty_cycle);
+    }
+
+    free(timings);
+
+    return success;
+}
+
+static bool xremote_cross_remote_item_read_message(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* buf = furi_string_alloc();
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "protocol", buf)) break;
+        InfraredMessage message;
+        message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf));
+        success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) &&
+                  flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) &&
+                  xremote_ir_signal_is_message_valid(&message);
+
+        if(!success) break;
+
+        xremote_ir_signal_set_message(item->ir_signal, &message);
+    } while(false);
+
+    furi_string_free(buf);
+    return success;
+}
+
+static bool xremote_cross_remote_item_read_ir(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* buf;
+    bool success = false;
+    buf = furi_string_alloc();
+    item->type = XRemoteRemoteItemTypeInfrared;
+    item->time = 0;
+
+    do {
+        if(!flipper_format_read_string(ff, "name", item->name)) break;
+        if(!flipper_format_read_uint32(ff, "time", &item->time, 1)) item->time = 1000;
+        if(!flipper_format_read_string(ff, "type", buf)) break;
+        if(furi_string_equal(buf, "raw")) {
+            if(!xremote_cross_remote_item_read_ir_signal_raw(item, ff)) break;
+        } else if(furi_string_equal(buf, "parsed")) {
+            if(!xremote_cross_remote_item_read_message(item, ff)) break;
+        } else {
+            break;
+        }
+        success = true;
+    } while(false);
+    furi_string_free(buf);
+
+    return success;
+}
+
+static bool xremote_cross_remote_item_read_pause(CrossRemoteItem* item, FlipperFormat* ff) {
+    bool success = false;
+    item->type = XRemoteRemoteItemTypePause;
+
+    do {
+        if(!flipper_format_read_string(ff, "name", item->name)) break;
+        if(!flipper_format_read_uint32(ff, "time", &item->time, 1)) break;
+        success = true;
+    } while(false);
+
+    return success;
+}
+
+bool xremote_cross_remote_item_read(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* type = furi_string_alloc();
+    bool success = false;
+    do {
+        if(!flipper_format_read_string(ff, "remote_type", type)) break;
+        if(furi_string_equal(type, "IR")) {
+            success = xremote_cross_remote_item_read_ir(item, ff);
+        } else if(furi_string_equal(type, "SG")) {
+            success = xremote_cross_remote_item_read_sg(item, ff);
+        } else if(furi_string_equal(type, "PAUSE")) {
+            success = xremote_cross_remote_item_read_pause(item, ff);
+        } else {
+            break;
+        }
+        success = true;
+    } while(false);
+    furi_string_free(type);
+    return success;
+}
+
+void xremote_cross_remote_item_free(CrossRemoteItem* item) {
+    furi_string_free(item->name);
+    furi_string_free(item->filename);
+    //Determine type before free
+    xremote_ir_signal_free(item->ir_signal);
+    xremote_sg_remote_free(item->sg_signal);
+    free(item);
+}
+
+void xremote_cross_remote_item_set_type(CrossRemoteItem* item, int type) {
+    item->type = type;
+}
+
+void xremote_cross_remote_item_set_name(CrossRemoteItem* item, const char* name) {
+    furi_string_set(item->name, name);
+}
+
+void xremote_cross_remote_item_set_filename(CrossRemoteItem* item, const char* filename) {
+    furi_string_set(item->filename, filename);
+}
+
+void xremote_cross_remote_item_set_time(CrossRemoteItem* item, uint32_t time) {
+    item->time = time;
+}
+
+void xremote_cross_remote_item_set_ir_signal(CrossRemoteItem* item, InfraredSignal* signal) {
+    xremote_ir_signal_set_signal(item->ir_signal, signal);
+}
+
+void xremote_cross_remote_item_set_sg_signal(CrossRemoteItem* item, SubGhzRemote* subghz) {
+    item->sg_signal = subghz;
+}
+
+const char* xremote_cross_remote_item_get_name(CrossRemoteItem* item) {
+    return furi_string_get_cstr(item->name);
+}
+
+const char* xremote_cross_remote_item_get_filename(CrossRemoteItem* item) {
+    return furi_string_get_cstr(item->filename);
+}
+
+InfraredSignal* xremote_cross_remote_item_get_ir_signal(CrossRemoteItem* item) {
+    return item->ir_signal;
+}
+
+SubGhzRemote* xremote_cross_remote_item_get_sg_signal(CrossRemoteItem* item) {
+    return item->sg_signal;
+}
+
+uint32_t xremote_cross_remote_item_get_time(CrossRemoteItem* item) {
+    return item->time;
+}
+
+bool xremote_cross_remote_item_ir_signal_save(
+    InfraredSignal* signal,
+    FlipperFormat* ff,
+    const char* name,
+    uint32_t time) {
+    if(!flipper_format_write_comment_cstr(ff, "") ||
+       !flipper_format_write_string_cstr(ff, "remote_type", "IR") ||
+       !flipper_format_write_string_cstr(ff, "name", name) ||
+       !flipper_format_write_uint32(ff, "time", &time, 1)) {
+        return false;
+    } else if(signal->is_raw) {
+        return xremote_ir_signal_save_raw(&signal->payload.raw, ff);
+    } else {
+        return xremote_ir_signal_save_message(&signal->payload.message, ff);
+    }
+}
+
+bool xremote_cross_remote_item_pause_save(FlipperFormat* ff, uint32_t time, const char* name) {
+    if(!flipper_format_write_comment_cstr(ff, "") ||
+       !flipper_format_write_string_cstr(ff, "remote_type", "PAUSE") ||
+       !flipper_format_write_string_cstr(ff, "name", name) ||
+       !flipper_format_write_uint32(ff, "time", &time, 1)) {
+        return false;
+    }
+    return true;
+}
+
+bool xremote_cross_remote_item_sg_signal_save(
+    SubGhzRemote* remote,
+    FlipperFormat* ff,
+    const char* name,
+    const char* filename) {
+    if(!flipper_format_write_comment_cstr(ff, "") ||
+       !flipper_format_write_string_cstr(ff, "remote_type", "SG") ||
+       !flipper_format_write_string_cstr(ff, "name", name) ||
+       !flipper_format_write_string_cstr(ff, "filename", filename)) {
+        return false;
+    }
+    return xremote_sg_signal_save_data(remote, ff);
+}

+ 36 - 0
models/cross/xremote_cross_remote_item.h

@@ -0,0 +1,36 @@
+#pragma once
+
+#include "../infrared/xremote_ir_signal.h"
+#include "../subghz/xremote_sg_remote.h"
+#include "../../xremote_i.h"
+
+bool xremote_cross_remote_item_read(CrossRemoteItem* item, FlipperFormat* ff);
+
+CrossRemoteItem* xremote_cross_remote_item_alloc();
+void xremote_cross_remote_item_free(CrossRemoteItem* item);
+
+void xremote_cross_remote_item_set_name(CrossRemoteItem* item, const char* name);
+const char* xremote_cross_remote_item_get_name(CrossRemoteItem* item);
+void xremote_cross_remote_item_set_filename(CrossRemoteItem* item, const char* filename);
+const char* xremote_cross_remote_item_get_filename(CrossRemoteItem* item);
+
+void xremote_cross_remote_item_set_type(CrossRemoteItem* item, int type);
+void xremote_cross_remote_item_set_time(CrossRemoteItem* item, uint32_t time);
+uint32_t xremote_cross_remote_item_get_time(CrossRemoteItem* item);
+
+InfraredSignal* xremote_cross_remote_item_get_ir_signal(CrossRemoteItem* item);
+void xremote_cross_remote_item_set_ir_signal(CrossRemoteItem* item, InfraredSignal* signal);
+SubGhzRemote* xremote_cross_remote_item_get_sg_signal(CrossRemoteItem* item);
+void xremote_cross_remote_item_set_sg_signal(CrossRemoteItem* item, SubGhzRemote* subghz);
+
+bool xremote_cross_remote_item_pause_save(FlipperFormat* ff, uint32_t time, const char* name);
+bool xremote_cross_remote_item_ir_signal_save(
+    InfraredSignal* signal,
+    FlipperFormat* ff,
+    const char* name,
+    uint32_t time);
+bool xremote_cross_remote_item_sg_signal_save(
+    SubGhzRemote* remote,
+    FlipperFormat* ff,
+    const char* name,
+    const char* filename);

+ 0 - 28
models/cross/xremote_remote.h

@@ -1,28 +0,0 @@
-#pragma once
-
-#include "xremote_remote_item.h"
-#include "../../xremote_i.h"
-
-#define CROSS_REMOTE_PAUSE_NAME "Pause %ds"
-
-CrossRemote* cross_remote_alloc();
-
-void cross_remote_free(CrossRemote* cross_remote);
-bool cross_remote_load(CrossRemote* cross_remote, FuriString* path);
-const char* cross_remote_get_name(CrossRemote* remote);
-void cross_remote_set_transmitting(CrossRemote* remote, int status);
-int cross_remote_get_transmitting(CrossRemote* remote);
-bool cross_remote_add_pause(CrossRemote* remote, int time);
-bool cross_remote_add_ir_item(CrossRemote* remote, const char* name, InfraredSignal* signal);
-bool cross_remote_add_subghz(CrossRemote* remote, SubGhzRemote* subghz);
-void cross_remote_remove_item(CrossRemote* remote, size_t index);
-void cross_remote_rename_item(CrossRemote* remote, size_t index, const char* name);
-size_t cross_remote_get_item_count(CrossRemote* remote);
-CrossRemoteItem* cross_remote_get_item(CrossRemote* remote, size_t index);
-
-void cross_remote_set_name(CrossRemote* remote, const char* name);
-void cross_remote_set_path(CrossRemote* remote, const char* path);
-bool cross_remote_save_new(CrossRemote* remote, const char* name);
-bool cross_remote_store(CrossRemote* remote);
-void cross_remote_reset(CrossRemote* remote);
-bool cross_remote_delete(CrossRemote* remote);

+ 0 - 31
models/cross/xremote_remote_item.h

@@ -1,31 +0,0 @@
-#pragma once
-
-#include "../infrared/xremote_ir_signal.h"
-#include "../subghz/xremote_sg_remote.h"
-#include "../subghz/subghz_i.h"
-#include "../../xremote_i.h"
-
-bool xremote_remote_item_read(CrossRemoteItem* item, FlipperFormat* ff);
-bool xremote_remote_item_read_ir(CrossRemoteItem* item, FlipperFormat* ff);
-bool xremote_remote_item_read_ir_signal_raw(CrossRemoteItem* item, FlipperFormat* ff);
-bool xremote_remote_item_read_message(CrossRemoteItem* item, FlipperFormat* ff);
-
-bool xremote_remote_item_read_pause(CrossRemoteItem* item, FlipperFormat* ff);
-
-CrossRemoteItem* xremote_remote_item_alloc();
-void xremote_remote_item_free(CrossRemoteItem* item);
-
-void xremote_remote_item_set_name(CrossRemoteItem* item, const char* name);
-const char* xremote_remote_item_get_name(CrossRemoteItem* item);
-
-void xremote_remote_item_set_type(CrossRemoteItem* item, int type);
-void xremote_remote_item_set_time(CrossRemoteItem* item, int32_t time);
-
-InfraredSignal* xremote_remote_item_get_ir_signal(CrossRemoteItem* item);
-void xremote_remote_item_set_ir_signal(CrossRemoteItem* item, InfraredSignal* signal);
-SubGhzRemote* xremote_remote_item_get_sg_signal(CrossRemoteItem* item);
-void xremote_remote_item_set_sg_signal(CrossRemoteItem* item, SubGhzRemote* subghz);
-
-bool xremote_pause_save(FlipperFormat* ff, int32_t time, const char* name);
-bool xremote_ir_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
-bool xremote_sg_signal_save(SubGhzRemote* remote, FlipperFormat* ff, const char* name);

+ 0 - 55
models/subghz/subghz_i.h

@@ -1,55 +0,0 @@
-
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <m-array.h>
-#include <toolbox/path.h>
-#include <storage/storage.h>
-#include <core/common_defines.h>
-#include <flipper_format/flipper_format.h>
-#include <lib/subghz/protocols/raw.h>
-//#include <lib/toolbox/path.h>
-//#include <flipper_format/flipper_format_i.h>
-#include <lib/toolbox/stream/stream.h>
-//#include <lib/subghz/protocols/protocol_items.h> //Not found
-#include <lib/subghz/subghz_worker.h>
-#include <lib/subghz/subghz_setting.h>
-#include <lib/subghz/receiver.h>
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/devices/devices.h>
-//#include <lib/subghz/blocks/custom_btn.h>
-
-#include <flipper_format/flipper_format_i.h>
-
-#include "subghz_types.h"
-
-extern const SubGhzProtocolRegistry subghz_protocol_registry;
-
-struct SubGhzTxRx {
-    //SubGhzWorker* worker;
-
-    SubGhzEnvironment* environment;
-    SubGhzReceiver* receiver;
-    //SubGhzTransmitter* transmitter;
-    //SubGhzProtocolFlag filter;
-    SubGhzProtocolDecoderBase* decoder_result;
-    FlipperFormat* fff_data;
-
-    SubGhzRadioPreset* preset;
-    //SubGhzHistory* history;
-    //uint16_t idx_menu_chosen;
-    //SubGhzTxRxState txrx_state;
-    //SubGhzHopperState hopper_state;
-    //SubGhzSpeakerState speaker_state;
-    //uint8_t hopper_timeout;
-    //uint8_t hopper_idx_frequency;
-    //SubGhzRxKeyState rx_key_state;
-
-    //float raw_threshold_rssi;
-    //uint8_t raw_threshold_rssi_low_count;
-    const SubGhzDevice* radio_device;
-};
-
-typedef struct SubGhzTxRx SubGhzTxRx;

+ 9 - 155
models/subghz/xremote_sg_remote.c

@@ -1,136 +1,44 @@
-#include "subghz/types.h"
-#include <lib/toolbox/path.h>
-#include <lib/subghz/types.h>
-#include <lib/subghz/subghz_worker.h>
-#include <lib/subghz/subghz_setting.h>
-#include <lib/subghz/receiver.h>
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/registry.h>
-//#include <lib/subghz/protocols/keeloq.h>
-//#include <lib/subghz/protocols/secplus_v1.h>
-//#include <lib/subghz/protocols/secplus_v2.h>
-//#include <lib/subghz/protocols/princeton_for_testing.h>
-//#include <lib/subghz/blocks/math.h>
-//#include <lib/subghz/protocols/raw.h>
-//#include <lib/subghz/protocols/bin_raw.h>
-//#include <lib/subghz/protocols/protocol_items.h>
-//#include <lib/subghz/protocols/protocol_items.c>
-#include <lib/subghz/subghz_keystore.h>
-//#include <lib/subghz/subghz_file_encoder_worker.h>
-#include <gui/modules/variable_item_list.h>
 #include "xremote_sg_remote.h"
 #include "xremote_sg_remote.h"
 
 
 #define TAG "Xremote"
 #define TAG "Xremote"
 
 
 struct SubGhzRemote {
 struct SubGhzRemote {
     FuriString* name;
     FuriString* name;
+    FuriString* filename;
     FuriString* path;
     FuriString* path;
-    SubGhzTxRx* txrx;
-    uint32_t frequency;
-    SubGhzSetting* setting;
 };
 };
 
 
 const char* xremote_sg_remote_get_name(SubGhzRemote* remote) {
 const char* xremote_sg_remote_get_name(SubGhzRemote* remote) {
     return furi_string_get_cstr(remote->name);
     return furi_string_get_cstr(remote->name);
 }
 }
 
 
-void subghz_preset_init(
-    void* context,
-    const char* preset_name,
-    uint32_t frequency,
-    uint8_t* preset_data,
-    size_t preset_data_size) {
-    furi_assert(context);
-    SubGhzRemote* remote = context;
-    furi_string_set(remote->txrx->preset->name, preset_name);
-    remote->txrx->preset->frequency = frequency;
-    remote->txrx->preset->data = preset_data;
-    remote->txrx->preset->data_size = preset_data_size;
-}
-
-const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return subghz_devices_get_name(instance->radio_device);
+const char* xremote_sg_remote_get_filename(SubGhzRemote* remote) {
+    return furi_string_get_cstr(remote->filename);
 }
 }
 
 
 SubGhzRemote* xremote_sg_remote_alloc() {
 SubGhzRemote* xremote_sg_remote_alloc() {
     SubGhzRemote* remote = malloc(sizeof(SubGhzRemote));
     SubGhzRemote* remote = malloc(sizeof(SubGhzRemote));
     remote->name = furi_string_alloc();
     remote->name = furi_string_alloc();
+    remote->filename = furi_string_alloc();
     remote->path = furi_string_alloc();
     remote->path = furi_string_alloc();
 
 
-    // SubGhz Settings
-    remote->setting = subghz_setting_alloc();
-    subghz_setting_load(remote->setting, EXT_PATH("subghz/assets/setting_user"));
-
-    remote->txrx = malloc(sizeof(SubGhzTxRx));
-    remote->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
-    remote->txrx->preset->name = furi_string_alloc();
-    subghz_preset_init(
-        remote, "AM650", subghz_setting_get_default_frequency(remote->setting), NULL, 0);
-    remote->txrx->fff_data = flipper_format_string_alloc();
-    remote->txrx->environment = subghz_environment_alloc();
-    /*subghz_environment_set_came_atomo_rainbow_table_file_name(
-        remote->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
-    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
-        remote->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n"));
-    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
-        remote->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s"));
-    subghz_environment_set_protocol_registry(
-        remote->txrx->environment, (void*)&subghz_protocol_registry);
-    remote->txrx->receiver = subghz_receiver_alloc_init(remote->txrx->environment);*/
-    //remote->txrx->worker = subghz_worker_alloc();
-
     return remote;
     return remote;
 }
 }
 
 
 void xremote_sg_remote_free(SubGhzRemote* remote) {
 void xremote_sg_remote_free(SubGhzRemote* remote) {
     furi_string_free(remote->path);
     furi_string_free(remote->path);
     furi_string_free(remote->name);
     furi_string_free(remote->name);
-
-    // TXRX
-    subghz_receiver_free(remote->txrx->receiver);
-    subghz_environment_free(remote->txrx->environment);
-    flipper_format_free(remote->txrx->fff_data);
-    furi_string_free(remote->txrx->preset->name);
-    free(remote->txrx->preset);
-    free(remote->txrx);
+    furi_string_free(remote->filename);
 
 
     free(remote);
     free(remote);
 }
 }
 
 
-bool xremtoe_sg_set_preset(SubGhzRemote* remote, const char* preset) {
-    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
-        furi_string_set(remote->txrx->preset->name, "AM270");
-    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
-        furi_string_set(remote->txrx->preset->name, "AM650");
-    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
-        furi_string_set(remote->txrx->preset->name, "FM238");
-    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
-        furi_string_set(remote->txrx->preset->name, "FM476");
-    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
-        furi_string_set(remote->txrx->preset->name, "CUSTOM");
-    } else {
-        FURI_LOG_E(TAG, "Unknown preset");
-        return false;
-    }
-    return true;
-}
-
-uint32_t xremote_sg_remote_get_frequency(SubGhzRemote* remote) {
-    return remote->txrx->preset->frequency;
-}
-
-const char* xremote_sg_remote_get_preset(SubGhzRemote* remote) {
-    return furi_string_get_cstr(remote->txrx->preset->name);
-}
-
 bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path) {
 bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path) {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
     FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
 
 
     FuriString* buf;
     FuriString* buf;
     buf = furi_string_alloc();
     buf = furi_string_alloc();
-    uint32_t temp_data32;
 
 
     FURI_LOG_I(TAG, "loading SG Remote: \'%s\'", furi_string_get_cstr(path));
     FURI_LOG_I(TAG, "loading SG Remote: \'%s\'", furi_string_get_cstr(path));
     bool success = false;
     bool success = false;
@@ -139,78 +47,24 @@ bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path) {
         if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
         if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
         const char* fullPath = furi_string_get_cstr(path);
         const char* fullPath = furi_string_get_cstr(path);
         char* fileName = strrchr(fullPath, '/') + 1;
         char* fileName = strrchr(fullPath, '/') + 1;
+        furi_string_set_str(remote->filename, fullPath);
         char* dotPosition = strrchr(fileName, '.');
         char* dotPosition = strrchr(fileName, '.');
         if(dotPosition != NULL) { // check if there is a dot in the file name
         if(dotPosition != NULL) { // check if there is a dot in the file name
             *dotPosition = '\0'; // set the dot position to NULL character to truncate the string
             *dotPosition = '\0'; // set the dot position to NULL character to truncate the string
         }
         }
         //remote->name = fileName;
         //remote->name = fileName;
         furi_string_set_str(remote->name, fileName);
         furi_string_set_str(remote->name, fileName);
+        //furi_string_set_str(remote->filename, fileName);
+        //free(fileName);
         uint32_t version;
         uint32_t version;
         if(!flipper_format_read_header(ff, buf, &version)) break;
         if(!flipper_format_read_header(ff, buf, &version)) break;
         if(!furi_string_equal(buf, "Flipper SubGhz RAW File") || (version != 1)) break;
         if(!furi_string_equal(buf, "Flipper SubGhz RAW File") || (version != 1)) break;
 
 
-        if(!flipper_format_read_uint32(ff, "Frequency", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "Missing Frequency");
-            break;
-        }
-        remote->frequency = temp_data32;
-
-        if(!flipper_format_read_string(ff, "Preset", buf)) {
-            FURI_LOG_E(TAG, "Missing Preset");
-            break;
-        }
-        if(!xremtoe_sg_set_preset(remote, furi_string_get_cstr(buf))) {
-            break;
-        }
-
-        if(!strcmp(furi_string_get_cstr(buf), "FuriHalSubGhzPresetCustom")) {
-            //Todo add Custom_preset_module
-            //delete preset if it already exists
-            subghz_setting_delete_custom_preset(
-                remote->setting, furi_string_get_cstr(remote->txrx->preset->name));
-            //load custom preset from file
-            if(!subghz_setting_load_custom_preset(
-                   remote->setting, furi_string_get_cstr(remote->txrx->preset->name), ff)) {
-                FURI_LOG_E(TAG, "Missing Custom preset");
-                break;
-            }
-        }
-
-        if(!flipper_format_read_string(ff, "Protocol", buf)) {
-            FURI_LOG_E(TAG, "Missing Protocol");
-            break;
-        }
-
-        if(!strcmp(furi_string_get_cstr(buf), "RAW")) {
-            subghz_protocol_raw_gen_fff_data(
-                remote->txrx->fff_data,
-                furi_string_get_cstr(path),
-                subghz_txrx_radio_device_get_name(remote->txrx));
-        } else {
-            stream_copy_full(
-                flipper_format_get_raw_stream(ff),
-                flipper_format_get_raw_stream(remote->txrx->fff_data));
-        }
-
-        /*remote->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
-            remote->txrx->receiver, furi_string_get_cstr(buf));*/
-        if(remote->txrx->decoder_result) {
-            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
-                remote->txrx->decoder_result, remote->txrx->fff_data);
-            if(status != SubGhzProtocolStatusOk) {
-                //load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
-                success = false;
-                break;
-            }
-        } else {
-            FURI_LOG_E(TAG, "Protocol not found");
-            break;
-        }
-
         success = true;
         success = true;
     } while(false);
     } while(false);
 
 
     furi_string_free(buf);
     furi_string_free(buf);
+    flipper_format_buffered_file_close(ff);
     flipper_format_free(ff);
     flipper_format_free(ff);
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
     return success;
     return success;

+ 10 - 9
models/subghz/xremote_sg_remote.h

@@ -1,21 +1,22 @@
 #pragma once
 #pragma once
 
 
 //#include "../../xremote_i.h"
 //#include "../../xremote_i.h"
-#include "subghz_i.h"
-//#include <lib/subghz/protocols/protocol_items.h>
-
-//extern const SubGhzProtocolRegistry subghz_protocol_registry;
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+//#include <m-array.h>
+#include <storage/storage.h>
+#include <core/common_defines.h>
+#include <flipper_format/flipper_format.h>
+#include <flipper_format/flipper_format_i.h>
 
 
 typedef struct SubGhzRemote SubGhzRemote;
 typedef struct SubGhzRemote SubGhzRemote;
 
 
 const char* xremote_sg_remote_get_name(SubGhzRemote* remote);
 const char* xremote_sg_remote_get_name(SubGhzRemote* remote);
+const char* xremote_sg_remote_get_filename(SubGhzRemote* remote);
 
 
 SubGhzRemote* xremote_sg_remote_alloc();
 SubGhzRemote* xremote_sg_remote_alloc();
 
 
 void xremote_sg_remote_free(SubGhzRemote* remote);
 void xremote_sg_remote_free(SubGhzRemote* remote);
 
 
-bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path);
-
-uint32_t xremote_sg_remote_get_frequency(SubGhzRemote* remote);
-
-const char* xremote_sg_remote_get_preset(SubGhzRemote* remote);
+bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path);

+ 3 - 31
scenes/xremote_scene_create.c

@@ -16,7 +16,6 @@ static void xremote_create_callback(void* context, int32_t index, InputType type
     } else if(type == InputTypeRelease) {
     } else if(type == InputTypeRelease) {
         custom_type = XRemoteCustomEventMenuVoid;
         custom_type = XRemoteCustomEventMenuVoid;
     } else if(type == InputTypeShort) {
     } else if(type == InputTypeShort) {
-        //somehow ButtonMenuItemTypeCommon uses InputTypeShort
         custom_type = XRemoteCustomEventMenuSelected;
         custom_type = XRemoteCustomEventMenuSelected;
     } else {
     } else {
         furi_crash("Unexpected Input Type");
         furi_crash("Unexpected Input Type");
@@ -30,14 +29,13 @@ void xremote_scene_create_on_enter(void* context) {
     furi_assert(context);
     furi_assert(context);
     XRemote* app = context;
     XRemote* app = context;
     ButtonMenu* button_menu = app->button_menu_create;
     ButtonMenu* button_menu = app->button_menu_create;
-    //SceneManager* scene_manager = app->scene_manager;
 
 
-    size_t item_count = cross_remote_get_item_count(app->cross_remote);
+    size_t item_count = xremote_cross_remote_get_item_count(app->cross_remote);
     for(size_t i = 0; i < item_count; ++i) {
     for(size_t i = 0; i < item_count; ++i) {
-        CrossRemoteItem* item = cross_remote_get_item(app->cross_remote, i);
+        CrossRemoteItem* item = xremote_cross_remote_get_item(app->cross_remote, i);
         button_menu_add_item(
         button_menu_add_item(
             button_menu,
             button_menu,
-            xremote_remote_item_get_name(item),
+            xremote_cross_remote_item_get_name(item),
             i,
             i,
             xremote_create_callback,
             xremote_create_callback,
             ButtonMenuItemTypeCommon,
             ButtonMenuItemTypeCommon,
@@ -76,13 +74,6 @@ bool xremote_scene_create_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeBack) {
     if(event.type == SceneManagerEventTypeBack) {
         scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu);
         scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu);
         consumed = true;
         consumed = true;
-        /*if(is_transmitter_idle) {
-            const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
-            consumed = scene_manager_search_and_switch_to_previous_scene_one_of(
-                scene_manager, possible_scenes, COUNT_OF(possible_scenes));
-        } else {
-            consumed = true;
-        }*/
     } else if(event.type == SceneManagerEventTypeCustom) {
     } else if(event.type == SceneManagerEventTypeCustom) {
         const uint16_t custom_type = xremote_custom_menu_event_get_type(event.event);
         const uint16_t custom_type = xremote_custom_menu_event_get_type(event.event);
         const int16_t button_index = xremote_custom_menu_event_get_value(event.event);
         const int16_t button_index = xremote_custom_menu_event_get_value(event.event);
@@ -101,25 +92,6 @@ bool xremote_scene_create_on_event(void* context, SceneManagerEvent event) {
             app->edit_item = button_index;
             app->edit_item = button_index;
             scene_manager_next_scene(app->scene_manager, XRemoteSceneEditItem);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneEditItem);
         }
         }
-        /*switch(event.event) {
-            case XRemoteCustomEventCreateLeft:
-            case XRemoteCustomEventCreateRight:
-                break;
-            case XRemoteCustomEventCreateUp:
-            case XRemoteCustomEventCreateDown:
-                break;
-            case XRemoteCustomEventCreateBack:
-                notification_message(app->notification, &sequence_reset_red);
-                notification_message(app->notification, &sequence_reset_green);
-                notification_message(app->notification, &sequence_reset_blue);
-                if(!scene_manager_search_and_switch_to_previous_scene(
-                    app->scene_manager, XRemoteSceneMenu)) {
-                        scene_manager_stop(app->scene_manager);
-                        view_dispatcher_stop(app->view_dispatcher);
-                    }
-                consumed = true;
-                break;
-        }*/
     }
     }
 
 
     return consumed;
     return consumed;

+ 2 - 2
scenes/xremote_scene_create_add.c

@@ -79,8 +79,8 @@ bool xremote_scene_create_add_on_event(void* context, SceneManagerEvent event) {
                 scene_manager_next_scene(app->scene_manager, XRemoteSceneIrList);
                 scene_manager_next_scene(app->scene_manager, XRemoteSceneIrList);
             }
             }
             if(button_index == ButtonIndexSubghz) {
             if(button_index == ButtonIndexSubghz) {
-                //scene_manager_next_scene(app->scene_manager, XRemoteSceneSgList);
-                scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneSgList);
+                //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
             }
             }
             if(button_index == ButtonIndexPause) {
             if(button_index == ButtonIndexPause) {
                 scene_manager_next_scene(app->scene_manager, XRemoteScenePauseSet);
                 scene_manager_next_scene(app->scene_manager, XRemoteScenePauseSet);

+ 2 - 2
scenes/xremote_scene_edit_item.c

@@ -1,5 +1,5 @@
 #include "../xremote.h"
 #include "../xremote.h"
-#include "../models/cross/xremote_remote.h"
+#include "../models/cross/xremote_cross_remote.h"
 
 
 enum SubmenuIndexEdit {
 enum SubmenuIndexEdit {
     SubmenuIndexRename = 10,
     SubmenuIndexRename = 10,
@@ -32,7 +32,7 @@ bool xremote_scene_edit_item_on_event(void* context, SceneManagerEvent event) {
         return true;
         return true;
     } else if(event.type == SceneManagerEventTypeCustom) {
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexDelete) {
         if(event.event == SubmenuIndexDelete) {
-            cross_remote_remove_item(app->cross_remote, app->edit_item);
+            xremote_cross_remote_remove_item(app->cross_remote, app->edit_item);
         } else if(event.event == SubmenuIndexRename) {
         } else if(event.event == SubmenuIndexRename) {
             scene_manager_next_scene(app->scene_manager, XRemoteSceneSaveRemoteItem);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneSaveRemoteItem);
             //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
             //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);

+ 2 - 1
scenes/xremote_scene_ir_remote.c

@@ -65,7 +65,8 @@ bool xremote_scene_ir_remote_on_event(void* context, SceneManagerEvent event) {
             const char* button_name = xremote_ir_remote_button_get_name(ir_button);
             const char* button_name = xremote_ir_remote_button_get_name(ir_button);
             InfraredSignal* signal = xremote_ir_remote_button_get_signal(ir_button);
             InfraredSignal* signal = xremote_ir_remote_button_get_signal(ir_button);
 
 
-            cross_remote_add_ir_item(app->cross_remote, button_name, signal);
+            xremote_cross_remote_add_ir_item(
+                app->cross_remote, button_name, signal, app->ir_timing);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
             consumed = true;
             consumed = true;
         }
         }

+ 1 - 0
scenes/xremote_scene_menu.c

@@ -1,3 +1,4 @@
+#include "../helpers/subghz/subghz.h"
 #include "../xremote.h"
 #include "../xremote.h"
 
 
 enum SubmenuIndex {
 enum SubmenuIndex {

+ 1 - 1
scenes/xremote_scene_pause_set.c

@@ -39,7 +39,7 @@ bool xremote_scene_pause_set_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
             consumed = true;
             break;
             break;
         case XRemoteCustomEventPauseSetOk:
         case XRemoteCustomEventPauseSetOk:
-            //cross_remote_add_pause(app->cross_remote, time);
+            //xremote_cross_remote_add_pause(app->cross_remote, time);
             scene_manager_search_and_switch_to_previous_scene(
             scene_manager_search_and_switch_to_previous_scene(
                 app->scene_manager, XRemoteSceneCreate);
                 app->scene_manager, XRemoteSceneCreate);
             consumed = true;
             consumed = true;

+ 4 - 2
scenes/xremote_scene_save_remote.c

@@ -20,7 +20,9 @@ void xremote_scene_save_remote_on_enter(void* context) {
     //A lot missing here
     //A lot missing here
 
 
     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
-        furi_string_get_cstr(folder_path), XREMOTE_APP_EXTENSION, cross_remote_get_name(remote));
+        furi_string_get_cstr(folder_path),
+        XREMOTE_APP_EXTENSION,
+        xremote_cross_remote_get_name(remote));
     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 
 
     furi_string_free(folder_path);
     furi_string_free(folder_path);
@@ -45,7 +47,7 @@ bool xremote_scene_save_remote_on_event(void* context, SceneManagerEvent event)
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         bool success = false;
         bool success = false;
 
 
-        success = cross_remote_save_new(remote, app->text_store[0]);
+        success = xremote_cross_remote_save_new(remote, app->text_store[0]);
 
 
         if(success) {
         if(success) {
             scene_manager_next_scene(scene_manager, XRemoteSceneMenu);
             scene_manager_next_scene(scene_manager, XRemoteSceneMenu);

+ 3 - 3
scenes/xremote_scene_save_remote_item.c

@@ -1,5 +1,5 @@
 #include "../xremote.h"
 #include "../xremote.h"
-#include "../models/cross/xremote_remote.h"
+#include "../models/cross/xremote_cross_remote.h"
 
 
 #include <string.h>
 #include <string.h>
 #include <toolbox/path.h>
 #include <toolbox/path.h>
@@ -11,7 +11,7 @@ void xremote_scene_save_remote_item_on_enter(void* context) {
     text_input_set_header_text(text_input, "Name the Sequence");
     text_input_set_header_text(text_input, "Name the Sequence");
 
 
     size_t enter_name_length = XREMOTE_MAX_REMOTE_NAME_LENGTH;
     size_t enter_name_length = XREMOTE_MAX_REMOTE_NAME_LENGTH;
-    CrossRemoteItem* item = cross_remote_get_item(app->cross_remote, app->edit_item);
+    CrossRemoteItem* item = xremote_cross_remote_get_item(app->cross_remote, app->edit_item);
     strncpy(app->text_store[0], furi_string_get_cstr(item->name), enter_name_length);
     strncpy(app->text_store[0], furi_string_get_cstr(item->name), enter_name_length);
     text_input_set_result_callback(
     text_input_set_result_callback(
         text_input,
         text_input,
@@ -31,7 +31,7 @@ bool xremote_scene_save_remote_item_on_event(void* context, SceneManagerEvent ev
     bool consumed = false;
     bool consumed = false;
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
-        cross_remote_rename_item(remote, app->edit_item, app->text_store[0]);
+        xremote_cross_remote_rename_item(remote, app->edit_item, app->text_store[0]);
         scene_manager_next_scene(scene_manager, XRemoteSceneCreate);
         scene_manager_next_scene(scene_manager, XRemoteSceneCreate);
         consumed = true;
         consumed = true;
     }
     }

+ 14 - 14
scenes/xremote_scene_settings.c

@@ -1,29 +1,29 @@
 #include "../xremote.h"
 #include "../xremote.h"
 #include <lib/toolbox/value_index.h>
 #include <lib/toolbox/value_index.h>
-
+/*
 enum SettingsIndex {
 enum SettingsIndex {
     SettingsIndexHaptic = 10,
     SettingsIndexHaptic = 10,
     SettingsIndexValue1,
     SettingsIndexValue1,
     SettingsIndexValue2,
     SettingsIndexValue2,
-};
+};*/
 
 
-const char* const haptic_text[2] = {
+/*const char* const haptic_text[2] = {
     "OFF",
     "OFF",
     "ON",
     "ON",
 };
 };
 const uint32_t haptic_value[2] = {
 const uint32_t haptic_value[2] = {
     XRemoteHapticOff,
     XRemoteHapticOff,
     XRemoteHapticOn,
     XRemoteHapticOn,
-};
+};*/
 
 
-const char* const speaker_text[2] = {
+/*const char* const speaker_text[2] = {
     "OFF",
     "OFF",
     "ON",
     "ON",
 };
 };
 const uint32_t speaker_value[2] = {
 const uint32_t speaker_value[2] = {
     XRemoteSpeakerOff,
     XRemoteSpeakerOff,
     XRemoteSpeakerOn,
     XRemoteSpeakerOn,
-};
+};*/
 
 
 const char* const led_text[2] = {
 const char* const led_text[2] = {
     "OFF",
     "OFF",
@@ -43,20 +43,20 @@ const uint32_t settings_value[2] = {
     XRemoteSettingsOn,
     XRemoteSettingsOn,
 };
 };
 
 
-static void xremote_scene_settings_set_haptic(VariableItem* item) {
+/*static void xremote_scene_settings_set_haptic(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     XRemote* app = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
     uint8_t index = variable_item_get_current_value_index(item);
 
 
     variable_item_set_current_value_text(item, haptic_text[index]);
     variable_item_set_current_value_text(item, haptic_text[index]);
     app->haptic = haptic_value[index];
     app->haptic = haptic_value[index];
-}
+}*/
 
 
-static void xremote_scene_settings_set_speaker(VariableItem* item) {
+/*static void xremote_scene_settings_set_speaker(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     XRemote* app = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
     uint8_t index = variable_item_get_current_value_index(item);
     variable_item_set_current_value_text(item, speaker_text[index]);
     variable_item_set_current_value_text(item, speaker_text[index]);
     app->speaker = speaker_value[index];
     app->speaker = speaker_value[index];
-}
+}*/
 
 
 static void xremote_scene_settings_set_led(VariableItem* item) {
 static void xremote_scene_settings_set_led(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     XRemote* app = variable_item_get_context(item);
@@ -92,18 +92,18 @@ void xremote_scene_settings_on_enter(void* context) {
     uint8_t value_index;
     uint8_t value_index;
 
 
     // Vibro on/off
     // Vibro on/off
-    item = variable_item_list_add(
+    /*    item = variable_item_list_add(
         app->variable_item_list, "Vibro/Haptic:", 2, xremote_scene_settings_set_haptic, app);
         app->variable_item_list, "Vibro/Haptic:", 2, xremote_scene_settings_set_haptic, app);
     value_index = value_index_uint32(app->haptic, haptic_value, 2);
     value_index = value_index_uint32(app->haptic, haptic_value, 2);
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, haptic_text[value_index]);
+    variable_item_set_current_value_text(item, haptic_text[value_index]);*/
 
 
     // Sound on/off
     // Sound on/off
-    item = variable_item_list_add(
+    /*   item = variable_item_list_add(
         app->variable_item_list, "Sound:", 2, xremote_scene_settings_set_speaker, app);
         app->variable_item_list, "Sound:", 2, xremote_scene_settings_set_speaker, app);
     value_index = value_index_uint32(app->speaker, speaker_value, 2);
     value_index = value_index_uint32(app->speaker, speaker_value, 2);
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, speaker_text[value_index]);
+    variable_item_set_current_value_text(item, speaker_text[value_index]);*/
 
 
     // LED Effects on/off
     // LED Effects on/off
     item = variable_item_list_add(
     item = variable_item_list_add(

+ 1 - 1
scenes/xremote_scene_sg_list.c

@@ -20,7 +20,7 @@ void xremote_scene_sg_list_on_enter(void* context) {
 
 
         xremote_sg_remote_load(app->sg_remote_buffer, app->file_path);
         xremote_sg_remote_load(app->sg_remote_buffer, app->file_path);
         //xremote_ir_remote_load(app->ir_remote_buffer, app->file_path);
         //xremote_ir_remote_load(app->ir_remote_buffer, app->file_path);
-        cross_remote_add_subghz(app->cross_remote, app->sg_remote_buffer);
+        xremote_cross_remote_add_subghz(app->cross_remote, app->sg_remote_buffer);
     }
     }
 
 
     if(success) {
     if(success) {

+ 53 - 24
scenes/xremote_scene_transmit.c

@@ -1,6 +1,7 @@
 #include "../xremote.h"
 #include "../xremote.h"
 #include "../views/xremote_transmit.h"
 #include "../views/xremote_transmit.h"
 #include "../models/infrared/xremote_ir_signal.h"
 #include "../models/infrared/xremote_ir_signal.h"
+#include "../helpers/subghz/subghz.h"
 
 
 static const NotificationSequence* xremote_notification_sequences[] = {
 static const NotificationSequence* xremote_notification_sequences[] = {
     &sequence_success,
     &sequence_success,
@@ -47,7 +48,7 @@ void xremote_scene_transmit_stop_ir_signal(XRemote* app) {
 }
 }
 
 
 void xremote_scene_transmit_send_ir_signal(XRemote* app, CrossRemoteItem* item) {
 void xremote_scene_transmit_send_ir_signal(XRemote* app, CrossRemoteItem* item) {
-    InfraredSignal* signal = xremote_remote_item_get_ir_signal(item);
+    InfraredSignal* signal = xremote_cross_remote_item_get_ir_signal(item);
     dolphin_deed(DolphinDeedIrSend);
     dolphin_deed(DolphinDeedIrSend);
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStartSend);
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStartSend);
     if(xremote_scene_ir_signal_is_raw(signal)) {
     if(xremote_scene_ir_signal_is_raw(signal)) {
@@ -62,7 +63,11 @@ void xremote_scene_transmit_send_ir_signal(XRemote* app, CrossRemoteItem* item)
         app->ir_worker, infrared_worker_tx_get_signal_steady_callback, app);
         app->ir_worker, infrared_worker_tx_get_signal_steady_callback, app);
     infrared_worker_tx_start(app->ir_worker);
     infrared_worker_tx_start(app->ir_worker);
     app->transmitting = true;
     app->transmitting = true;
-    furi_thread_flags_wait(0, FuriFlagWaitAny, app->ir_timing);
+    uint32_t time = app->ir_timing;
+    if(item->time > 0) {
+        time = item->time;
+    }
+    furi_thread_flags_wait(0, FuriFlagWaitAny, time);
     xremote_scene_transmit_stop_ir_signal(app);
     xremote_scene_transmit_stop_ir_signal(app);
 }
 }
 
 
@@ -75,13 +80,15 @@ void xremote_scene_transmit_send_pause(XRemote* app, CrossRemoteItem* item) {
 }
 }
 
 
 void xremote_scene_transmit_send_subghz(XRemote* app, CrossRemoteItem* item) {
 void xremote_scene_transmit_send_subghz(XRemote* app, CrossRemoteItem* item) {
-    UNUSED(item);
     app->transmitting = true;
     app->transmitting = true;
     xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStartSend);
     xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStartSend);
-    // ADD SEND METHOD HERE
-    furi_thread_flags_wait(0, FuriFlagWaitAny, 2000); //Remove later
-    app->transmitting = false;
-    xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
+    if(furi_string_utf8_length(item->filename) < 3) {
+        xremote_cross_remote_set_transmitting(app->cross_remote, XRemoteTransmittingStop);
+        app->transmitting = false;
+        return;
+    }
+    subghz_send(app, furi_string_get_cstr(item->filename));
+    //furi_thread_flags_wait(0, FuriFlagWaitAny, 2000);
 }
 }
 
 
 void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
 void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
@@ -93,17 +100,18 @@ void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
         return;
         return;
     }
     }
 
 
-    xremote_transmit_model_set_name(app->xremote_transmit, xremote_remote_item_get_name(item));
+    xremote_transmit_model_set_name(
+        app->xremote_transmit, xremote_cross_remote_item_get_name(item));
     xremote_transmit_model_set_type(app->xremote_transmit, item->type);
     xremote_transmit_model_set_type(app->xremote_transmit, item->type);
     if(item->type == XRemoteRemoteItemTypeInfrared) {
     if(item->type == XRemoteRemoteItemTypeInfrared) {
         xremote_scene_transmit_send_ir_signal(app, item);
         xremote_scene_transmit_send_ir_signal(app, item);
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
     } else if(item->type == XRemoteRemoteItemTypePause) {
     } else if(item->type == XRemoteRemoteItemTypePause) {
         xremote_scene_transmit_send_pause(app, item);
         xremote_scene_transmit_send_pause(app, item);
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
     } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
     } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
         xremote_scene_transmit_send_subghz(app, item);
         xremote_scene_transmit_send_subghz(app, item);
     }
     }
-
-    cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
 }
 }
 
 
 void xremote_scene_transmit_run_remote(void* context) {
 void xremote_scene_transmit_run_remote(void* context) {
@@ -111,24 +119,30 @@ void xremote_scene_transmit_run_remote(void* context) {
     XRemote* app = context;
     XRemote* app = context;
     CrossRemote* remote = app->cross_remote;
     CrossRemote* remote = app->cross_remote;
 
 
-    size_t item_count = cross_remote_get_item_count(remote);
+    size_t item_count = xremote_cross_remote_get_item_count(remote);
     for(size_t i = 0; i < item_count;) {
     for(size_t i = 0; i < item_count;) {
-        if(cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
-            cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
-            CrossRemoteItem* item = cross_remote_get_item(remote, i);
+        if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
+            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
+            CrossRemoteItem* item = xremote_cross_remote_get_item(remote, i);
             xremote_scene_transmit_send_signal(app, item);
             xremote_scene_transmit_send_signal(app, item);
-            //furi_thread_flags_wait(0, FuriFlagWaitAny, 2000);
-            xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStartSend);
-        } else if(cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
+            //furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
+        } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStopSubghz) {
+            i++;
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            app->transmitting = false;
+            subghz_txrx_stop(app->subghz->txrx);
+            xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
+            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+            //furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
+        } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
             i++;
             i++;
-            cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
         }
         }
     }
     }
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
 
 
-    //scene_manager_next_scene(app->scene_manager, XRemoteSceneXrList);
     scene_manager_previous_scene(app->scene_manager);
     scene_manager_previous_scene(app->scene_manager);
-    //xremote_transmit_model_set_name(app->xremote_transmit, cross_remote_get_name(remote));
+    //xremote_transmit_model_set_name(app->xremote_transmit, xremote_cross_remote_get_name(remote));
 }
 }
 
 
 void xremote_scene_transmit_on_enter(void* context) {
 void xremote_scene_transmit_on_enter(void* context) {
@@ -142,14 +156,29 @@ void xremote_scene_transmit_on_enter(void* context) {
 
 
 bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
 bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
     XRemote* app = context;
     XRemote* app = context;
-
-    UNUSED(app);
-    UNUSED(event);
     bool consumed = false;
     bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        FURI_LOG_D(TAG, "Custom Event");
+        switch(event.event) {
+        case XRemoteCustomEventViewTransmitterSendStop:
+            FURI_LOG_D("SUBGHZ", "stop event"); // doesn't trigger
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        FURI_LOG_D(TAG, "Tick Event");
+        if(app->state_notifications == SubGhzNotificationStateTx && app->led == 1) {
+            //blink for subghz
+        }
+    }
+
     return consumed;
     return consumed;
 }
 }
 
 
 void xremote_scene_transmit_on_exit(void* context) {
 void xremote_scene_transmit_on_exit(void* context) {
     XRemote* app = context;
     XRemote* app = context;
-    UNUSED(app);
+    app->transmitting = false;
+    app->state_notifications = SubGhzNotificationStateIDLE;
 }
 }

+ 1 - 1
scenes/xremote_scene_xr_list.c

@@ -21,7 +21,7 @@ void xremote_scene_xr_list_on_enter(void* context) {
 
 
     if(success) {
     if(success) {
         //Load Remote into buffer
         //Load Remote into buffer
-        success = cross_remote_load(app->cross_remote, app->file_path);
+        success = xremote_cross_remote_load(app->cross_remote, app->file_path);
     }
     }
 
 
     if(success) {
     if(success) {

+ 1 - 1
scenes/xremote_scene_xr_list_edit.c

@@ -16,7 +16,7 @@ void xremote_scene_xr_list_edit_on_enter(void* context) {
 
 
     if(success) {
     if(success) {
         //Load Remote into buffer
         //Load Remote into buffer
-        success = cross_remote_load(app->cross_remote, app->file_path);
+        success = xremote_cross_remote_load(app->cross_remote, app->file_path);
     }
     }
 
 
     if(success) {
     if(success) {

+ 2 - 2
scenes/xremote_scene_xr_list_edit_item.c

@@ -41,11 +41,11 @@ bool xremote_scene_xr_list_edit_item_on_event(void* context, SceneManagerEvent e
         return true;
         return true;
     } else if(event.type == SceneManagerEventTypeCustom) {
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexDelete) {
         if(event.event == SubmenuIndexDelete) {
-            cross_remote_delete(app->cross_remote);
+            xremote_cross_remote_delete(app->cross_remote);
         } else if(event.event == SubmenuIndexEdit) {
         } else if(event.event == SubmenuIndexEdit) {
             strncpy(
             strncpy(
                 app->text_store[0],
                 app->text_store[0],
-                cross_remote_get_name(app->cross_remote),
+                xremote_cross_remote_get_name(app->cross_remote),
                 XREMOTE_MAX_REMOTE_NAME_LENGTH);
                 XREMOTE_MAX_REMOTE_NAME_LENGTH);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
             return 0;
             return 0;

+ 1 - 1
views/xremote_pause_set.c

@@ -82,7 +82,7 @@ bool xremote_pause_set_input(InputEvent* event, void* context) {
                 XRemotePauseSetModel * model,
                 XRemotePauseSetModel * model,
                 {
                 {
                     XRemote* app = instance->context;
                     XRemote* app = instance->context;
-                    cross_remote_add_pause(app->cross_remote, model->time);
+                    xremote_cross_remote_add_pause(app->cross_remote, model->time);
                 },
                 },
                 true);
                 true);
 
 

+ 2 - 0
views/xremote_transmit.c

@@ -93,6 +93,8 @@ void xremote_transmit_draw_subghz(Canvas* canvas, XRemoteTransmitModel* model) {
 void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
 void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
     if(model->type == XRemoteRemoteItemTypeInfrared) {
     if(model->type == XRemoteRemoteItemTypeInfrared) {
         xremote_transmit_draw_ir(canvas, model);
         xremote_transmit_draw_ir(canvas, model);
+    } else if(model->type == XRemoteRemoteItemTypeSubGhz) {
+        xremote_transmit_draw_subghz(canvas, model);
     } else if(model->type == XRemoteRemoteItemTypePause) {
     } else if(model->type == XRemoteRemoteItemTypePause) {
         xremote_transmit_draw_pause(canvas, model);
         xremote_transmit_draw_pause(canvas, model);
     }
     }

+ 8 - 1
xremote.c

@@ -49,6 +49,7 @@ XRemote* xremote_app_alloc() {
     app->transmitting = 0;
     app->transmitting = 0;
     app->ir_timing = 1000;
     app->ir_timing = 1000;
     app->ir_timing_char = "1000";
     app->ir_timing_char = "1000";
+    app->stop_transmit = false;
 
 
     // Load configs
     // Load configs
     xremote_read_settings(app);
     xremote_read_settings(app);
@@ -58,12 +59,14 @@ XRemote* xremote_app_alloc() {
 
 
     app->ir_remote_buffer = xremote_ir_remote_alloc();
     app->ir_remote_buffer = xremote_ir_remote_alloc();
     app->ir_worker = infrared_worker_alloc();
     app->ir_worker = infrared_worker_alloc();
-    app->cross_remote = cross_remote_alloc();
+    app->cross_remote = xremote_cross_remote_alloc();
 
 
     app->sg_remote_buffer = xremote_sg_remote_alloc();
     app->sg_remote_buffer = xremote_sg_remote_alloc();
 
 
     app->loading = loading_alloc();
     app->loading = loading_alloc();
 
 
+    app->subghz = subghz_alloc();
+
     app->text_input = text_input_alloc();
     app->text_input = text_input_alloc();
     view_dispatcher_add_view(
     view_dispatcher_add_view(
         app->view_dispatcher, XRemoteViewIdTextInput, text_input_get_view(app->text_input));
         app->view_dispatcher, XRemoteViewIdTextInput, text_input_get_view(app->text_input));
@@ -123,6 +126,10 @@ void xremote_app_free(XRemote* app) {
 
 
     infrared_worker_free(app->ir_worker);
     infrared_worker_free(app->ir_worker);
 
 
+    xremote_cross_remote_free(app->cross_remote);
+
+    subghz_free(app->subghz);
+
     // View Dispatcher
     // View Dispatcher
     view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdMenu);
     view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdMenu);
     view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdCreate);
     view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdCreate);

+ 8 - 1
xremote.h

@@ -6,14 +6,19 @@
 #include "views/xremote_pause_set.h"
 #include "views/xremote_pause_set.h"
 #include "helpers/xremote_storage.h"
 #include "helpers/xremote_storage.h"
 #include "models/infrared/xremote_ir_remote.h"
 #include "models/infrared/xremote_ir_remote.h"
-#include "models/cross/xremote_remote.h"
+#include "models/cross/xremote_cross_remote.h"
+#include "helpers/subghz/subghz_types.h"
+#include "helpers/subghz/subghz.h"
 #include "xremote_i.h"
 #include "xremote_i.h"
 
 
+typedef struct SubGhz SubGhz;
+
 typedef struct {
 typedef struct {
     Gui* gui;
     Gui* gui;
     DialogsApp* dialogs;
     DialogsApp* dialogs;
     FuriString* file_path;
     FuriString* file_path;
     NotificationApp* notification;
     NotificationApp* notification;
+    SubGhzNotificationState state_notifications;
     ViewDispatcher* view_dispatcher;
     ViewDispatcher* view_dispatcher;
     Submenu* submenu;
     Submenu* submenu;
     Submenu* editmenu;
     Submenu* editmenu;
@@ -41,7 +46,9 @@ typedef struct {
     uint32_t ir_timing;
     uint32_t ir_timing;
     char* ir_timing_char;
     char* ir_timing_char;
     bool transmitting;
     bool transmitting;
+    bool stop_transmit;
     char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
     char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
+    SubGhz* subghz;
 } XRemote;
 } XRemote;
 
 
 typedef enum {
 typedef enum {

+ 3 - 1
xremote_i.h

@@ -87,6 +87,7 @@ typedef enum {
     XRemoteTransmittingIdle,
     XRemoteTransmittingIdle,
     XRemoteTransmittingStart,
     XRemoteTransmittingStart,
     XRemoteTransmittingStop,
     XRemoteTransmittingStop,
+    XRemoteTransmittingStopSubghz,
 } XRemoteRemoteTransmissionStatus;
 } XRemoteRemoteTransmissionStatus;
 
 
 struct InfraredSignal {
 struct InfraredSignal {
@@ -99,10 +100,11 @@ struct InfraredSignal {
 
 
 struct CrossRemoteItem {
 struct CrossRemoteItem {
     FuriString* name;
     FuriString* name;
+    FuriString* filename;
     InfraredSignal* ir_signal;
     InfraredSignal* ir_signal;
     SubGhzRemote* sg_signal;
     SubGhzRemote* sg_signal;
     int16_t type;
     int16_t type;
-    int32_t time;
+    uint32_t time;
 };
 };
 
 
 typedef struct CrossRemote CrossRemote;
 typedef struct CrossRemote CrossRemote;