Sfoglia il codice sorgente

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

also fix various issues and remove unused code
MX 10 mesi fa
parent
commit
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));