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

Merge quac from https://github.com/xMasterX/all-the-plugins

# Conflicts:
#	quac/CHANGELOG.md
#	quac/application.fam
#	quac/quac.h
Willy-JL 10 месяцев назад
Родитель
Сommit
70eda9c763

+ 1 - 1
quac/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/quac 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/quac 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/rdefeo/quac master /

+ 8 - 0
quac/CHANGELOG.md

@@ -1,3 +1,11 @@
+## 0.8.0
+
+- Complete refactor of SubGhz code
+- Supports dynamic SubGhz signals with rolling codes
+- Auto-detect External SubGhz Antenna and use/prefer if available
+- Removed SugGhz External Antenna setting due to new auto-detect feature
+- Removed SubGhz Repeat setting, no longer needed (was non-standard)
+
 ## 0.7.2
 
 - Updated for firmware SDK 1.2

+ 3 - 2
quac/README.md

@@ -44,9 +44,11 @@ The app does not provide any recording functionality - you must use the existing
 
 The signal files are played back as recorded. During playback/transmit, the LED light will flash blue until the action is complete. For RFID, NFC, and iButton signals, they are continuously played back for their defined durations: RFID - 2.5 seconds, NFC - 1 second, iButton - 1 second. These defaults can be changed in [application Settings](README.md#application-settings).
 
+SubGhz signals that are dynamic (i.e. have rolling codes / counters) will be re-saved on playback, ensuring the persistence of the new counter values. If an External SubGhz antenna is available, it will be used.
+
 ## Signal Organization
 
-The key to organizing your Quac! interface is to organize your `/ext/apps_data/quac` folder structure. The UI is derived directly from the filesystem structure. Every individual file/signal is given a label on screen. And every folder/directory is a logical group of more files/folders. Selecting a group in the UI will show you the contents of that folder. There is no limit on the number of actions or folders - nest as deep as you want!
+The key to organizing your Quac! interface is to organize your `/ext/apps_quac` folder structure. The UI is derived directly from the filesystem structure. Every individual file/signal is given a label on screen. And every folder/directory is a logical group of more files/folders. Selecting a group in the UI will show you the contents of that folder. There is no limit on the number of actions or folders - nest as deep as you want!
 
 You can organize your files by device type, or by function. For example, you may have a folder of "TV" actions, which correspond to Channel Up, Channel Down, Volume Up, Volume Down, etc. Or you may have a "Work" folder, which contains files/actions that correspond to Parking Gate, Garage Door, Lobby Entrance.
 
@@ -124,7 +126,6 @@ The settings menu will appear as the last item when you are viewing the "root" d
 * RFID Duration: Changes the length of time a RFID signal is transmitted. Within playlists, this can be overridden per `.rfid` file.
 * NFC Duration: Changes the length of time a NFC signal is transmitted. Within playlists, this can be overridden per `.nfc` file.
 * iButton Duration: Changes the length of time a iButton signal is transmitted. Within playlists, this can be overridden per `.ibtn` file.
-* SubGhz Ext Ant: Whether to try using the external antenna for sub-ghz signals. If this is "Enabled" but no external antenna is attached, or the external antenna can't be accessed, Quac! will fall back to using the internal antenna.
 * IR Ext Ant: Whether to use the external device for IR signals. If enabled, but no external IR device is attached to TX, then the internal IR device will be used.
 * Show Hidden: Will display files and folders that start with a period (`.`)
 * About: Application info

+ 0 - 1
quac/README_flipperlab.md

@@ -46,7 +46,6 @@ The settings menu will appear as the last item when you are viewing the "root" d
 * RFID Duration: Changes the length of time a RFID signal is transmitted. Can be overridden, per RFID file in a Playlist
 * NFC Duration: Changes the length of time a NFC signal is transmitted. Can be overridden, per NFC file in a Playlist
 * iButton Duration: Changes the length of time a iButton signal is transmitted. Can be overridden, per iButton file in a Playlist
-* SubGhz Ext Ant: Enables / Disables use of external sub-ghz antenna
 * IR Ext Ant: Enables / Disables use of external IR antenna
 * Show Hidden: Toggles display of files/folders that start with a period.
 

+ 1 - 1
quac/actions/action.c

@@ -10,4 +10,4 @@ struct Item;
  * @param   item        Selected item to transmit
  * @param   error       Error message if unsuccessful
 */
-void action_tx(void* context, Item* item, FuriString* error);
+void action_tx(void* context, Item* item, FuriString* error);

+ 2 - 2
quac/actions/action_i.h

@@ -5,9 +5,9 @@
 
 #define ACTION_SET_ERROR(_msg_fmt, ...) furi_string_printf(error, _msg_fmt, ##__VA_ARGS__)
 
-void action_subghz_tx(void* context, const FuriString* action_path, FuriString* error);
+void action_subghz_tx(void* context, FuriString* action_path, FuriString* error);
 void action_rfid_tx(void* context, const FuriString* action_path, FuriString* error);
 void action_ir_tx(void* context, const FuriString* action_path, FuriString* error);
 void action_nfc_tx(void* context, const FuriString* action_path, FuriString* error);
 void action_ibutton_tx(void* context, const FuriString* action_path, FuriString* error);
-void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error);
+void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error);

