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

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
 - Infrared working
+- SubGhz working
 - Pause working
-- SubGhz in Development
-- Edit/Rename/Delete features in development
+- IR Timing features in development
 
 ## What this is?
 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>
 - 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
 - If you do not have one, download a firmware<br>
 - Plug your Flipper Zero in via USB. <br>

+ 2 - 10
application.fam

@@ -3,20 +3,12 @@ App(
     name="Cross Remote",
     apptype=FlipperAppType.EXTERNAL,
     entry_point="xremote_app",
-    cdefines=["APP_XREMOTE"],
-    requires=[
-        "gui",
-        "storage",
-        "dialogs",
-    ],
     stack_size=3 * 1024,
-    order=10,
-    fap_libs=["assets"],
     fap_icon="icons/xremote_10px.png",
     fap_icon_assets="icons",
-    fap_version="1.1",
+    fap_version="2.0",
     fap_category="Infrared",
     fap_author="Leedave",
     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
 
-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
 - Read out commands you recorded in the IR app
+- Read out commands you saved as .sub files
 - Combine commands to a chain/playlist 
 - Add pauses inbetween commands 
 - Save your command chain / playlist to flipper
 - Disable LED effects if not wanted
 - Configure duration of IR Signals
 
-## In Development
-- Ability to also add SubGhz commands 
-
 ## What good is this?
 
 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
-- Patched Memory Leak in storage
+- Patched memory leak in storage
 
 ## v1.0
 - 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,
 } SubGhzSpeakerState;
 
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeAuto,
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
 /** SubGhzRxKeyState state */
 typedef enum {
     SubGhzRxKeyStateIDLE,
@@ -62,18 +69,9 @@ typedef enum {
     SubGhzLockOn,
 } SubGhzLock;
 
+/** SubGhz load type file */
 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,
     XRemoteCustomEventPauseSetDown,
     XRemoteCustomEventPauseSetOk,
+
+    XRemoteCustomEventViewTransmitterSendStop,
 } XRemoteCustomEvent;
 
 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"
 
 #define TAG "Xremote"
 
 struct SubGhzRemote {
     FuriString* name;
+    FuriString* filename;
     FuriString* path;
-    SubGhzTxRx* txrx;
-    uint32_t frequency;
-    SubGhzSetting* setting;
 };
 
 const char* xremote_sg_remote_get_name(SubGhzRemote* remote) {
     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* remote = malloc(sizeof(SubGhzRemote));
     remote->name = furi_string_alloc();
+    remote->filename = 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;
 }
 
 void xremote_sg_remote_free(SubGhzRemote* remote) {
     furi_string_free(remote->path);
     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);
 }
 
-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) {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
 
     FuriString* buf;
     buf = furi_string_alloc();
-    uint32_t temp_data32;
 
     FURI_LOG_I(TAG, "loading SG Remote: \'%s\'", furi_string_get_cstr(path));
     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;
         const char* fullPath = furi_string_get_cstr(path);
         char* fileName = strrchr(fullPath, '/') + 1;
+        furi_string_set_str(remote->filename, fullPath);
         char* dotPosition = strrchr(fileName, '.');
         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
         }
         //remote->name = fileName;
         furi_string_set_str(remote->name, fileName);
+        //furi_string_set_str(remote->filename, fileName);
+        //free(fileName);
         uint32_t version;
         if(!flipper_format_read_header(ff, buf, &version)) 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;
     } while(false);
 
     furi_string_free(buf);
+    flipper_format_buffered_file_close(ff);
     flipper_format_free(ff);
     furi_record_close(RECORD_STORAGE);
     return success;

+ 10 - 9
models/subghz/xremote_sg_remote.h

@@ -1,21 +1,22 @@
 #pragma once
 
 //#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;
 
 const char* xremote_sg_remote_get_name(SubGhzRemote* remote);
+const char* xremote_sg_remote_get_filename(SubGhzRemote* remote);
 
 SubGhzRemote* xremote_sg_remote_alloc();
 
 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) {
         custom_type = XRemoteCustomEventMenuVoid;
     } else if(type == InputTypeShort) {
-        //somehow ButtonMenuItemTypeCommon uses InputTypeShort
         custom_type = XRemoteCustomEventMenuSelected;
     } else {
         furi_crash("Unexpected Input Type");
@@ -30,14 +29,13 @@ void xremote_scene_create_on_enter(void* context) {
     furi_assert(context);
     XRemote* app = context;
     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) {
-        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,
-            xremote_remote_item_get_name(item),
+            xremote_cross_remote_item_get_name(item),
             i,
             xremote_create_callback,
             ButtonMenuItemTypeCommon,
@@ -76,13 +74,6 @@ bool xremote_scene_create_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeBack) {
         scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu);
         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) {
         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);
@@ -101,25 +92,6 @@ bool xremote_scene_create_on_event(void* context, SceneManagerEvent event) {
             app->edit_item = button_index;
             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;

+ 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);
             }
             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) {
                 scene_manager_next_scene(app->scene_manager, XRemoteScenePauseSet);

+ 2 - 2
scenes/xremote_scene_edit_item.c

@@ -1,5 +1,5 @@
 #include "../xremote.h"
-#include "../models/cross/xremote_remote.h"
+#include "../models/cross/xremote_cross_remote.h"
 
 enum SubmenuIndexEdit {
     SubmenuIndexRename = 10,
@@ -32,7 +32,7 @@ bool xremote_scene_edit_item_on_event(void* context, SceneManagerEvent event) {
         return true;
     } else if(event.type == SceneManagerEventTypeCustom) {
         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) {
             scene_manager_next_scene(app->scene_manager, XRemoteSceneSaveRemoteItem);
             //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);
             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);
             consumed = true;
         }

+ 1 - 0
scenes/xremote_scene_menu.c

@@ -1,3 +1,4 @@
+#include "../helpers/subghz/subghz.h"
 #include "../xremote.h"
 
 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;
             break;
         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(
                 app->scene_manager, XRemoteSceneCreate);
             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
 
     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);
 
     furi_string_free(folder_path);
