Jelajahi Sumber

remake subghz playlist to work with dynamic keys, and use txrx lib

also fix various issues and remove unused code
MX 8 bulan lalu
induk
melakukan
0c74099ad1

+ 1 - 1
application.fam

@@ -4,7 +4,7 @@ App(
     apptype=FlipperAppType.EXTERNAL,
     entry_point="playlist_app",
     requires=["storage", "gui", "dialogs", "subghz"],
-    stack_size=2 * 1024,
+    stack_size=4 * 1024,
     order=14,
     fap_icon="playlist_10px.png",
     fap_category="Sub-GHz",

+ 0 - 64
helpers/radio_device_loader.c

@@ -1,64 +0,0 @@
-#include "radio_device_loader.h"
-
-#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
-#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
-
-static void radio_device_loader_power_on() {
-    uint8_t attempts = 0;
-    while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
-        furi_hal_power_enable_otg();
-        //CC1101 power-up time
-        furi_delay_ms(10);
-    }
-}
-
-static void radio_device_loader_power_off() {
-    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
-}
-
-bool radio_device_loader_is_connect_external(const char* name) {
-    bool is_connect = false;
-    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
-
-    if(!is_otg_enabled) {
-        radio_device_loader_power_on();
-    }
-
-    const SubGhzDevice* device = subghz_devices_get_by_name(name);
-    if(device) {
-        is_connect = subghz_devices_is_connect(device);
-    }
-
-    if(!is_otg_enabled) {
-        radio_device_loader_power_off();
-    }
-    return is_connect;
-}
-
-const SubGhzDevice* radio_device_loader_set(
-    const SubGhzDevice* current_radio_device,
-    SubGhzRadioDeviceType radio_device_type) {
-    const SubGhzDevice* radio_device;
-
-    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
-       radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
-        radio_device_loader_power_on();
-        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
-        subghz_devices_begin(radio_device);
-    } else if(current_radio_device == NULL) {
-        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
-    } else {
-        radio_device_loader_end(current_radio_device);
-        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
-    }
-
-    return radio_device;
-}
-
-void radio_device_loader_end(const SubGhzDevice* radio_device) {
-    furi_assert(radio_device);
-    radio_device_loader_power_off();
-    if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) {
-        subghz_devices_end(radio_device);
-    }
-}

+ 0 - 15
helpers/radio_device_loader.h

@@ -1,15 +0,0 @@
-#pragma once
-
-#include <lib/subghz/devices/devices.h>
-
-/** SubGhzRadioDeviceType */
-typedef enum {
-    SubGhzRadioDeviceTypeInternal,
-    SubGhzRadioDeviceTypeExternalCC1101,
-} SubGhzRadioDeviceType;
-
-const SubGhzDevice* radio_device_loader_set(
-    const SubGhzDevice* current_radio_device,
-    SubGhzRadioDeviceType radio_device_type);
-
-void radio_device_loader_end(const SubGhzDevice* radio_device);

+ 615 - 0
helpers/subghz_txrx.c

@@ -0,0 +1,615 @@
+#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>
+
+#include <lib/subghz/protocols/raw.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
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
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
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;

+ 316 - 191
playlist.c

@@ -11,26 +11,25 @@
 #include <lib/subghz/protocols/protocol_items.h>
 #include <flipper_format/flipper_format_i.h>
 
-#include "helpers/radio_device_loader.h"
+#include "helpers/subghz_txrx.h"
+#include <lib/subghz/blocks/custom_btn.h>
+#include <lib/subghz/protocols/raw.h>
 
 #include "flipper_format_stream.h"
 #include "flipper_format_stream_i.h"
 
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/protocols/raw.h>
-
 #include "playlist_file.h"
 #include "canvas_helper.h"
 
 #define PLAYLIST_FOLDER "/ext/subplaylist"
-#define PLAYLIST_EXT ".txt"
-#define TAG "Playlist"
+#define PLAYLIST_EXT    ".txt"
+#define TAG             "Playlist"
 
-#define STATE_NONE 0
+#define STATE_NONE     0
 #define STATE_OVERVIEW 1
-#define STATE_SENDING 2
+#define STATE_SENDING  2
 