+ 1 - 1
quac/actions/action_ibutton.c

@@ -42,4 +42,4 @@ void action_nfc_tx(void* context, const FuriString* action_path, FuriString* err
     nfc_device_clear(device); // probably not needed?
     nfc_free(nfc);
     nfc_device_free(device);
-}
+}

+ 1 - 1
quac/actions/action_qpl.c

@@ -12,7 +12,7 @@
 #include "action_i.h"
 #include "quac.h"
 
-#define RFID_FILE_TYPE    "Flipper RFID key"
+#define RFID_FILE_TYPE "Flipper RFID key"
 #define RFID_FILE_VERSION 1
 
 // lifted from flipperzero-firmware/applications/main/lfrfid/lfrfid_cli.c

+ 135 - 213
quac/actions/action_subghz.c

@@ -1,120 +1,113 @@
 // Methods for Sub-GHz transmission
 
-// subghz
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/devices/devices.h>
-#include <lib/subghz/devices/cc1101_configs.h>
-#include <lib/subghz/protocols/raw.h>
-#include <lib/subghz/subghz_protocol_registry.h>
+#include <flipper_format/flipper_format_i.h>
+#include <path.h>
+#include <string.h>
 
-#include <flipper_format/flipper_format.h>
+#include "helpers/subghz_txrx.h"
 
 #include "action_i.h"
 #include "quac.h"
 
