#include "raw.h" #include #include "../subghz_file_encoder_worker.h" #include "../blocks/const.h" #include "../blocks/decoder.h" #include "../blocks/encoder.h" #include "../blocks/generic.h" #include "../blocks/math.h" #include #include #include #include #define TAG "SubGhzProtocolRAW" #define SUBGHZ_DOWNLOAD_MAX_SIZE 512 #define SUBGHZ_AUTO_DETECT_DOWNLOAD_MAX_SIZE 2048 #define SUBGHZ_AUTO_DETECT_RAW_THRESHOLD -72.0f #define SUBGHZ_AUTO_DETECT_RAW_POSTROLL_FRAMES 40 static const SubGhzBlockConst subghz_protocol_raw_const = { .te_short = 50, .te_long = 32700, .te_delta = 0, .min_count_bit_for_found = 0, }; struct SubGhzProtocolDecoderRAW { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; int32_t* upload_raw; uint16_t ind_write; Storage* storage; FlipperFormat* flipper_file; uint32_t file_is_open; FuriString* file_name; size_t sample_write; bool last_level; bool auto_mode; bool has_rssi_above_threshold; int rssi_threshold; uint8_t postroll_frames; }; struct SubGhzProtocolEncoderRAW { SubGhzProtocolEncoderBase base; bool is_running; FuriString* file_name; SubGhzFileEncoderWorker* file_worker_encoder; }; typedef enum { RAWFileIsOpenClose = 0, RAWFileIsOpenWrite, RAWFileIsOpenRead, } RAWFilIsOpen; const SubGhzProtocolDecoder subghz_protocol_raw_decoder = { .alloc = subghz_protocol_decoder_raw_alloc, .free = subghz_protocol_decoder_raw_free, .feed = subghz_protocol_decoder_raw_feed, .reset = subghz_protocol_decoder_raw_reset, .get_hash_data = subghz_protocol_decoder_raw_get_hash_data, .serialize = subghz_protocol_decoder_raw_serialize, .deserialize = subghz_protocol_decoder_raw_deserialize, .get_string = subghz_protocol_decoder_raw_get_string, }; const SubGhzProtocolEncoder subghz_protocol_raw_encoder = { .alloc = subghz_protocol_encoder_raw_alloc, .free = subghz_protocol_encoder_raw_free, .deserialize = subghz_protocol_encoder_raw_deserialize, .stop = subghz_protocol_encoder_raw_stop, .yield = subghz_protocol_encoder_raw_yield, }; const SubGhzProtocol subghz_protocol_raw = { .name = SUBGHZ_PROTOCOL_RAW_NAME, .type = SubGhzProtocolTypeRAW, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_RAW | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_raw_decoder, .encoder = &subghz_protocol_raw_encoder, }; bool subghz_protocol_raw_save_to_file_init( SubGhzProtocolDecoderRAW* instance, const char* dev_name, SubGhzRadioPreset* preset) { furi_assert(instance); instance->storage = furi_record_open(RECORD_STORAGE); instance->flipper_file = flipper_format_file_alloc(instance->storage); FuriString* temp_str; temp_str = furi_string_alloc(); bool init = false; do { // Create subghz folder directory if necessary if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { break; } // Create saved directory if necessary if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { break; } furi_string_set(instance->file_name, dev_name); // First remove subghz device file if it was saved furi_string_printf(temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); if(!storage_simply_remove(instance->storage, furi_string_get_cstr(temp_str))) { break; } // Open file if(!flipper_format_file_open_always( instance->flipper_file, furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to open file for write: %s", furi_string_get_cstr(temp_str)); break; } if(!flipper_format_write_header_cstr( instance->flipper_file, SUBGHZ_RAW_FILE_TYPE, SUBGHZ_RAW_FILE_VERSION)) { FURI_LOG_E(TAG, "Unable to add header"); break; } if(!flipper_format_write_uint32( instance->flipper_file, "Frequency", &preset->frequency, 1)) { FURI_LOG_E(TAG, "Unable to add Frequency"); break; } subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); if(!flipper_format_write_string_cstr( instance->flipper_file, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); break; } if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( instance->flipper_file, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); break; } if(!flipper_format_write_hex( instance->flipper_file, "Custom_preset_data", preset->data, preset->data_size)) { FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); break; } } if(!flipper_format_write_string_cstr( instance->flipper_file, "Protocol", instance->base.protocol->name)) { FURI_LOG_E(TAG, "Unable to add Protocol"); break; } instance->upload_raw = malloc(SUBGHZ_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); instance->file_is_open = RAWFileIsOpenWrite; instance->sample_write = 0; init = true; } while(0); furi_string_free(temp_str); return init; } static bool subghz_protocol_raw_save_to_file_write(SubGhzProtocolDecoderRAW* instance) { furi_assert(instance); bool is_write = false; if(instance->file_is_open == RAWFileIsOpenWrite) { if(!flipper_format_write_int32( instance->flipper_file, "RAW_Data", instance->upload_raw, instance->ind_write)) { FURI_LOG_E(TAG, "Unable to add RAW_Data"); } else { instance->sample_write += instance->ind_write; instance->ind_write = 0; is_write = true; } } return is_write; } void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance) { furi_assert(instance); if(instance->file_is_open == RAWFileIsOpenWrite && instance->ind_write) subghz_protocol_raw_save_to_file_write(instance); if(instance->file_is_open != RAWFileIsOpenClose) { free(instance->upload_raw); instance->upload_raw = NULL; flipper_format_file_close(instance->flipper_file); flipper_format_free(instance->flipper_file); furi_record_close(RECORD_STORAGE); } instance->file_is_open = RAWFileIsOpenClose; } void subghz_protocol_decoder_raw_set_rssi_threshold(void* context, int rssi_threshold) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; FURI_LOG_D(TAG, "RSSI set: (%d)", rssi_threshold); instance->rssi_threshold = rssi_threshold; subghz_protocol_decoder_raw_reset(context); } void subghz_protocol_decoder_raw_set_auto_mode(void* context, bool auto_mode) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; instance->auto_mode = auto_mode; if(auto_mode) { if(instance->upload_raw == NULL) { instance->upload_raw = malloc(SUBGHZ_AUTO_DETECT_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); } } else { if(instance->upload_raw != NULL) { free(instance->upload_raw); instance->upload_raw = NULL; } } subghz_protocol_decoder_raw_reset(context); } size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance) { return instance->sample_write + instance->ind_write; } void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderRAW* instance = malloc(sizeof(SubGhzProtocolDecoderRAW)); instance->base.protocol = &subghz_protocol_raw; instance->upload_raw = NULL; instance->ind_write = 0; instance->last_level = false; instance->file_is_open = RAWFileIsOpenClose; instance->postroll_frames = 0; instance->rssi_threshold = SUBGHZ_AUTO_DETECT_RAW_THRESHOLD; instance->file_name = furi_string_alloc(); return instance; } void subghz_protocol_decoder_raw_free(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; furi_string_free(instance->file_name); if(instance->upload_raw != NULL) { free(instance->upload_raw); instance->upload_raw = NULL; } free(instance); } void subghz_protocol_decoder_raw_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; instance->ind_write = 0; instance->has_rssi_above_threshold = false; instance->last_level = false; instance->postroll_frames = 0; } bool subghz_protocol_decoder_raw_write_data(void* context, bool level, uint32_t duration) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; bool wrote_data = false; if(instance->last_level != level) { instance->last_level = (level ? true : false); instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); subghz_protocol_blocks_add_bit(&instance->decoder, (level) ? 1 : 0); wrote_data = true; } if(instance->ind_write == SUBGHZ_AUTO_DETECT_DOWNLOAD_MAX_SIZE) { if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); return false; } return wrote_data; } void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; if(instance->upload_raw != NULL && duration > subghz_protocol_raw_const.te_short) { if(instance->auto_mode) { float rssi = furi_hal_subghz_get_rssi(); if(rssi >= instance->rssi_threshold) { subghz_protocol_decoder_raw_write_data(context, level, duration); instance->has_rssi_above_threshold = true; instance->postroll_frames = 0; } else if(instance->has_rssi_above_threshold) { subghz_protocol_decoder_raw_write_data(instance, level, duration); instance->postroll_frames++; if(instance->postroll_frames >= SUBGHZ_AUTO_DETECT_RAW_POSTROLL_FRAMES) { if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); } } } else { if(instance->last_level != level) { instance->last_level = (level ? true : false); instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); subghz_protocol_blocks_add_bit(&instance->decoder, (level) ? 1 : 0); } if(instance->ind_write == SUBGHZ_DOWNLOAD_MAX_SIZE) { subghz_protocol_raw_save_to_file_write(instance); } } } } bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); UNUSED(context); UNUSED(flipper_format); //ToDo stub, for backwards compatibility return true; } uint8_t subghz_protocol_decoder_raw_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; return subghz_protocol_blocks_get_hash_data( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { furi_assert(context); //SubGhzProtocolDecoderRAW* instance = context; UNUSED(context); //ToDo no use furi_string_cat_printf(output, "RAW Data"); } void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolEncoderRAW* instance = malloc(sizeof(SubGhzProtocolEncoderRAW)); instance->base.protocol = &subghz_protocol_raw; instance->file_name = furi_string_alloc(); instance->is_running = false; return instance; } int subghz_protocol_encoder_get_rssi_threshold(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; return instance->rssi_threshold; } void subghz_protocol_encoder_raw_stop(void* context) { SubGhzProtocolEncoderRAW* instance = context; instance->is_running = false; if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { subghz_file_encoder_worker_stop(instance->file_worker_encoder); subghz_file_encoder_worker_free(instance->file_worker_encoder); } } void subghz_protocol_encoder_raw_free(void* context) { furi_assert(context); SubGhzProtocolEncoderRAW* instance = context; subghz_protocol_encoder_raw_stop(instance); furi_string_free(instance->file_name); free(instance); } void subghz_protocol_raw_file_encoder_worker_set_callback_end( SubGhzProtocolEncoderRAW* instance, SubGhzProtocolEncoderRAWCallbackEnd callback_end, void* context_end) { furi_assert(instance); furi_assert(callback_end); subghz_file_encoder_worker_callback_end( instance->file_worker_encoder, callback_end, context_end); } static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* instance) { furi_assert(instance); instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( instance->file_worker_encoder, furi_string_get_cstr(instance->file_name))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); instance->is_running = true; } else { subghz_protocol_encoder_raw_stop(instance); } return instance->is_running; } void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path) { do { stream_clean(flipper_format_get_raw_stream(flipper_format)); if(!flipper_format_write_string_cstr(flipper_format, "Protocol", "RAW")) { FURI_LOG_E(TAG, "Unable to add Protocol"); break; } if(!flipper_format_write_string_cstr(flipper_format, "File_name", file_path)) { FURI_LOG_E(TAG, "Unable to add File_name"); break; } } while(false); } bool subghz_protocol_decoder_raw_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; if(instance->auto_mode) { furi_assert(instance); bool res = false; FuriString* temp_str; temp_str = furi_string_alloc(); do { stream_clean(flipper_format_get_raw_stream(flipper_format)); if(!flipper_format_write_header_cstr( flipper_format, SUBGHZ_RAW_FILE_TYPE, SUBGHZ_RAW_FILE_VERSION)) { FURI_LOG_E(TAG, "Unable to add header"); break; } if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { FURI_LOG_E(TAG, "Unable to add Frequency"); break; } subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); break; } if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( flipper_format, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); break; } if(!flipper_format_write_hex( flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); break; } } if(!flipper_format_write_string_cstr( flipper_format, "Protocol", instance->base.protocol->name)) { FURI_LOG_E(TAG, "Unable to add Protocol"); break; } if(!flipper_format_write_int32( flipper_format, "RAW_Data", instance->upload_raw, instance->ind_write)) { FURI_LOG_E(TAG, "Unable to add Raw Data"); break; } else { instance->ind_write = 0; } res = true; } while(false); furi_string_free(temp_str); return res; } else { return false; } } bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderRAW* instance = context; bool res = false; FuriString* temp_str; temp_str = furi_string_alloc(); do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); break; } if(!flipper_format_read_string(flipper_format, "File_name", temp_str)) { FURI_LOG_E(TAG, "Missing File_name"); break; } furi_string_set(instance->file_name, temp_str); res = subghz_protocol_encoder_raw_worker_init(instance); } while(false); furi_string_free(temp_str); return res; } LevelDuration subghz_protocol_encoder_raw_yield(void* context) { SubGhzProtocolEncoderRAW* instance = context; if(!instance->is_running) return level_duration_reset(); return subghz_file_encoder_worker_get_level_duration(instance->file_worker_encoder); }