-#define WIDTH 128
+#define WIDTH  128
 #define HEIGHT 64
 
 typedef struct {
@@ -60,7 +59,8 @@ typedef struct {
     DisplayMeta* meta;
 
     FuriString* file_path; // path to the playlist file
-    const SubGhzDevice* radio_device;
+
+    SubGhzTxRx* txrx; // subghz txrx instance
 
     bool ctl_request_exit; // can be set to true if the worker should exit
     bool ctl_pause; // can be set to true if the worker should pause
@@ -68,6 +68,8 @@ typedef struct {
     bool ctl_request_prev; // can be set to true if the worker should go to the previous file
 
     bool is_running; // indicates if the worker is running
+
+    bool raw_file_is_tx;
 } PlaylistWorker;
 
 typedef struct {
@@ -89,26 +91,42 @@ void meta_set_state(DisplayMeta* meta, int state) {
     view_port_update(meta->view_port);
 }
 
-static FuriHalSubGhzPreset str_to_preset(FuriString* preset) {
-    if(furi_string_cmp_str(preset, "FuriHalSubGhzPresetOok270Async") == 0) {
-        return FuriHalSubGhzPresetOok270Async;
-    }
-    if(furi_string_cmp_str(preset, "FuriHalSubGhzPresetOok650Async") == 0) {
-        return FuriHalSubGhzPresetOok650Async;
-    }
-    if(furi_string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev238Async") == 0) {
-        return FuriHalSubGhzPreset2FSKDev238Async;
-    }
-    if(furi_string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev476Async") == 0) {
-        return FuriHalSubGhzPreset2FSKDev476Async;
-    }
-    if(furi_string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) {
-        return FuriHalSubGhzPresetMSK99_97KbAsync;
-    }
-    if(furi_string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) {
-        return FuriHalSubGhzPresetMSK99_97KbAsync;
-    }
-    return FuriHalSubGhzPresetCustom;
+typedef struct SubGhzNeedSaveContext {
+    PlaylistWorker* worker;
+    SubGhzTxRx* txrx;
+    char* file_path;
+} SubGhzNeedSaveContext;
+
+static void playlist_subghz_need_save_callback(void* context) {
+    FURI_LOG_I(TAG, "Saving updated subghz signal");
+    SubGhzNeedSaveContext* savectx = (SubGhzNeedSaveContext*)context;
+
+    FlipperFormat* ff = subghz_txrx_get_fff_data(savectx->txrx);
+
+    Stream* ff_stream = flipper_format_get_raw_stream(ff);
+    flipper_format_delete_key(ff, "Repeat");
+
+    do {
+        if(!storage_simply_remove(savectx->worker->storage, savectx->file_path)) {
+            FURI_LOG_E(TAG, "Failed to delete subghz file before re-save");
+            break;
+        }
+        stream_seek(ff_stream, 0, StreamOffsetFromStart);
+        stream_save_to_file(
+            ff_stream, savectx->worker->storage, savectx->file_path, FSOM_CREATE_ALWAYS);
+        if(storage_common_stat(savectx->worker->storage, savectx->file_path, NULL) != FSE_OK) {
+            FURI_LOG_E(TAG, "Error verifying new subghz file after re-save");
+            break;
+        }
+    } while(0);
+}
+
+void playlist_subghz_raw_end_callback(void* context) {
+    FURI_LOG_I(TAG, "Stopping TX on RAW");
+    furi_assert(context);
+    PlaylistWorker* worker = context;
+
+    worker->raw_file_is_tx = false;
 }
 
 // -4: missing protocol
@@ -118,146 +136,278 @@ static FuriHalSubGhzPreset str_to_preset(FuriString* preset) {
 // 0: ok
 // 1: resend
 // 2: exited
-static int playlist_worker_process(
-    PlaylistWorker* worker,
-    FlipperFormat* fff_file,
-    FlipperFormat* fff_data,
-    const char* path,
-    FuriString* preset,
-    FuriString* protocol) {
+static int playlist_worker_process(PlaylistWorker* worker, const char* file_name) {
     // actual sending of .sub file
 
-    if(!flipper_format_file_open_existing(fff_file, path)) {
-        FURI_LOG_E(TAG, "  (TX) Failed to open %s", path);
-        return -1;
-    }
+    FuriString* preset_name = furi_string_alloc();
+    FuriString* protocol_name = furi_string_alloc();
+    FuriString* temp_str = furi_string_alloc();
 
-    // read frequency or default to 433.92MHz
-    uint32_t frequency = 0;
-    if(!flipper_format_read_uint32(fff_file, "Frequency", &frequency, 1)) {
-        FURI_LOG_W(TAG, "  (TX) Missing Frequency, defaulting to 433.92MHz");
-        frequency = 433920000;
-    }
-    if(!subghz_devices_is_frequency_valid(worker->radio_device, frequency)) {
-        FURI_LOG_E(
-            TAG, "  (TX) The SubGhz device used does not support the frequency %lu", frequency);
-        return -2;
-    }
+    uint8_t status = 0;
 
-    // check if preset is present
-    if(!flipper_format_read_string(fff_file, "Preset", preset)) {
-        FURI_LOG_E(TAG, "  (TX) Missing Preset");
-        return -3;
-        // load preset
-    } else {
-                uint32_t temp_data32;
-                uint8_t* custom_preset_data;
-                uint32_t custom_preset_data_size;
-
-                if(!strcmp(furi_string_get_cstr(preset), "FuriHalSubGhzPresetCustom")) {
-
-                    if(!flipper_format_get_value_count(fff_file, "Custom_preset_data", &temp_data32))
-                        return -3;
-                    if(!temp_data32 || (temp_data32 % 2)) {
-                        FURI_LOG_E(TAG, "  (TX) Missing Custom preset data");
-                        return -3;
-                    }
-                    custom_preset_data_size = sizeof(uint8_t) * temp_data32;
-                    custom_preset_data = malloc(custom_preset_data_size);
-                    if(!flipper_format_read_hex(
-                           fff_file,
-                           "Custom_preset_data",
-                           custom_preset_data,
-                           custom_preset_data_size)) {
-                        FURI_LOG_E(TAG, " (TX) Custom preset data read error");
-                        return -3;
-                    }
-                    subghz_devices_load_preset(worker->radio_device,str_to_preset(preset),custom_preset_data);
-                    free(custom_preset_data);
-                    FURI_LOG_D(TAG, "  (TX) Custom preset loaded ");
-                } else {
-                    subghz_devices_load_preset(worker->radio_device, str_to_preset(preset), NULL);
-                    FURI_LOG_D(TAG, "  (TX) Standart preset loaded ");
-                }
+    do {
+        FlipperFormat* fff_data_file = flipper_format_file_alloc(worker->storage);
 
-    }
+        SubGhzNeedSaveContext save_context = {worker, worker->txrx, (char*)file_name};
+        subghz_txrx_set_need_save_callback(
+            worker->txrx, playlist_subghz_need_save_callback, &save_context);
 
-    // check if protocol is present
-    if(!flipper_format_read_string(fff_file, "Protocol", protocol)) {
-        FURI_LOG_E(TAG, "  (TX) Missing Protocol");
-        return -4;
-    }
+        Stream* fff_data_stream =
+            flipper_format_get_raw_stream(subghz_txrx_get_fff_data(worker->txrx));
+        stream_clean(fff_data_stream);
 
-    if(!furi_string_cmp_str(protocol, "RAW")) {
-        subghz_protocol_raw_gen_fff_data(
-            fff_data, path, subghz_devices_get_name(worker->radio_device));
-    } else {
-        stream_copy_full(
-            flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data));
-    }
-    flipper_format_file_close(fff_file);
-    flipper_format_free(fff_file);
+        furi_string_reset(temp_str);
+        furi_string_reset(preset_name);
+        furi_string_reset(protocol_name);
 
-    // (try to) send file
-    SubGhzEnvironment* environment = subghz_environment_alloc();
-    subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
-    SubGhzTransmitter* transmitter =
-        subghz_transmitter_alloc_init(environment, furi_string_get_cstr(protocol));
+        worker->raw_file_is_tx = false;
 
-    subghz_transmitter_deserialize(transmitter, fff_data);
+        subghz_custom_btns_reset();
 
-    frequency = subghz_devices_set_frequency(worker->radio_device, frequency);
+        uint32_t temp_data32;
 
-    // Set device to TX and check frequency is alowed to TX
-    if(!subghz_devices_set_tx(worker->radio_device)) {
-        FURI_LOG_E(
-            TAG,
-            "  (TX) The SubGhz device used does not support the frequency for transmitеing, %lu",
-            frequency);
+        uint32_t frequency = 0;
 
-        subghz_devices_idle(worker->radio_device);
+        if(!flipper_format_file_open_existing(fff_data_file, file_name)) {
+            FURI_LOG_E(TAG, "Error opening %s", file_name);
+            flipper_format_free(fff_data_file);
 
-        subghz_transmitter_free(transmitter);
-        subghz_environment_free(environment);
-        return -5;
-    }
-    FURI_LOG_D(TAG, "  (TX) Start sending ...");
-    int status = 0;
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
 
-    subghz_devices_start_async_tx(worker->radio_device, subghz_transmitter_yield, transmitter);
-    while(!subghz_devices_is_async_complete_tx(worker->radio_device)) {
-        if(worker->ctl_request_exit) {
-            FURI_LOG_D(TAG, "    (TX) Requested to exit. Cancelling sending...");
-            status = 2;
-            break;
+            return -1;
         }
-        if(worker->ctl_pause) {
-            FURI_LOG_D(TAG, "    (TX) Requested to pause. Cancelling and resending...");
-            status = 1;
-            break;
+
+        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
+            FURI_LOG_E(TAG, "Missing or incorrect header");
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
+
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -1;
         }
-        if(worker->ctl_request_skip) {
-            worker->ctl_request_skip = false;
-            FURI_LOG_D(TAG, "    (TX) Requested to skip. Cancelling and resending...");
-            status = 0;
-            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");
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
+
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -1;
         }
-        if(worker->ctl_request_prev) {
-            worker->ctl_request_prev = false;
-            FURI_LOG_D(TAG, "    (TX) Requested to prev. Cancelling and resending...");
-            status = 3;
-            break;
+
+        SubGhzSetting* setting = subghz_txrx_get_setting(worker->txrx);
+        if(!flipper_format_read_uint32(fff_data_file, "Frequency", &frequency, 1)) {
+            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(worker->txrx, frequency)) {
+            FURI_LOG_E(TAG, "Frequency not supported on the chosen radio module");
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
+
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -1;
         }
-        furi_delay_ms(50);
-    }
 
-    FURI_LOG_D(TAG, "  (TX) Done sending.");
+        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Preset");
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
 
-    subghz_devices_stop_async_tx(worker->radio_device);
-    subghz_devices_idle(worker->radio_device);
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -3;
+        }
 
-    subghz_transmitter_free(transmitter);
-    subghz_environment_free(environment);
+        furi_string_set_str(
+            temp_str, subghz_txrx_get_preset_name(worker->txrx, furi_string_get_cstr(temp_str)));
+        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
+            FURI_LOG_E(TAG, "Unknown preset");
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
+
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -3;
+        }
+
+        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");
+                flipper_format_file_close(fff_data_file);
+                flipper_format_free(fff_data_file);
+
+                furi_string_free(preset_name);
+                furi_string_free(protocol_name);
+                furi_string_free(temp_str);
+
+                return -1;
+            }
+        }
+        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(
+            worker->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", protocol_name)) {
+            FURI_LOG_E(TAG, "Missing protocol");
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
+
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -4;
+        }
+
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(worker->txrx);
+        if(!strcmp(furi_string_get_cstr(protocol_name), "RAW")) {
+            subghz_protocol_raw_gen_fff_data(
+                fff_data, file_name, subghz_txrx_radio_device_get_name(worker->txrx));
+        } else {
+            stream_copy_full(
+                flipper_format_get_raw_stream(fff_data_file),
+                flipper_format_get_raw_stream(fff_data));
+        }
+
+        uint32_t repeat = 200;
+        if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &repeat, 1)) {
+            FURI_LOG_E(TAG, "Unable Repeat");
+            //
+        }
+
+        if(subghz_txrx_load_decoder_by_name_protocol(
+               worker->txrx, furi_string_get_cstr(protocol_name))) {
+            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
+                subghz_txrx_get_decoder(worker->txrx), fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                flipper_format_file_close(fff_data_file);
+                flipper_format_free(fff_data_file);
+
+                furi_string_free(preset_name);
+                furi_string_free(protocol_name);
+                furi_string_free(temp_str);
+
+                return -2;
+            }
+        } else {
+            FURI_LOG_E(TAG, "Protocol not found: %s", furi_string_get_cstr(protocol_name));
+            flipper_format_file_close(fff_data_file);
+            flipper_format_free(fff_data_file);
+
+            furi_string_free(preset_name);
+            furi_string_free(protocol_name);
+            furi_string_free(temp_str);
+
+            return -2;
+        }
+
+        flipper_format_file_close(fff_data_file);
+        flipper_format_free(fff_data_file);
+
+        FURI_LOG_I(TAG, "Starting TX");
+
+        if(subghz_txrx_tx_start(worker->txrx, subghz_txrx_get_fff_data(worker->txrx)) !=
+           SubGhzTxRxStartTxStateOk) {
+            FURI_LOG_E(TAG, "Failed to start TX");
+        }
+
+        bool skip_extra_stop = false;
+        FURI_LOG_D(TAG, "Checking if file is RAW...");
+        if(!strcmp(furi_string_get_cstr(protocol_name), "RAW")) {
+            subghz_txrx_set_raw_file_encoder_worker_callback_end(
+                worker->txrx, playlist_subghz_raw_end_callback, worker);
+            worker->raw_file_is_tx = true;
+            skip_extra_stop = true;
+        }
+
+        do {
+            if(worker->ctl_request_exit) {
+                FURI_LOG_D(TAG, "    (TX) Requested to exit. Cancelling sending...");
+                status = 2;
+                break;
+            }
+            if(worker->ctl_pause) {
+                FURI_LOG_D(TAG, "    (TX) Requested to pause. Cancelling...");
+                status = 1;
+                break;
+            }
+            if(worker->ctl_request_skip) {
+                worker->ctl_request_skip = false;
+                FURI_LOG_D(TAG, "    (TX) Requested to skip. Cancelling and skipping...");
+                status = 0;
+                break;
+            }
+            if(worker->ctl_request_prev) {
+                worker->ctl_request_prev = false;
+                FURI_LOG_D(TAG, "    (TX) Requested to prev. Cancelling and resending...");
+                status = 3;
+                break;
+            }
+            furi_delay_ms(1);
+        } while(worker->raw_file_is_tx);
+
+        if(!worker->raw_file_is_tx && !skip_extra_stop) {
+            if(worker->ctl_request_exit) {
+                FURI_LOG_D(TAG, "    (TX) Requested to exit. Cancelling sending...");
+                status = 2;
+            }
+            if(worker->ctl_pause) {
+                FURI_LOG_D(TAG, "    (TX) Requested to pause. Cancelling...");
+                status = 1;
+            }
+            if(worker->ctl_request_skip) {
+                worker->ctl_request_skip = false;
+                FURI_LOG_D(TAG, "    (TX) Requested to skip. Cancelling and skipping...");
+                status = 0;
+            }
+            if(worker->ctl_request_prev) {
+                worker->ctl_request_prev = false;
+                FURI_LOG_D(TAG, "    (TX) Requested to prev. Cancelling and resending...");
+                status = 3;
+            }
+            furi_delay_ms(1600);
+            subghz_txrx_stop(worker->txrx);
+        } else {
+            furi_delay_ms(20);
+            subghz_txrx_stop(worker->txrx);
+        }
+        skip_extra_stop = false;
+
+    } while(false);
+
+    FURI_LOG_D(TAG, "TX Finished");
+    subghz_custom_btns_reset();
+
+    furi_string_free(preset_name);
+    furi_string_free(protocol_name);
+    furi_string_free(temp_str);
 
     return status;
 }