-#define SUBGHZ_DEVICE_CC1101_EXT_NAME "cc1101_ext"
-#define SUBGHZ_DEVICE_CC1101_INT_NAME "cc1101_int"
-
-static FuriHalSubGhzPreset action_subghz_get_preset_name(const char* preset_name) {
-    FuriHalSubGhzPreset preset = FuriHalSubGhzPresetIDLE;
-    if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) {
-        preset = FuriHalSubGhzPresetOok270Async;
-    } else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) {
-        preset = FuriHalSubGhzPresetOok650Async;
-    } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev238Async")) {
-        preset = FuriHalSubGhzPreset2FSKDev238Async;
-    } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev476Async")) {
-        preset = FuriHalSubGhzPreset2FSKDev476Async;
-    } else if(!strcmp(preset_name, "FuriHalSubGhzPresetCustom")) {
-        preset = FuriHalSubGhzPresetCustom;
-    } else {
-        FURI_LOG_E(TAG, "SUBGHZ: Unknown preset!");
-    }
-    return preset;
-}
+#define SUBGHZ_DIR_PATH EXT_PATH("subghz/")
+
+typedef struct SubGhzNeedSaveContext {
+    App* app;
+    SubGhzTxRx* txrx;
+    FuriString* file_path;
+} SubGhzNeedSaveContext;
+
+void action_subghz_need_save_callback(void* context) {
+    FURI_LOG_I(TAG, "Saving udpated subghz signal");
+    SubGhzNeedSaveContext* savectx = (SubGhzNeedSaveContext*)context;
+    FlipperFormat* ff = subghz_txrx_get_fff_data(savectx->txrx);
 
-static const SubGhzDevice* action_subghz_get_device(uint32_t* device_ind) {
-    const SubGhzDevice* device = NULL;
-    switch(*device_ind) {
-    case 1: {
-        // Power on the external antenna
-        uint8_t attempts = 5;
-        while(--attempts > 0) {
-            if(furi_hal_power_enable_otg()) break;
+    Stream* ff_stream = flipper_format_get_raw_stream(ff);
+    flipper_format_delete_key(ff, "Repeat");
+    flipper_format_delete_key(ff, "Manufacture");
+
+    do {
+        if(!storage_simply_remove(
+               savectx->app->storage, furi_string_get_cstr(savectx->file_path))) {
+            FURI_LOG_E(TAG, "Failed to delete subghz file before re-save");
+            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);
-            }
+        stream_seek(ff_stream, 0, StreamOffsetFromStart);
+        stream_save_to_file(
+            ff_stream,
+            savectx->app->storage,
+            furi_string_get_cstr(savectx->file_path),
+            FSOM_CREATE_ALWAYS);
+        if(storage_common_stat(
+               savectx->app->storage, furi_string_get_cstr(savectx->file_path), NULL) != FSE_OK) {
+            FURI_LOG_E(TAG, "Error verifying new subghz file after re-save");
+            break;
         }
-        device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
-        break;
-    }
-    default:
-        device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
-        break;
-    }
-    if(!subghz_devices_is_connect(device)) {
-        // Power off
-        if(furi_hal_power_is_otg_enabled()) {
-            furi_hal_power_disable_otg();
+    } while(0);
+
+    // Update original .sub file.
+    //In case when rolling code was used in Quac we must update original .sub file with actual rolling code counter
+
+    // Take file name from quac_app path
+    FuriString* quac_filename = furi_string_alloc();
+    furi_string_reset(quac_filename);
+    path_extract_filename(savectx->file_path, quac_filename, false);
+    FURI_LOG_I(TAG, "Extracted quac filename: %s", furi_string_get_cstr(quac_filename));
+
+    //create new char string with full path (dir+filename) to original subghz folder
+    char* full_subghz_file_name =
+        malloc(1 + strlen(SUBGHZ_DIR_PATH) + strlen(furi_string_get_cstr(quac_filename)));
+    strcpy(full_subghz_file_name, SUBGHZ_DIR_PATH);
+    strcat(full_subghz_file_name, furi_string_get_cstr(quac_filename));
+    FURI_LOG_I(TAG, "Full path to safe file: %s", full_subghz_file_name);
+
+    //Save subghz file to original subghz location
+    do {
+        if(!storage_simply_remove(savectx->app->storage, full_subghz_file_name)) {
+            FURI_LOG_E(
+                TAG, "Failed to delete subghz file before re-save in original SUBGHZ location");
+            break;
         }
-        if(*device_ind == 1) {
-            FURI_LOG_W(TAG, "Can't connect to External antenna, using Internal");
+        stream_seek(ff_stream, 0, StreamOffsetFromStart);
+        stream_save_to_file(
+            ff_stream, savectx->app->storage, full_subghz_file_name, FSOM_CREATE_ALWAYS);
+        if(storage_common_stat(savectx->app->storage, full_subghz_file_name, NULL) != FSE_OK) {
+            FURI_LOG_E(
+                TAG, "Error verifying new subghz file after re-save in original SUBGHZ location");
+            break;
         }
-        device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
-        *device_ind = 0;
-    }
-    return device;
+    } while(0);
+
+    free(full_subghz_file_name);
+    furi_string_free(quac_filename);
 }
 
-// Lifted from flipperzero-firmware/applications/main/subghz/subghz_cli.c
-void action_subghz_tx(void* context, const FuriString* action_path, FuriString* error) {
+void action_subghz_tx(void* context, FuriString* action_path, FuriString* error) {
     App* app = context;
     const char* file_name = furi_string_get_cstr(action_path);
-    uint32_t repeat = app->settings.subghz_repeat; // Defaults to 10 in the CLI
-    uint32_t device_ind = app->settings.subghz_use_ext_antenna ? 1 : 0;
 
     FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
-    FlipperFormat* fff_data_raw = flipper_format_string_alloc();
+
+    SubGhzTxRx* txrx = subghz_txrx_alloc();
+
+    SubGhzNeedSaveContext save_context = {app, txrx, action_path};
+    subghz_txrx_set_need_save_callback(txrx, action_subghz_need_save_callback, &save_context);
+
+    Stream* fff_data_stream = flipper_format_get_raw_stream(subghz_txrx_get_fff_data(txrx));
+    stream_clean(fff_data_stream);
+
+    FuriString* preset_name = furi_string_alloc();
+    FuriString* protocol_name = furi_string_alloc();
+
     FuriString* temp_str;
     temp_str = furi_string_alloc();
     uint32_t temp_data32;
-    bool check_file = false;
-    const SubGhzDevice* device = NULL;
 
     uint32_t frequency = 0;
-    SubGhzTransmitter* transmitter = NULL;
 
     FURI_LOG_I(TAG, "SUBGHZ: Action starting...");
 
-    subghz_devices_init();
-    SubGhzEnvironment* environment = subghz_environment_alloc();
-    if(!subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) {
-        FURI_LOG_W(TAG, "Load_keystore keeloq_mfcodes - failed to load");
-    }
-    if(!subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) {
-        FURI_LOG_W(TAG, "Load_keystore keeloq_mfcodes_user - failed to load");
-    }
-    subghz_environment_set_came_atomo_rainbow_table_file_name(
-        environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
-    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
-        environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
-    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
-        environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
-    subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
-
     do {
-        device = action_subghz_get_device(&device_ind);
-        if(device == NULL) {
-            FURI_LOG_E(TAG, "Error device not found");
-            ACTION_SET_ERROR("SUBGHZ: Device not found");
-            break;
-        }
-
         if(!flipper_format_file_open_existing(fff_data_file, file_name)) {
             FURI_LOG_E(TAG, "Error opening %s", file_name);
             ACTION_SET_ERROR("SUBGHZ: Error opening %s", file_name);
@@ -136,14 +129,12 @@ void action_subghz_tx(void* context, const FuriString* action_path, FuriString*
             break;
         }
 
+        SubGhzSetting* setting = subghz_txrx_get_setting(txrx);
         if(!flipper_format_read_uint32(fff_data_file, "Frequency", &frequency, 1)) {
-            FURI_LOG_E(TAG, "Missing Frequency");
-            ACTION_SET_ERROR("SUBGHZ: Missing frequency");
-            break;
-        }
-
-        if(!subghz_devices_is_frequency_valid(device, frequency)) {
-            FURI_LOG_E(TAG, "Frequency not supported");
+            FURI_LOG_W(TAG, "Missing Frequency. Setting default frequency");
+            frequency = subghz_setting_get_default_frequency(setting);
+        } else if(!subghz_txrx_radio_device_is_frequecy_valid(txrx, frequency)) {
+            FURI_LOG_E(TAG, "Frequency not supported on the chosen radio module");
             ACTION_SET_ERROR("SUBGHZ: Frequency not supported");
             break;
         }
@@ -154,148 +145,79 @@ void action_subghz_tx(void* context, const FuriString* action_path, FuriString*
             break;
         }
 
-        FuriHalSubGhzPreset preset = action_subghz_get_preset_name(furi_string_get_cstr(temp_str));
-        if(preset == FuriHalSubGhzPresetIDLE) {
+        furi_string_set_str(
+            temp_str, subghz_txrx_get_preset_name(txrx, furi_string_get_cstr(temp_str)));
+        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
+            FURI_LOG_E(TAG, "Unknown preset");
             ACTION_SET_ERROR("SUBGHZ: Unknown preset");
             break;
         }
 
-        subghz_devices_begin(device);
-        subghz_devices_reset(device);
-        subghz_devices_idle(device);
-
-        if(preset == FuriHalSubGhzPresetCustom) {
-            uint8_t* custom_preset_data;
-            uint32_t custom_preset_data_size;
-            if(!flipper_format_get_value_count(fff_data_file, "Custom_preset_data", &temp_data32))
-                break;
-            if(!temp_data32 || (temp_data32 % 2)) {
-                FURI_LOG_E(TAG, "Custom_preset_data size error");
-                ACTION_SET_ERROR("SUBGHZ: Custom_preset_data size error");
-                break;
-            }
-            custom_preset_data_size = sizeof(uint8_t) * temp_data32;
-            custom_preset_data = malloc(custom_preset_data_size);
-            if(!flipper_format_read_hex(
-                   fff_data_file,
-                   "Custom_preset_data",
-                   custom_preset_data,
-                   custom_preset_data_size)) {
-                FURI_LOG_E(TAG, "Custom_preset_data read error");
-                ACTION_SET_ERROR("SUBGHZ: Custom_preset_data read error");
+        if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) {
+            subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str));
+            if(!subghz_setting_load_custom_preset(
+                   setting, furi_string_get_cstr(temp_str), fff_data_file)) {
+                FURI_LOG_E(TAG, "Missing Custom preset");
+                ACTION_SET_ERROR("SUBGHZ: Missing Custom preset");
                 break;
             }
-            subghz_devices_load_preset(device, preset, custom_preset_data);
-            free(custom_preset_data);
-        } else {
-            subghz_devices_load_preset(device, preset, NULL);
         }
-
-        subghz_devices_set_frequency(device, frequency);
+        furi_string_set(preset_name, temp_str);
+        size_t preset_index =
+            subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(preset_name));
+        subghz_txrx_set_preset(
+            txrx,
+            furi_string_get_cstr(preset_name),
+            frequency,
+            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)) {
+        if(!flipper_format_read_string(fff_data_file, "Protocol", protocol_name)) {
             FURI_LOG_E(TAG, "Missing protocol");
             ACTION_SET_ERROR("SUBGHZ: Missing protocol");
             break;
         }
 
-        SubGhzProtocolStatus status;
-        bool is_init_protocol = true;
-        if(furi_string_equal(temp_str, "RAW")) {
-            FURI_LOG_I(TAG, "Protocol = RAW");
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(txrx);
+        if(!strcmp(furi_string_get_cstr(protocol_name), "RAW")) {
             subghz_protocol_raw_gen_fff_data(
-                fff_data_raw, file_name, subghz_devices_get_name(device));
-            transmitter =
-                subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str));
-            if(transmitter == NULL) {
-                FURI_LOG_E(TAG, "Error transmitter");
-                is_init_protocol = false;
-            }
-
-            if(is_init_protocol) {
-                status = subghz_transmitter_deserialize(transmitter, fff_data_raw);
-                if(status != SubGhzProtocolStatusOk) {
-                    FURI_LOG_E(TAG, "Error deserialize protocol");
-                    is_init_protocol = false;
-                }
-            }
-        } else { // if not RAW protocol
-            FURI_LOG_I(TAG, "Protocol != RAW");
-            flipper_format_insert_or_update_uint32(fff_data_file, "Repeat", &repeat, 1);
-            transmitter =
-                subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str));
-            if(transmitter == NULL) {
-                FURI_LOG_E(TAG, "Error transmitter");
-                is_init_protocol = false;
-            }
-            if(is_init_protocol) {
-                status = subghz_transmitter_deserialize(transmitter, fff_data_file);
-                if(status != SubGhzProtocolStatusOk) {
-                    FURI_LOG_E(TAG, "Error deserialize protocol");
-                    ACTION_SET_ERROR("SUBGHZ: Protocol error");
-                    is_init_protocol = false;
-                }
-            }
+                fff_data, file_name, subghz_txrx_radio_device_get_name(txrx));
+        } else {
+            stream_copy_full(
+                flipper_format_get_raw_stream(fff_data_file),
+                flipper_format_get_raw_stream(fff_data));
         }
 
-        if(is_init_protocol) {
-            check_file = true;
-        } else {
-            subghz_devices_sleep(device);
-            subghz_devices_end(device);
-            if(transmitter != NULL) {
-                subghz_transmitter_free(transmitter);
+        if(subghz_txrx_load_decoder_by_name_protocol(txrx, furi_string_get_cstr(protocol_name))) {
+            SubGhzProtocolStatus status =
+                subghz_protocol_decoder_base_deserialize(subghz_txrx_get_decoder(txrx), fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                break;
             }
+        } else {
+            FURI_LOG_E(TAG, "Protocol not found: %s", furi_string_get_cstr(protocol_name));
+            break;
         }
     } while(false);
 
+    flipper_format_file_close(fff_data_file);
     flipper_format_free(fff_data_file);
 
-    if(check_file) {
-        furi_hal_power_suppress_charge_enter();
-        subghz_devices_set_tx(device);
-        FURI_LOG_I(
-            TAG,
-            "Transmitting at %s. Frequency=%lu, Protocol=%s",
-            file_name,
-            frequency,
-            furi_string_get_cstr(temp_str));
-        do {
-            // FURI_LOG_I(TAG, "delaying 200ms");
-            furi_delay_ms(100); // needed? orig 200
-            if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
-                while(!subghz_devices_is_async_complete_tx(device)) {
-                    // || cli_cmd_interrupt_received
-                    furi_delay_ms(100); // orig 333
-                }
-                subghz_devices_stop_async_tx(device);
-            } else {
-                FURI_LOG_W(TAG, "Transmission on this frequency is restricted in your region");
-            }
-
-            if(furi_string_equal(temp_str, "RAW")) {
-                subghz_transmitter_stop(transmitter);
-                repeat--;
-                // FURI_LOG_I(TAG, "decrementing repeat: %lu", repeat);
-                if(repeat) subghz_transmitter_deserialize(transmitter, fff_data_raw);
-            }
-
-        } while(repeat && furi_string_equal(temp_str, "RAW"));
-
-        subghz_devices_sleep(device);
-        subghz_devices_end(device);
-        // power off
-        if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
-
-        furi_hal_power_suppress_charge_exit();
-        subghz_transmitter_free(transmitter);
+    if(subghz_txrx_tx_start(txrx, subghz_txrx_get_fff_data(txrx)) != SubGhzTxRxStartTxStateOk) {
+        FURI_LOG_E(TAG, "Failed to start TX");
     }
 
+    // TODO: Should this be based on a Setting?
+    furi_delay_ms(100);
+
     FURI_LOG_I(TAG, "SUBGHZ: Action complete.");
 
-    flipper_format_free(fff_data_raw);
+    // This will call need_save_callback, if necessary
+    subghz_txrx_stop(txrx);
+
+    subghz_txrx_free(txrx);
+    furi_string_free(preset_name);
+    furi_string_free(protocol_name);
     furi_string_free(temp_str);
-    subghz_devices_deinit();
-    subghz_environment_free(environment);
 }

+ 613 - 0
quac/actions/helpers/subghz_txrx.c

@@ -0,0 +1,613 @@
+#include "subghz_txrx_i.h" // IWYU pragma: keep
+
+#include <lib/subghz/subghz_protocol_registry.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(void) {
+    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_I(
+        TAG,
+        "radio device type = %s",
+        instance->radio_device_type == SubGhzRadioDeviceTypeInternal ? "Internal" : "External");
+
+    return instance;
+}
+
+void subghz_txrx_free(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    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;
+}
+
+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;
+    }
+}
+
+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;
+    }
+
+    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;
+    do {
+        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;
+        }
+        ret = SubGhzTxRxStartTxStateOk;
+
+        SubGhzRadioPreset* preset = instance->preset;
+        instance->transmitter =
+            subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
+
+        if(instance->transmitter) {
+            if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
+               SubGhzProtocolStatusOk) {
+                if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
+                    subghz_txrx_begin(
+                        instance,
+                        subghz_setting_get_preset_data_by_name(
+                            instance->setting, furi_string_get_cstr(preset->name)));
+                    if(preset->frequency) {
+                        if(!subghz_txrx_tx(instance, preset->frequency)) {
+                            FURI_LOG_E(TAG, "Only Rx");
+                            ret = SubGhzTxRxStartTxStateErrorOnlyRx;
+                        }
+                    } 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
+                    subghz_devices_start_async_tx(
+                        instance->radio_device, subghz_transmitter_yield, instance->transmitter);
+                }
+            } else {
+                ret = SubGhzTxRxStartTxStateErrorParserOthers;
+            }
+        } else {
+            ret = SubGhzTxRxStartTxStateErrorParserOthers;
+        }
+        if(ret != SubGhzTxRxStartTxStateOk) {
+            subghz_transmitter_free(instance->transmitter);
+            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
quac/actions/helpers/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(void);
+
+/**
+ * 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
quac/actions/helpers/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;
+};

+ 91 - 0
quac/actions/helpers/subghz_types.h

@@ -0,0 +1,91 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzNotification state */
+typedef enum {
+    SubGhzNotificationStateStarting,
+    SubGhzNotificationStateIDLE,
+    SubGhzNotificationStateTx,
+    SubGhzNotificationStateRx,
+    SubGhzNotificationStateRxDone,
+} SubGhzNotificationState;
+
+/** SubGhzTxRx state */
+typedef enum {
+    SubGhzTxRxStateIDLE,
+    SubGhzTxRxStateRx,
+    SubGhzTxRxStateTx,
+    SubGhzTxRxStateSleep,
+} SubGhzTxRxState;
+
+/** SubGhzHopperState state */
+typedef enum {
+    SubGhzHopperStateOFF,
+    SubGhzHopperStateRunnig,
+    SubGhzHopperStatePause,
+    SubGhzHopperStateRSSITimeOut,
+} SubGhzHopperState;
+
+/** SubGhzSpeakerState state */
+typedef enum {
+    SubGhzSpeakerStateDisable,
+    SubGhzSpeakerStateShutdown,
+    SubGhzSpeakerStateEnable,
+} SubGhzSpeakerState;
+
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeAuto,
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
+/** SubGhzRxKeyState state */
+typedef enum {
+    SubGhzRxKeyStateIDLE,
+    SubGhzRxKeyStateNoSave,
+    SubGhzRxKeyStateNeedSave,
+    SubGhzRxKeyStateBack,
+    SubGhzRxKeyStateStart,
+    SubGhzRxKeyStateAddKey,
+    SubGhzRxKeyStateExit,
+    SubGhzRxKeyStateRAWLoad,
+    SubGhzRxKeyStateRAWMore,
+    SubGhzRxKeyStateRAWSave,
+} SubGhzRxKeyState;
+
+/** SubGhzLoadKeyState state */
+typedef enum {
+    SubGhzLoadKeyStateUnknown,
+    SubGhzLoadKeyStateOK,
+    SubGhzLoadKeyStateParseErr,
+    SubGhzLoadKeyStateProtocolDescriptionErr,
+} SubGhzLoadKeyState;
+
+/** SubGhzLock */
+typedef enum {
+    SubGhzLockOff,
+    SubGhzLockOn,
+} SubGhzLock;
+
+typedef enum {
+    SubGhzViewIdMenu,
+    SubGhzViewIdReceiver,
+    SubGhzViewIdPopup,
+    SubGhzViewIdTextInput,
+    SubGhzViewIdWidget,
+    SubGhzViewIdTransmitter,
+    SubGhzViewIdVariableItemList,
+    SubGhzViewIdFrequencyAnalyzer,
+    SubGhzViewIdReadRAW,
+
+} SubGhzViewId;
+
+/** SubGhz load type file */
+typedef enum {
+    SubGhzLoadTypeFileNoLoad,
+    SubGhzLoadTypeFileKey,
+    SubGhzLoadTypeFileRaw,
+} SubGhzLoadTypeFile;

+ 1 - 1
quac/application.fam

@@ -8,7 +8,7 @@ App(
     stack_size=2 * 1024,
     fap_category="Tools",
     # Optional values
-    fap_version="0.7.2",
+    fap_version="0.8.0",
     fap_icon="images/quac.png",  # 10x10 1-bit PNG
     fap_description="Quick Action remote control app",
     fap_author="Roberto De Feo",

+ 1 - 1
quac/images/.gitkeep

@@ -169,4 +169,4 @@ ItemType item_get_item_type_from_extension(const char* ext) {
         type = Item_Playlist;
     }
     return type;
-}
+}

