From 194727515b58a5f449f251c3e9c5493b5695bfbc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Aug 2022 04:46:59 +0300 Subject: [PATCH] subghz decode raw gui by qistoph --- applications/subghz/helpers/subghz_types.h | 5 + .../subghz/scenes/subghz_scene_config.h | 1 + .../subghz/scenes/subghz_scene_decode_raw.c | 272 ++++++++++++++++++ .../subghz/scenes/subghz_scene_more_raw.c | 13 + .../subghz/scenes/subghz_scene_receiver.c | 5 +- applications/subghz/subghz_i.h | 3 + applications/subghz/views/receiver.c | 51 +++- applications/subghz/views/receiver.h | 8 + assets/icons/SubGhz/Decoding_123x52.png | Bin 0 -> 11929 bytes lib/subghz/subghz_file_encoder_worker.c | 12 + lib/subghz/subghz_file_encoder_worker.h | 9 + 11 files changed, 370 insertions(+), 9 deletions(-) create mode 100644 applications/subghz/scenes/subghz_scene_decode_raw.c create mode 100644 assets/icons/SubGhz/Decoding_123x52.png 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 0000000000000000000000000000000000000000..0773c7b74b02b72b5627e4e0c6ff65305a44d749 GIT binary patch literal 11929 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 + 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