@@ -292,12 +442,8 @@ void updatePlayListView(PlaylistWorker* worker, const char* str) {
 
 static bool playlist_worker_play_playlist_once(
     PlaylistWorker* worker,
-    Storage* storage,
     FlipperFormat* fff_head,
-    FlipperFormat* fff_data,
-    FuriString* data,
-    FuriString* preset,
-    FuriString* protocol) {
+    FuriString* data) {
     //
     if(!flipper_format_rewind(fff_head)) {
         FURI_LOG_E(TAG, "Failed to rewind file");
@@ -330,17 +476,15 @@ static bool playlist_worker_play_playlist_once(
 
             FURI_LOG_D(TAG, "(worker) Sending %s", str);
 
-            FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+            int status = playlist_worker_process(worker, str);
 
-            int status =
-                playlist_worker_process(worker, fff_file, fff_data, str, preset, protocol);
+            FURI_LOG_D(TAG, "Finished file, status %d", status);
 
             // if there was an error, fff_file is not already freed
             // why do you do this with numbers without meaning x_x
             if(status < 0) {
                 if(status > -5) {
-                    flipper_format_file_close(fff_file);
-                    flipper_format_free(fff_file);
+                    //
                 }
 
                 furi_check(
@@ -383,28 +527,21 @@ static bool playlist_worker_play_playlist_once(
 }
 
 static int32_t playlist_worker_thread(void* ctx) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* fff_head = flipper_format_file_alloc(storage);
-
     PlaylistWorker* worker = ctx;
+
+    FlipperFormat* fff_head = flipper_format_file_alloc(worker->storage);
+
     if(!flipper_format_file_open_existing(fff_head, furi_string_get_cstr(worker->file_path))) {
         FURI_LOG_E(TAG, "Failed to open %s", furi_string_get_cstr(worker->file_path));
         worker->is_running = false;
 
-        furi_record_close(RECORD_STORAGE);
         flipper_format_free(fff_head);
         return 0;
     }
 
     playlist_worker_wait_pause(worker);
-    FlipperFormat* fff_data = flipper_format_string_alloc();
 
-    FuriString* data;
-    FuriString* preset;
-    FuriString* protocol;
-    data = furi_string_alloc();
-    preset = furi_string_alloc();
-    protocol = furi_string_alloc();
+    FuriString* data = furi_string_alloc();
 
     for(int i = 0; i < MAX(1, worker->meta->playlist_repetitions); i++) {
         // infinite repetitions if playlist_repetitions is 0
@@ -425,20 +562,14 @@ static int32_t playlist_worker_thread(void* ctx) {
             worker->meta->current_playlist_repetition,
             worker->meta->playlist_repetitions);
 
-        if(!playlist_worker_play_playlist_once(
-               worker, storage, fff_head, fff_data, data, preset, protocol)) {
+        if(!playlist_worker_play_playlist_once(worker, fff_head, data)) {
             break;
         }
     }
 
-    furi_record_close(RECORD_STORAGE);
     flipper_format_free(fff_head);
 
     furi_string_free(data);
-    furi_string_free(preset);
-    furi_string_free(protocol);
-
-    flipper_format_free(fff_data);
 
     FURI_LOG_D(TAG, "Done reading. Read %d data lines.", worker->meta->current_count);
     worker->is_running = false;
@@ -488,22 +619,18 @@ PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) {
 
     instance->thread = furi_thread_alloc();
     furi_thread_set_name(instance->thread, "PlaylistWorker");
-    furi_thread_set_stack_size(instance->thread, 2048);
+    furi_thread_set_stack_size(instance->thread, 6 * 1024);
     furi_thread_set_context(instance->thread, instance);
     furi_thread_set_callback(instance->thread, playlist_worker_thread);
 
+    instance->storage = furi_record_open(RECORD_STORAGE);
+
     instance->meta = meta;
     instance->ctl_pause = true; // require the user to manually start the worker
 
     instance->file_path = furi_string_alloc();
 
-    subghz_devices_init();
-
-    instance->radio_device =
-        radio_device_loader_set(instance->radio_device, SubGhzRadioDeviceTypeExternalCC1101);
-
-    subghz_devices_reset(instance->radio_device);
-    subghz_devices_idle(instance->radio_device);
+    instance->txrx = subghz_txrx_alloc();
 
     return instance;
 }
@@ -513,10 +640,9 @@ void playlist_worker_free(PlaylistWorker* instance) {
     furi_thread_free(instance->thread);
     furi_string_free(instance->file_path);
 
-    subghz_devices_sleep(instance->radio_device);
-    radio_device_loader_end(instance->radio_device);
+    subghz_txrx_free(instance->txrx);
 
-    subghz_devices_deinit();
+    furi_record_close(RECORD_STORAGE);
 
     free(instance);
 }
@@ -760,10 +886,9 @@ void playlist_start_worker(Playlist* app, DisplayMeta* meta) {
     app->worker = playlist_worker_alloc(meta);
 
     // count playlist items
-    Storage* storage = furi_record_open(RECORD_STORAGE);
+
     app->meta->total_count =
-        playlist_count_playlist_items(storage, furi_string_get_cstr(app->file_path));
-    furi_record_close(RECORD_STORAGE);
+        playlist_count_playlist_items(app->worker->storage, furi_string_get_cstr(app->file_path));
 
     // start thread
     playlist_worker_start(app->worker, furi_string_get_cstr(app->file_path));