+ 0 - 1
quac/item.h

@@ -5,7 +5,6 @@
 
 #include "item.h"
 #include "scenes/scenes.h"
-#include "scenes/scene_items.h"
 
 /* generated by fbt from .png files in images folder */
 #include <quac_icons.h>

+ 1 - 3
quac/quac.h

@@ -17,7 +17,7 @@
 #include "item.h"
 
 #define QUAC_NAME    "Quac!"
-#define QUAC_VERSION "v0.7.2"
+#define QUAC_VERSION "v0.8.0"
 #define QUAC_ABOUT                                    \
     "Quick Action remote control\n" QUAC_VERSION "\n" \
     "github.com/rdefeo/quac"
@@ -61,8 +61,6 @@ typedef struct App {
         uint32_t rfid_duration; // Defaults to 2500 ms
         uint32_t nfc_duration; // Defaults to 1000 ms
         uint32_t ibutton_duration; // Defaults to 1000 ms
-        uint32_t subghz_repeat; // Defaults to 10, just like the CLI
-        bool subghz_use_ext_antenna; // Defaults to False
         bool ir_use_ext_module; // Defaults to False
         bool show_hidden; // Defaults to False
     } settings;

+ 0 - 24
quac/quac_settings.c

@@ -13,8 +13,6 @@ void quac_set_default_settings(App* app) {
     app->settings.rfid_duration = 2500;
     app->settings.nfc_duration = 1000;
     app->settings.ibutton_duration = 1000;
-    app->settings.subghz_repeat = 10;
-    app->settings.subghz_use_ext_antenna = false;
     app->settings.ir_use_ext_module = false;
     app->settings.show_hidden = false;
 }
