diff --git a/applications/subghz/helpers/subghz_types.h b/applications/subghz/helpers/subghz_types.h index 55ed40f84..d6d6d025f 100644 --- a/applications/subghz/helpers/subghz_types.h +++ b/applications/subghz/helpers/subghz_types.h @@ -81,3 +81,8 @@ struct SubGhzPresetDefinition { }; typedef struct SubGhzPresetDefinition SubGhzPresetDefinition; + +typedef enum { + SubGhzViewReceiverModeLive, + SubGhzViewReceiverModeFile, +} SubGhzViewReceiverMode; diff --git a/applications/subghz/scenes/subghz_scene_config.h b/applications/subghz/scenes/subghz_scene_config.h index 47cfe8212..c2731910b 100644 --- a/applications/subghz/scenes/subghz_scene_config.h +++ b/applications/subghz/scenes/subghz_scene_config.h @@ -29,6 +29,7 @@ ADD_SCENE(subghz, set_seed_bft, SetSeedBft) ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer) ADD_SCENE(subghz, read_raw, ReadRAW) ADD_SCENE(subghz, more_raw, MoreRAW) +ADD_SCENE(subghz, decode_raw, DecodeRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) diff --git a/applications/subghz/scenes/subghz_scene_decode_raw.c b/applications/subghz/scenes/subghz_scene_decode_raw.c new file mode 100644 index 000000000..ae7719437 --- /dev/null +++ b/applications/subghz/scenes/subghz_scene_decode_raw.c @@ -0,0 +1,272 @@ +#include "../subghz_i.h" +#include "../views/receiver.h" + +#include + +#define TAG "SubGhzDecodeRaw" +#define SAMPLES_TO_READ_PER_TICK 400 + +// TODO: +// [X] Remember RAW file after decoding +// [X] Decode in tick events instead of on_enter +// [X] Make "Config" label optional in subghz_view_receiver_draw (../views/receiver.c) +// [X] Make "Scanning..." label optional in subghz_view_receiver_draw (../views/receiver.c) +// [X] Add Decoding logo +// [ ] Design nicer Decoding logo +// [X] Check progress in stream_buffer, instead of raw stream +// [X] Blink led while decoding +// [X] Stop rx blink (blue, fast) on history item view +// [X] Don't reparse file on back +// [X] Fix: RX animation+LED returning from decoded detail view +// [X] Find good value for SAMPLES_TO_READ_PER_TICK +// [X] Fix: read errors (slow flash) after aborting decode read + +typedef enum { + SubGhzDecodeRawStateStart, + SubGhzDecodeRawStateLoading, + SubGhzDecodeRawStateLoaded, +} SubGhzDecodeRawState; + +SubGhzDecodeRawState decode_raw_state = SubGhzDecodeRawStateStart; +SubGhzFileEncoderWorker* file_worker_encoder; + +static void subghz_scene_receiver_update_statusbar(void* context) { + SubGhz* subghz = context; + string_t history_stat_str; + string_init(history_stat_str); + if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) { + string_t frequency_str; + string_t modulation_str; + + string_init(frequency_str); + string_init(modulation_str); + + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + + subghz_view_receiver_add_data_statusbar( + subghz->subghz_receiver, + string_get_cstr(frequency_str), + string_get_cstr(modulation_str), + string_get_cstr(history_stat_str)); + + string_clear(frequency_str); + string_clear(modulation_str); + } else { + subghz_view_receiver_add_data_statusbar( + subghz->subghz_receiver, string_get_cstr(history_stat_str), "", ""); + } + string_clear(history_stat_str); +} + +void subghz_scene_decode_raw_callback(SubGhzCustomEvent event, void* context) { + furi_assert(context); + SubGhz* subghz = context; + view_dispatcher_send_custom_event(subghz->view_dispatcher, event); +} + +static void subghz_scene_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + SubGhz* subghz = context; + string_t str_buff; + string_init(str_buff); + + if(subghz_history_add_to_history(subghz->txrx->history, decoder_base, subghz->txrx->preset)) { + string_reset(str_buff); + + subghz->state_notifications = SubGhzNotificationStateRxDone; + + subghz_history_get_text_item_menu( + subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1); + subghz_view_receiver_add_item_to_menu( + subghz->subghz_receiver, + string_get_cstr(str_buff), + subghz_history_get_type_protocol( + subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1)); + + subghz_scene_receiver_update_statusbar(subghz); + } + subghz_receiver_reset(receiver); + string_clear(str_buff); +} + +bool subghz_scene_decode_raw_start(SubGhz* subghz) { + string_t file_name; + string_init(file_name); + bool success = false; + do { + if(!flipper_format_rewind(subghz->txrx->fff_data)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(subghz->txrx->fff_data, "File_name", file_name)) { + FURI_LOG_E(TAG, "Missing File_name"); + break; + } + + success = true; + } while(false); + + if(success) { + FURI_LOG_I(TAG, "Listening at \033[0;33m%s\033[0m.", string_get_cstr(file_name)); + + file_worker_encoder = subghz_file_encoder_worker_alloc(); + if(subghz_file_encoder_worker_start(file_worker_encoder, string_get_cstr(file_name))) { + //the worker needs a file in order to open and read part of the file + furi_delay_ms(100); + } else { + success = false; + } + + if(!success) { + subghz_file_encoder_worker_free(file_worker_encoder); + } + } + + string_clear(file_name); + return success; +} + +bool subghz_scene_decode_raw_next(SubGhz* subghz) { + LevelDuration level_duration; + + for(uint32_t read = SAMPLES_TO_READ_PER_TICK; read > 0; --read) { + level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder); + if(!level_duration_is_reset(level_duration)) { + bool level = level_duration_get_level(level_duration); + uint32_t duration = level_duration_get_duration(level_duration); + subghz_receiver_decode(subghz->txrx->receiver, level, duration); + } else { + decode_raw_state = SubGhzDecodeRawStateLoaded; + subghz->state_notifications = SubGhzNotificationStateIDLE; + + subghz_view_receiver_add_data_progress(subghz->subghz_receiver, "100%"); + return false; // No more samples available + } + } + + // Update progress info + string_t progress_str; + string_init(progress_str); + subghz_file_encoder_worker_get_text_progress(file_worker_encoder, progress_str); + + subghz_view_receiver_add_data_progress(subghz->subghz_receiver, string_get_cstr(progress_str)); + + string_clear(progress_str); + + return true; // More samples available +} + +void subghz_scene_decode_raw_on_enter(void* context) { + SubGhz* subghz = context; + + string_t str_buff; + string_init(str_buff); + + subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock); + subghz_view_receiver_set_mode(subghz->subghz_receiver, SubGhzViewReceiverModeFile); + subghz_view_receiver_set_callback( + subghz->subghz_receiver, subghz_scene_decode_raw_callback, subghz); + + subghz_receiver_set_rx_callback( + subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); + + if(decode_raw_state == SubGhzDecodeRawStateStart) { + //Decode RAW to history + subghz_history_reset(subghz->txrx->history); + if(subghz_scene_decode_raw_start(subghz)) { + decode_raw_state = SubGhzDecodeRawStateLoading; + subghz->state_notifications = SubGhzNotificationStateRx; + } + } else { + //Load history to receiver + subghz_view_receiver_exit(subghz->subghz_receiver); + for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { + string_reset(str_buff); + subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i); + subghz_view_receiver_add_item_to_menu( + subghz->subghz_receiver, + string_get_cstr(str_buff), + subghz_history_get_type_protocol(subghz->txrx->history, i)); + } + string_clear(str_buff); + subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); + } + + subghz_scene_receiver_update_statusbar(subghz); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); +} + +bool subghz_scene_decode_raw_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SubGhzCustomEventViewReceiverBack: + decode_raw_state = SubGhzDecodeRawStateStart; + subghz->txrx->idx_menu_chosen = 0; + subghz_receiver_set_rx_callback(subghz->txrx->receiver, NULL, subghz); + + if(subghz_file_encoder_worker_is_running(file_worker_encoder)) { + subghz_file_encoder_worker_stop(file_worker_encoder); + } + subghz_file_encoder_worker_free(file_worker_encoder); + + subghz->state_notifications = SubGhzNotificationStateIDLE; + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneMoreRAW); + consumed = true; + break; + case SubGhzCustomEventViewReceiverOK: + subghz->txrx->idx_menu_chosen = + subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); + subghz->state_notifications = SubGhzNotificationStateIDLE; + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); + consumed = true; + break; + case SubGhzCustomEventViewReceiverConfig: + FURI_LOG_I(TAG, "No config options"); + consumed = true; + break; + case SubGhzCustomEventViewReceiverOffDisplay: + notification_message(subghz->notifications, &sequence_display_backlight_off); + consumed = true; + break; + case SubGhzCustomEventViewReceiverUnlock: + subghz->lock = SubGhzLockOff; + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + switch(subghz->state_notifications) { + case SubGhzNotificationStateRx: + notification_message(subghz->notifications, &sequence_blink_cyan_10); + break; + case SubGhzNotificationStateRxDone: + notification_message(subghz->notifications, &subghs_sequence_rx); + subghz->state_notifications = SubGhzNotificationStateRx; + break; + default: + break; + } + + switch(decode_raw_state) { + case SubGhzDecodeRawStateLoading: + subghz_scene_decode_raw_next(subghz); + break; + default: + break; + } + } + return consumed; +} + +void subghz_scene_decode_raw_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/subghz/scenes/subghz_scene_more_raw.c b/applications/subghz/scenes/subghz_scene_more_raw.c index a5bade927..7c1b8f7bf 100644 --- a/applications/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/subghz/scenes/subghz_scene_more_raw.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" enum SubmenuIndex { + SubmenuIndexDecode, SubmenuIndexEdit, SubmenuIndexDelete, }; @@ -13,6 +14,13 @@ void subghz_scene_more_raw_submenu_callback(void* context, uint32_t index) { void subghz_scene_more_raw_on_enter(void* context) { SubGhz* subghz = context; + submenu_add_item( + subghz->submenu, + "Decode", + SubmenuIndexDecode, + subghz_scene_more_raw_submenu_callback, + subghz); + submenu_add_item( subghz->submenu, "Rename", @@ -50,6 +58,11 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; + } else if(event.event == SubmenuIndexDecode) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDecode); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDecodeRAW); + return true; } } return false; diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/subghz/scenes/subghz_scene_receiver.c index 7c1f016d0..9d65072ce 100644 --- a/applications/subghz/scenes/subghz_scene_receiver.c +++ b/applications/subghz/scenes/subghz_scene_receiver.c @@ -1,7 +1,7 @@ #include "../subghz_i.h" #include "../views/receiver.h" -static const NotificationSequence subghs_sequence_rx = { +const NotificationSequence subghs_sequence_rx = { &message_green_255, &message_vibro_on, @@ -14,7 +14,7 @@ static const NotificationSequence subghs_sequence_rx = { NULL, }; -static const NotificationSequence subghs_sequence_rx_locked = { +const NotificationSequence subghs_sequence_rx_locked = { &message_green_255, &message_display_backlight_on, @@ -109,6 +109,7 @@ void subghz_scene_receiver_on_enter(void* context) { } subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock); + subghz_view_receiver_set_mode(subghz->subghz_receiver, SubGhzViewReceiverModeLive); //Load history to receiver subghz_view_receiver_exit(subghz->subghz_receiver); diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 1f6e73572..894c7984b 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -137,3 +137,6 @@ void subghz_file_name_clear(SubGhz* subghz); bool subghz_path_is_file(string_t path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); + +extern const NotificationSequence subghs_sequence_rx; +extern const NotificationSequence subghs_sequence_rx_locked; diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index c28c33636..234372ff4 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -55,13 +55,25 @@ typedef struct { string_t frequency_str; string_t preset_str; string_t history_stat_str; + string_t progress_str; SubGhzReceiverHistory* history; uint16_t idx; uint16_t list_offset; uint16_t history_item; SubGhzViewReceiverBarShow bar_show; + SubGhzViewReceiverMode mode; } SubGhzViewReceiverModel; +void subghz_view_receiver_set_mode( + SubGhzViewReceiver* subghz_receiver, + SubGhzViewReceiverMode mode) { + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + model->mode = mode; + return true; + }); +} + void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { furi_assert(subghz_receiver); subghz_receiver->lock_count = 0; @@ -150,6 +162,17 @@ void subghz_view_receiver_add_data_statusbar( }); } +void subghz_view_receiver_add_data_progress( + SubGhzViewReceiver* subghz_receiver, + const char* progress_str) { + furi_assert(subghz_receiver); + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + string_set_str(model->progress_str, progress_str); + return true; + }); +} + static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { canvas_set_color(canvas, ColorBlack); canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); @@ -169,8 +192,12 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - elements_button_left(canvas, "Config"); - canvas_draw_line(canvas, 46, 51, 125, 51); + if(model->mode == SubGhzViewReceiverModeLive) { + elements_button_left(canvas, "Config"); + canvas_draw_line(canvas, 46, 51, 125, 51); + } else { + canvas_draw_str(canvas, 3, 62, string_get_cstr(model->progress_str)); + } bool scrollbar = model->history_item > 4; string_t str_buff; @@ -200,11 +227,18 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_set_color(canvas, ColorBlack); if(model->history_item == 0) { - canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 63, 46, "Scanning..."); - canvas_draw_line(canvas, 46, 51, 125, 51); - canvas_set_font(canvas, FontSecondary); + if(model->mode == SubGhzViewReceiverModeLive) { + canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 63, 46, "Scanning..."); + canvas_draw_line(canvas, 46, 51, 125, 51); + canvas_set_font(canvas, FontSecondary); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Decoding_123x52); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 63, 46, "Decoding..."); + canvas_set_font(canvas, FontSecondary); + } } switch(model->bar_show) { @@ -334,6 +368,7 @@ void subghz_view_receiver_exit(void* context) { string_reset(model->frequency_str); string_reset(model->preset_str); string_reset(model->history_stat_str); + for M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { string_clear(item_menu->item_str); @@ -369,6 +404,7 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { string_init(model->frequency_str); string_init(model->preset_str); string_init(model->history_stat_str); + string_init(model->progress_str); model->bar_show = SubGhzViewReceiverBarShowDefault; model->history = malloc(sizeof(SubGhzReceiverHistory)); SubGhzReceiverMenuItemArray_init(model->history->data); @@ -387,6 +423,7 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) { string_clear(model->frequency_str); string_clear(model->preset_str); string_clear(model->history_stat_str); + string_clear(model->progress_str); for M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { string_clear(item_menu->item_str); diff --git a/applications/subghz/views/receiver.h b/applications/subghz/views/receiver.h index aab7a76c5..829277174 100644 --- a/applications/subghz/views/receiver.h +++ b/applications/subghz/views/receiver.h @@ -8,6 +8,10 @@ typedef struct SubGhzViewReceiver SubGhzViewReceiver; typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context); +void subghz_view_receiver_set_mode( + SubGhzViewReceiver* subghz_receiver, + SubGhzViewReceiverMode mode); + void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); void subghz_view_receiver_set_callback( @@ -27,6 +31,10 @@ void subghz_view_receiver_add_data_statusbar( const char* preset_str, const char* history_stat_str); +void subghz_view_receiver_add_data_progress( + SubGhzViewReceiver* subghz_receiver, + const char* progress_str); + void subghz_view_receiver_add_item_to_menu( SubGhzViewReceiver* subghz_receiver, const char* name, diff --git a/assets/icons/SubGhz/Decoding_123x52.png b/assets/icons/SubGhz/Decoding_123x52.png new file mode 100644 index 000000000..0773c7b74 Binary files /dev/null and b/assets/icons/SubGhz/Decoding_123x52.png differ diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 8f65ea004..81bd94f09 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -79,6 +79,18 @@ bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, co return res; } +void subghz_file_encoder_worker_get_text_progress( + SubGhzFileEncoderWorker* instance, + string_t output) { + UNUSED(output); + Stream* stream = flipper_format_get_raw_stream(instance->flipper_format); + size_t total_size = stream_size(stream); + size_t current_offset = stream_tell(stream); + size_t buffer_avail = xStreamBufferBytesAvailable(instance->stream); + + string_printf(output, "%03u%%", 100 * (current_offset - buffer_avail) / total_size); +} + LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { furi_assert(context); SubGhzFileEncoderWorker* instance = context; diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index a87be5cd6..e7280a636 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -28,6 +28,15 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc(); */ void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance); +/** + * Get a description of the progress. + * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @param output + */ +void subghz_file_encoder_worker_get_text_progress( + SubGhzFileEncoderWorker* instance, + string_t output); + /** * Getting the level and duration of the upload to be loaded into DMA. * @param context Pointer to a SubGhzFileEncoderWorker instance