@@ -45,7 +47,7 @@ bool xremote_scene_save_remote_on_event(void* context, SceneManagerEvent event)
     if(event.type == SceneManagerEventTypeCustom) {
         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) {
             scene_manager_next_scene(scene_manager, XRemoteSceneMenu);

+ 3 - 3
scenes/xremote_scene_save_remote_item.c

@@ -1,5 +1,5 @@
 #include "../xremote.h"
-#include "../models/cross/xremote_remote.h"
+#include "../models/cross/xremote_cross_remote.h"
 
 #include <string.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");
 
     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);
     text_input_set_result_callback(
         text_input,
@@ -31,7 +31,7 @@ bool xremote_scene_save_remote_item_on_event(void* context, SceneManagerEvent ev
     bool consumed = false;
 
     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);
         consumed = true;
     }

+ 14 - 14
scenes/xremote_scene_settings.c

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

+ 53 - 24
scenes/xremote_scene_transmit.c

@@ -1,6 +1,7 @@
 #include "../xremote.h"
 #include "../views/xremote_transmit.h"
 #include "../models/infrared/xremote_ir_signal.h"
+#include "../helpers/subghz/subghz.h"
 
 static const NotificationSequence* xremote_notification_sequences[] = {
     &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) {
-    InfraredSignal* signal = xremote_remote_item_get_ir_signal(item);
+    InfraredSignal* signal = xremote_cross_remote_item_get_ir_signal(item);
     dolphin_deed(DolphinDeedIrSend);
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStartSend);
     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);
     infrared_worker_tx_start(app->ir_worker);
     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);
 }
 
@@ -75,13 +80,15 @@ void xremote_scene_transmit_send_pause(XRemote* app, CrossRemoteItem* item) {
 }
 
 void xremote_scene_transmit_send_subghz(XRemote* app, CrossRemoteItem* item) {
-    UNUSED(item);
     app->transmitting = true;
     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) {
@@ -93,17 +100,18 @@ void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
         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);
     if(item->type == XRemoteRemoteItemTypeInfrared) {
         xremote_scene_transmit_send_ir_signal(app, item);
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
     } else if(item->type == XRemoteRemoteItemTypePause) {
         xremote_scene_transmit_send_pause(app, item);
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
     } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
         xremote_scene_transmit_send_subghz(app, item);
     }
-
-    cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
 }
 
 void xremote_scene_transmit_run_remote(void* context) {
@@ -111,24 +119,30 @@ void xremote_scene_transmit_run_remote(void* context) {
     XRemote* app = context;
     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;) {
-        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);
-            //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++;
-            cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
         }
     }
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
 
-    //scene_manager_next_scene(app->scene_manager, XRemoteSceneXrList);
     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) {
@@ -142,14 +156,29 @@ void xremote_scene_transmit_on_enter(void* context) {
 
 bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
     XRemote* app = context;
-
-    UNUSED(app);
-    UNUSED(event);
     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;
 }
 
 void xremote_scene_transmit_on_exit(void* 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) {
         //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) {

+ 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) {
         //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) {

+ 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;
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexDelete) {
-            cross_remote_delete(app->cross_remote);
+            xremote_cross_remote_delete(app->cross_remote);
         } else if(event.event == SubmenuIndexEdit) {
             strncpy(
                 app->text_store[0],
-                cross_remote_get_name(app->cross_remote),
+                xremote_cross_remote_get_name(app->cross_remote),
                 XREMOTE_MAX_REMOTE_NAME_LENGTH);
             scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
             return 0;

+ 1 - 1
views/xremote_pause_set.c

@@ -82,7 +82,7 @@ bool xremote_pause_set_input(InputEvent* event, void* context) {
                 XRemotePauseSetModel * model,
                 {
                     XRemote* app = instance->context;
-                    cross_remote_add_pause(app->cross_remote, model->time);
+                    xremote_cross_remote_add_pause(app->cross_remote, model->time);
                 },
                 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) {
     if(model->type == XRemoteRemoteItemTypeInfrared) {
         xremote_transmit_draw_ir(canvas, model);
+    } else if(model->type == XRemoteRemoteItemTypeSubGhz) {
+        xremote_transmit_draw_subghz(canvas, model);
     } else if(model->type == XRemoteRemoteItemTypePause) {
         xremote_transmit_draw_pause(canvas, model);
     }

+ 8 - 1
xremote.c

@@ -49,6 +49,7 @@ XRemote* xremote_app_alloc() {
     app->transmitting = 0;
     app->ir_timing = 1000;
     app->ir_timing_char = "1000";
+    app->stop_transmit = false;
 
     // Load configs
     xremote_read_settings(app);
@@ -58,12 +59,14 @@ XRemote* xremote_app_alloc() {
 
     app->ir_remote_buffer = xremote_ir_remote_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->loading = loading_alloc();
 
+    app->subghz = subghz_alloc();
+
     app->text_input = text_input_alloc();
     view_dispatcher_add_view(
         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);
 
+    xremote_cross_remote_free(app->cross_remote);
+
+    subghz_free(app->subghz);
+
     // View Dispatcher
     view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdMenu);
     view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdCreate);

+ 8 - 1
xremote.h

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

+ 3 - 1
xremote_i.h

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