@@ -90,18 +88,6 @@ void quac_load_settings(App* app) {
             app->settings.ibutton_duration = temp_data32;
         }
 
-        if(!flipper_format_read_uint32(fff_settings, "SubGHz Repeat", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'SubGHz Repeat'");
-        } else {
-            app->settings.subghz_repeat = temp_data32;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "SubGHz Ext Antenna", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'SubGHz Ext Antenna'");
-        } else {
-            app->settings.subghz_use_ext_antenna = temp_data32 == 1;
-        }
-
         if(!flipper_format_read_uint32(fff_settings, "IR Ext Module", &temp_data32, 1)) {
             FURI_LOG_W(TAG, "SETTINGS: Missing 'IR Ext Module'");
         } else {
@@ -170,16 +156,6 @@ void quac_save_settings(App* app) {
             FURI_LOG_E(TAG, "SETTINGS: Failed to write 'iButton Duration'");
             break;
         }
-        if(!flipper_format_write_uint32(
-               fff_settings, "SubGHz Repeat", &app->settings.subghz_repeat, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'SubGHz Repeat'");
-            break;
-        }
-        temp_data32 = app->settings.subghz_use_ext_antenna ? 1 : 0;
-        if(!flipper_format_write_uint32(fff_settings, "SubGHz Ext Antenna", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'SubGHz Ext Antenna'");
-            break;
-        }
         temp_data32 = app->settings.ir_use_ext_module ? 1 : 0;
         if(!flipper_format_write_uint32(fff_settings, "IR Ext Module", &temp_data32, 1)) {
             FURI_LOG_E(TAG, "SETTINGS: Failed to write 'IR Ext Module'");

+ 3 - 4
quac/quac_settings.h

@@ -7,16 +7,15 @@
 #include "quac.h"
 #include "scenes.h"
 #include "scene_about.h"
-#include "../actions/action.h"
 #include "quac_icons.h"
 
 enum {
-    SceneActionRenameEvent,
+    SceneActionAboutEvent,
 };
 
 void scene_about_callback(void* context) {
     App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, SceneActionRenameEvent);
+    view_dispatcher_send_custom_event(app->view_dispatcher, SceneActionAboutEvent);
 }
 
 void scene_about_on_enter(void* context) {
@@ -38,4 +37,4 @@ bool scene_about_on_event(void* context, SceneManagerEvent event) {
 void scene_about_on_exit(void* context) {
     App* app = context;
     popup_reset(app->popup);
-}
+}

+ 1 - 1
quac/scenes/scene_about.h

@@ -88,4 +88,4 @@ bool scene_action_create_group_on_event(void* context, SceneManagerEvent event)
 void scene_action_create_group_on_exit(void* context) {
     App* app = context;
     text_input_reset(app->text_input);
-}
+}

+ 1 - 1
quac/scenes/scene_action_create_group.h

@@ -145,4 +145,4 @@ bool scene_action_ir_list_on_event(void* context, SceneManagerEvent event) {
 void scene_action_ir_list_on_exit(void* context) {
     App* app = context;
     submenu_reset(app->sub_menu);
-}
+}

+ 1 - 2
quac/scenes/scene_action_ir_list.h

@@ -7,7 +7,6 @@
 #include "quac.h"
 #include "scenes.h"
 #include "scene_action_rename.h"
-#include "../actions/action.h"
 
 #include <lib/toolbox/path.h>
 
@@ -101,4 +100,4 @@ bool scene_action_rename_on_event(void* context, SceneManagerEvent event) {
 void scene_action_rename_on_exit(void* context) {
     App* app = context;
     text_input_reset(app->text_input);
-}
+}

+ 1 - 1
quac/scenes/scene_action_rename.h

@@ -304,4 +304,4 @@ void scene_action_settings_on_exit(void* context) {
     ItemsView* new_items = item_get_items_view_from_path(app, app->items_view->path);
     item_items_view_free(app->items_view);
     app->items_view = new_items;
-}
+}

+ 1 - 1
quac/scenes/scene_action_settings.h

@@ -181,4 +181,4 @@ void scene_items_on_exit(void* context) {
     App* app = context;
     ActionMenu* menu = app->action_menu;
     action_menu_reset(menu);
-}
+}

+ 1 - 43
quac/scenes/scene_items.h

@@ -8,8 +8,6 @@
 #include "quac.h"
 #include "scenes.h"
 #include "scene_settings.h"
-#include "../actions/action.h"
-#include "../views/action_menu.h"
 #include "../quac_settings.h"
 
 #include <lib/toolbox/path.h>
@@ -19,7 +17,7 @@
 // dynamically know it's list index for our on_event method. However, we'll need to
 // hardcode the value..
 // TODO: Figure out a better way to do this
-#define SCENE_SETTINGS_ABOUT 10 // 11 items in our Settings list, so last index is 10
+#define SCENE_SETTINGS_ABOUT 8 // 9 items in our Settings list, so last index is 8
 
 static const char* const layout_text[2] = {"Vert", "Horiz"};
 static const uint32_t layout_value[2] = {QUAC_APP_PORTRAIT, QUAC_APP_LANDSCAPE};
@@ -49,19 +47,6 @@ static const uint32_t duration_value[V_DURATION_COUNT] = {
     10000,
 };
 
-#define V_REPEAT_COUNT 9
-static const char* const repeat_text[V_REPEAT_COUNT] = {
-    "1",
-    "2",
-    "3",
-    "5",
-    "8",
-    "10", // default
-    "15",
-    "20",
-    "50"};
-static const uint32_t repeat_value[V_REPEAT_COUNT] = {1, 2, 3, 5, 8, 10, 15, 20, 50};
-
 static const char* const disabled_enabled_text[2] = {"Disabled", "Enabled"};
 static const uint32_t disabled_enabled_value[2] = {false, true};
 
@@ -107,20 +92,6 @@ static void scene_settings_ibutton_duration_changed(VariableItem* item) {
     app->settings.ibutton_duration = duration_value[index];
 }
 
-static void scene_settings_subghz_repeat_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, repeat_text[index]);
-    app->settings.subghz_repeat = repeat_value[index];
-}
-
-static void scene_settings_subghz_ext_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, disabled_enabled_text[index]);
-    app->settings.subghz_use_ext_antenna = disabled_enabled_value[index];
-}
-
 static void scene_settings_ir_ext_changed(VariableItem* item) {
     App* app = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
@@ -186,19 +157,6 @@ void scene_settings_on_enter(void* context) {
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_text(item, duration_text[value_index]);
 
-    item = variable_item_list_add(
-        vil, "SubGHz Repeat", V_REPEAT_COUNT, scene_settings_subghz_repeat_changed, app);
-    value_index = value_index_uint32(app->settings.subghz_repeat, repeat_value, V_REPEAT_COUNT);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, repeat_text[value_index]);
-
-    item =
-        variable_item_list_add(vil, "SubGHz Ext Ant", 2, scene_settings_subghz_ext_changed, app);
-    value_index =
-        value_index_uint32(app->settings.subghz_use_ext_antenna, disabled_enabled_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, disabled_enabled_text[value_index]);
-
     item = variable_item_list_add(vil, "IR Ext Module", 2, scene_settings_ir_ext_changed, app);
     value_index = value_index_uint32(app->settings.ir_use_ext_module, disabled_enabled_value, 2);
     variable_item_set_current_value_index(item, value_index);

+ 1 - 0
quac/scenes/scene_settings.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <gui/scene_manager.h>
 typedef enum {
     QScene_Items,
     QScene_Settings,

+ 6 - 6
quac/screenshots/screenshot_1.png


+ 1 - 1
quac/views/action_menu.h

@@ -118,4 +118,4 @@ void action_menu_set_selected_item(ActionMenu* action_menu, uint32_t index);
 
 #ifdef __cplusplus
 }
-#endif
+#endif