From 1bb1022d51e7b44d89eeffa6a14f908a99d2d21e Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Sun, 7 Aug 2022 02:02:01 +0200 Subject: [PATCH 01/18] feat[brutehelper]: initial commit --- applications/brute_helper/application.fam | 14 +++ applications/brute_helper/brute_helper.c | 136 ++++++++++++++++++++++ applications/meta/application.fam | 1 + 3 files changed, 151 insertions(+) create mode 100644 applications/brute_helper/application.fam create mode 100644 applications/brute_helper/brute_helper.c diff --git a/applications/brute_helper/application.fam b/applications/brute_helper/application.fam new file mode 100644 index 000000000..c14e45362 --- /dev/null +++ b/applications/brute_helper/application.fam @@ -0,0 +1,14 @@ +App( + appid="brutehelper", + name="Brute Helper", + apptype=FlipperAppType.APP, + entry_point="brute_helper_app", + cdefines=["APP_BRUTEHELPER"], + requires=[ + "gui", + "dialogs", + ], + icon="A_UniRFRemix_14", + stack_size=2 * 1024, + order=11, +) diff --git a/applications/brute_helper/brute_helper.c b/applications/brute_helper/brute_helper.c new file mode 100644 index 000000000..41026e2b7 --- /dev/null +++ b/applications/brute_helper/brute_helper.c @@ -0,0 +1,136 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define BRUTE_HELPER_FOLDER "/ext/brutehelper" +#define BRUTE_HELPER_EXT ".txt" +#define TAG "Brute Helper" + +#define WIDTH 128 +#define HEIGHT 64 + +typedef struct { + FuriMutex* mutex; + FuriMessageQueue* input_queue; + ViewPort* view_port; + Gui* gui; + + string_t file_path; + /* data */ +} BruteHelper; + +static void render_callback(Canvas* canvas, void* ctx) { + BruteHelper* app = ctx; + furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); + + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, WIDTH / 2, HEIGHT / 2, AlignCenter, AlignTop, "Hello World!"); + + furi_mutex_release(app->mutex); +} + +static void input_callback(InputEvent* event, void* ctx) { + BruteHelper* app = ctx; + furi_message_queue_put(app->input_queue, event, 0); +} + +BruteHelper* brute_helper_alloc() { + BruteHelper* app = malloc(sizeof(BruteHelper)); + app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); + + // view port + app->view_port = view_port_alloc(); + view_port_draw_callback_set(app->view_port, render_callback, app); + view_port_input_callback_set(app->view_port, input_callback, app); + + // gui + app->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + + return app; +} + +void brute_helper_free(BruteHelper* app) { + string_clear(app->file_path); + + gui_remove_view_port(app->gui, app->view_port); + furi_record_close(RECORD_GUI); + view_port_free(app->view_port); + + furi_message_queue_free(app->input_queue); + furi_mutex_free(app->mutex); + + free(app); +} + +int32_t brute_helper_app(void* p) { + UNUSED(p); + + // create app + BruteHelper* app = brute_helper_alloc(); + + // init app + string_init(app->file_path); + + // create brute_helper folder + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, BRUTE_HELPER_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", BRUTE_HELPER_FOLDER); + } + furi_record_close(RECORD_STORAGE); + + string_set_str(app->file_path, BRUTE_HELPER_FOLDER); + + // select brute file + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + const bool res = dialog_file_browser_show( + dialogs, app->file_path, app->file_path, BRUTE_HELPER_EXT, true, &I_sub1_10px, true); + furi_record_close(RECORD_DIALOGS); + if(!res) { + FURI_LOG_E(TAG, "No file selected"); + } + + bool exit_loop = false; + InputEvent input; + while(1) { + furi_check( + furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); + FURI_LOG_I( + TAG, + "Key: %s, Type: %s", + input_get_key_name(input.key), + input_get_type_name(input.type)); + + switch(input.key) { + case InputKeyBack: + exit_loop = true; + break; + + default: + break; + } + + furi_mutex_release(app->mutex); + + if(exit_loop == true) { + break; + } + + view_port_update(app->view_port); + } + + brute_helper_free(app); + return 0; +} \ No newline at end of file diff --git a/applications/meta/application.fam b/applications/meta/application.fam index 9409a33b1..822ba9910 100644 --- a/applications/meta/application.fam +++ b/applications/meta/application.fam @@ -59,6 +59,7 @@ App( "clock", "spectrum_analyzer", "unirfremix", + "brutehelper" ], ) From 05b6f9a9019dd523a022f789ff313fc4f29fbed5 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Mon, 8 Aug 2022 00:05:49 +0200 Subject: [PATCH 02/18] refactor: rebrand `brute-helper` to `playlist` --- applications/meta/application.fam | 2 +- .../application.fam | 8 ++-- .../brute_helper.c => playlist/playlist.c} | 44 ++++++++++--------- 3 files changed, 29 insertions(+), 25 deletions(-) rename applications/{brute_helper => playlist}/application.fam (57%) rename applications/{brute_helper/brute_helper.c => playlist/playlist.c} (73%) diff --git a/applications/meta/application.fam b/applications/meta/application.fam index 822ba9910..8dacb3fb3 100644 --- a/applications/meta/application.fam +++ b/applications/meta/application.fam @@ -59,7 +59,7 @@ App( "clock", "spectrum_analyzer", "unirfremix", - "brutehelper" + "playlist" ], ) diff --git a/applications/brute_helper/application.fam b/applications/playlist/application.fam similarity index 57% rename from applications/brute_helper/application.fam rename to applications/playlist/application.fam index c14e45362..3b0ee1c7d 100644 --- a/applications/brute_helper/application.fam +++ b/applications/playlist/application.fam @@ -1,9 +1,9 @@ App( - appid="brutehelper", - name="Brute Helper", + appid="playlist", + name="Playlist", apptype=FlipperAppType.APP, - entry_point="brute_helper_app", - cdefines=["APP_BRUTEHELPER"], + entry_point="playlist_app", + cdefines=["APP_PLAYLIST"], requires=[ "gui", "dialogs", diff --git a/applications/brute_helper/brute_helper.c b/applications/playlist/playlist.c similarity index 73% rename from applications/brute_helper/brute_helper.c rename to applications/playlist/playlist.c index 41026e2b7..264e61cd0 100644 --- a/applications/brute_helper/brute_helper.c +++ b/applications/playlist/playlist.c @@ -12,9 +12,9 @@ #include #include -#define BRUTE_HELPER_FOLDER "/ext/brutehelper" -#define BRUTE_HELPER_EXT ".txt" -#define TAG "Brute Helper" +#define PLAYLIST_FOLDER "/ext/playlist" +#define PLAYLIST_EXT ".txt" +#define TAG "Playlist" #define WIDTH 128 #define HEIGHT 64 @@ -26,27 +26,31 @@ typedef struct { Gui* gui; string_t file_path; - /* data */ -} BruteHelper; +} Playlist; static void render_callback(Canvas* canvas, void* ctx) { - BruteHelper* app = ctx; + Playlist* app = ctx; furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); canvas_clear(canvas); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, WIDTH / 2, HEIGHT / 2, AlignCenter, AlignTop, "Hello World!"); + canvas_draw_str_aligned( + canvas, WIDTH / 2, HEIGHT / 2 - 5, AlignCenter, AlignTop, "Hello World"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, WIDTH / 2, HEIGHT / 2 + 10, AlignCenter, AlignTop, "from Playlist"); furi_mutex_release(app->mutex); } static void input_callback(InputEvent* event, void* ctx) { - BruteHelper* app = ctx; + Playlist* app = ctx; furi_message_queue_put(app->input_queue, event, 0); } -BruteHelper* brute_helper_alloc() { - BruteHelper* app = malloc(sizeof(BruteHelper)); +Playlist* playlist_alloc() { + Playlist* app = malloc(sizeof(Playlist)); app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); @@ -62,7 +66,7 @@ BruteHelper* brute_helper_alloc() { return app; } -void brute_helper_free(BruteHelper* app) { +void playlist_free(Playlist* app) { string_clear(app->file_path); gui_remove_view_port(app->gui, app->view_port); @@ -75,28 +79,28 @@ void brute_helper_free(BruteHelper* app) { free(app); } -int32_t brute_helper_app(void* p) { +int32_t playlist_app(void* p) { UNUSED(p); // create app - BruteHelper* app = brute_helper_alloc(); + Playlist* app = playlist_alloc(); // init app string_init(app->file_path); - // create brute_helper folder + // create playlist folder Storage* storage = furi_record_open(RECORD_STORAGE); - if(!storage_simply_mkdir(storage, BRUTE_HELPER_FOLDER)) { - FURI_LOG_E(TAG, "Could not create folder %s", BRUTE_HELPER_FOLDER); + if(!storage_simply_mkdir(storage, PLAYLIST_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", PLAYLIST_FOLDER); } furi_record_close(RECORD_STORAGE); - string_set_str(app->file_path, BRUTE_HELPER_FOLDER); + string_set_str(app->file_path, PLAYLIST_FOLDER); - // select brute file + // select playlist file DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const bool res = dialog_file_browser_show( - dialogs, app->file_path, app->file_path, BRUTE_HELPER_EXT, true, &I_sub1_10px, true); + dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); furi_record_close(RECORD_DIALOGS); if(!res) { FURI_LOG_E(TAG, "No file selected"); @@ -131,6 +135,6 @@ int32_t brute_helper_app(void* p) { view_port_update(app->view_port); } - brute_helper_free(app); + playlist_free(app); return 0; } \ No newline at end of file From f6a071ed6bf8690255161cc51f2e8ddfcc441021 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Mon, 8 Aug 2022 02:02:39 +0200 Subject: [PATCH 03/18] feat[playlist]: worker thread --- applications/playlist/playlist.c | 219 ++++++++++++++++++++++++++----- 1 file changed, 188 insertions(+), 31 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 264e61cd0..36c532011 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -12,10 +12,17 @@ #include #include +#include +#include +#include +#include + #define PLAYLIST_FOLDER "/ext/playlist" #define PLAYLIST_EXT ".txt" #define TAG "Playlist" +#define STATE_OVERVIEW 2 + #define WIDTH 128 #define HEIGHT 64 @@ -25,21 +32,45 @@ typedef struct { ViewPort* view_port; Gui* gui; - string_t file_path; + string_t file_path; // Path to the playlist file + int state; // Current state for rendering + int count; // Number of files in the playlist } Playlist; +typedef struct { + Playlist* parent; + FuriThread* thread; + Storage* storage; + FlipperFormat* format; + + string_t file_path; // Path to the playlist file + bool running; // True if the worker is running +} PlaylistWorker; + static void render_callback(Canvas* canvas, void* ctx) { Playlist* app = ctx; furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, WIDTH / 2, HEIGHT / 2 - 5, AlignCenter, AlignTop, "Hello World"); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, WIDTH / 2, HEIGHT / 2 + 10, AlignCenter, AlignTop, "from Playlist"); + switch(app->state) { + case STATE_OVERVIEW: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, WIDTH / 2, HEIGHT / 2 - 10, AlignCenter, AlignTop, "FILE"); + + // extract file name from file_path + string_t file_name; + string_init(file_name); + path_extract_filename(app->file_path, file_name, true); + + // draw file name + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, WIDTH / 2, HEIGHT / 2 + 10, AlignCenter, AlignTop, string_get_cstr(file_name)); + string_clear(file_name); + + break; + } furi_mutex_release(app->mutex); } @@ -49,8 +80,111 @@ static void input_callback(InputEvent* event, void* ctx) { furi_message_queue_put(app->input_queue, event, 0); } +static int32_t playlist_worker_thread(void* ctx) { + PlaylistWorker* worker = ctx; + FURI_LOG_I(TAG, "(worker) Worker start"); + + if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { + FURI_LOG_E(TAG, "(worker) Could not open file %s", string_get_cstr(worker->file_path)); + worker->running = false; + return 0; + } + + FURI_LOG_I(TAG, "(worker) Opened file %s", string_get_cstr(worker->file_path)); + + string_t data; + string_init(data); + + int count = 0; + + Stream* stream = flipper_format_get_raw_stream(worker->format); + while(worker->running && stream_read_line(stream, data)) { + string_strim(data); + FURI_LOG_I(TAG, "(worker) Read line %s", string_get_cstr(data)); + + char* str; + str = strstr(string_get_cstr(data), "SUB: "); + if(str != NULL) { + str = strchr(str, ' '); + + while(strchr(str, ' ') != NULL) { + str = strchr(str, ' '); + str += 1; + + count++; + FURI_LOG_I(TAG, "(worker) data %d: %s", count, str); + + furi_delay_ms(1000); // TODO: remove this delay + } + } + } + flipper_format_file_close(worker->format); + + string_clear(data); + FURI_LOG_I(TAG, "Done reading. Read %d data lines.", count); + + worker->running = false; + + return 0; +} + +PlaylistWorker* playlist_worker_alloc() { + PlaylistWorker* instance = malloc(sizeof(PlaylistWorker)); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "PlaylistWorker"); + furi_thread_set_stack_size(instance->thread, 2048); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, playlist_worker_thread); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->format = flipper_format_file_alloc(instance->storage); + + string_init(instance->file_path); + + return instance; +} + +void playlist_worker_stop(PlaylistWorker* worker) { + furi_assert(worker); + furi_assert(worker->running); + + worker->running = false; + furi_thread_join(worker->thread); +} + +bool playlist_worker_running(PlaylistWorker* worker) { + furi_assert(worker); + return worker->running; +} + +void playlist_worker_start(PlaylistWorker* instance, Playlist* parent, const char* file_path) { + furi_assert(instance); + furi_assert(!instance->running); + + string_set(instance->file_path, file_path); + instance->parent = parent; + instance->running = true; + + furi_thread_start(instance->thread); +} + +void playlist_worker_free(PlaylistWorker* instance) { + furi_assert(instance); + + furi_thread_free(instance->thread); + flipper_format_free(instance->format); + furi_record_close(RECORD_STORAGE); + + string_clear(instance->file_path); + + free(instance); +} + Playlist* playlist_alloc() { Playlist* app = malloc(sizeof(Playlist)); + app->state = 0; + app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); @@ -102,37 +236,60 @@ int32_t playlist_app(void* p) { const bool res = dialog_file_browser_show( dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); furi_record_close(RECORD_DIALOGS); - if(!res) { - FURI_LOG_E(TAG, "No file selected"); - } - bool exit_loop = false; - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - FURI_LOG_I( - TAG, - "Key: %s, Type: %s", - input_get_key_name(input.key), - input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyBack: - exit_loop = true; - break; - - default: + PlaylistWorker* worker = NULL; + do { + // check if a file was selected + if(!res) { + FURI_LOG_E(TAG, "No file selected"); break; } - furi_mutex_release(app->mutex); + app->state = STATE_OVERVIEW; - if(exit_loop == true) { - break; + FURI_LOG_I(TAG, "Starting thread ..."); + worker = playlist_worker_alloc(); + playlist_worker_start(worker, string_get_cstr(app->file_path)); + + bool exit_loop = false; + InputEvent input; + while(res) { // close application if no file was selected + FURI_LOG_I(TAG, "Checking queue"); + furi_check( + furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); + + FURI_LOG_I( + TAG, + "Key: %s, Type: %s", + input_get_key_name(input.key), + input_get_type_name(input.type)); + + switch(input.key) { + case InputKeyBack: + FURI_LOG_I(TAG, "Pressed Back button. Application will exit"); + exit_loop = true; + break; + default: + break; + } + + furi_mutex_release(app->mutex); + + // exit application + if(exit_loop == true) { + break; + } + + view_port_update(app->view_port); } + } while(0); - view_port_update(app->view_port); + if(worker != NULL) { + if(playlist_worker_running(worker)) { + FURI_LOG_I(TAG, "Thread is still running. Requesting thread to finish ..."); + playlist_worker_stop(worker); + } + playlist_worker_free(worker); } playlist_free(app); From 1a52e55a73ea8678e90086a21aec36f4c2c141e2 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Mon, 8 Aug 2022 02:55:18 +0200 Subject: [PATCH 04/18] feat[playlist]: display current .sub --- applications/playlist/example_playlist.txt | 5 + applications/playlist/playlist.c | 146 ++++++++++++--------- 2 files changed, 90 insertions(+), 61 deletions(-) create mode 100644 applications/playlist/example_playlist.txt diff --git a/applications/playlist/example_playlist.txt b/applications/playlist/example_playlist.txt new file mode 100644 index 000000000..7c7a05191 --- /dev/null +++ b/applications/playlist/example_playlist.txt @@ -0,0 +1,5 @@ +SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn300AllBit.sub +SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn310AllBit.sub +SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn315AllBit.sub +SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn318AllBit.sub +SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn390AllBit.sub \ No newline at end of file diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 36c532011..637910c6a 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -27,58 +27,29 @@ #define HEIGHT 64 typedef struct { - FuriMutex* mutex; - FuriMessageQueue* input_queue; - ViewPort* view_port; - Gui* gui; - - string_t file_path; // Path to the playlist file - int state; // Current state for rendering - int count; // Number of files in the playlist -} Playlist; - -typedef struct { - Playlist* parent; FuriThread* thread; Storage* storage; FlipperFormat* format; string_t file_path; // Path to the playlist file + string_t current_file; // Path to the current file bool running; // True if the worker is running } PlaylistWorker; -static void render_callback(Canvas* canvas, void* ctx) { - Playlist* app = ctx; - furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); +typedef struct { + FuriMutex* mutex; + FuriMessageQueue* input_queue; + ViewPort* view_port; + Gui* gui; - canvas_clear(canvas); + PlaylistWorker* worker; - switch(app->state) { - case STATE_OVERVIEW: - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, WIDTH / 2, HEIGHT / 2 - 10, AlignCenter, AlignTop, "FILE"); + string_t file_path; // Path to the playlist file - // extract file name from file_path - string_t file_name; - string_init(file_name); - path_extract_filename(app->file_path, file_name, true); + int state; // Current state for rendering +} Playlist; - // draw file name - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, WIDTH / 2, HEIGHT / 2 + 10, AlignCenter, AlignTop, string_get_cstr(file_name)); - string_clear(file_name); - - break; - } - - furi_mutex_release(app->mutex); -} - -static void input_callback(InputEvent* event, void* ctx) { - Playlist* app = ctx; - furi_message_queue_put(app->input_queue, event, 0); -} +//////////////////////////////////////////////////////////////////////////////// static int32_t playlist_worker_thread(void* ctx) { PlaylistWorker* worker = ctx; @@ -114,7 +85,11 @@ static int32_t playlist_worker_thread(void* ctx) { count++; FURI_LOG_I(TAG, "(worker) data %d: %s", count, str); - furi_delay_ms(1000); // TODO: remove this delay + // show current file + string_set_str(worker->current_file, str); + FURI_LOG_I(TAG, "(worker) current_file: %s", worker->current_file); + + furi_delay_ms(3000); // TODO: remove this delay } } } @@ -141,10 +116,24 @@ PlaylistWorker* playlist_worker_alloc() { instance->format = flipper_format_file_alloc(instance->storage); string_init(instance->file_path); + string_init(instance->current_file); return instance; } +void playlist_worker_free(PlaylistWorker* instance) { + furi_assert(instance); + + furi_thread_free(instance->thread); + flipper_format_free(instance->format); + furi_record_close(RECORD_STORAGE); + + string_clear(instance->file_path); + string_clear(instance->current_file); + + free(instance); +} + void playlist_worker_stop(PlaylistWorker* worker) { furi_assert(worker); furi_assert(worker->running); @@ -158,32 +147,71 @@ bool playlist_worker_running(PlaylistWorker* worker) { return worker->running; } -void playlist_worker_start(PlaylistWorker* instance, Playlist* parent, const char* file_path) { +void playlist_worker_start(PlaylistWorker* instance, const char* file_path) { furi_assert(instance); furi_assert(!instance->running); - string_set(instance->file_path, file_path); - instance->parent = parent; + string_set_str(instance->file_path, file_path); instance->running = true; furi_thread_start(instance->thread); } -void playlist_worker_free(PlaylistWorker* instance) { - furi_assert(instance); +//////////////////////////////////////////////////////////////////////////////// - furi_thread_free(instance->thread); - flipper_format_free(instance->format); - furi_record_close(RECORD_STORAGE); +static void render_callback(Canvas* canvas, void* ctx) { + Playlist* app = ctx; + furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); - string_clear(instance->file_path); + canvas_clear(canvas); - free(instance); + switch(app->state) { + case STATE_OVERVIEW: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, HEIGHT - 5, AlignLeft, AlignBottom, "FILE:"); + + // extract file name from file_path + { + int padL = canvas_string_width(canvas, "FILE: "); + + string_t file_name; + string_init(file_name); + path_extract_filename(app->file_path, file_name, true); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 5 + padL, HEIGHT - 5, AlignLeft, AlignBottom, string_get_cstr(file_name)); + string_clear(file_name); + } + + if(app->worker != NULL && app->worker->running == true && + !string_empty_p(app->worker->current_file)) { + string_t file_name; + string_init(file_name); + path_extract_filename(app->worker->current_file, file_name, true); + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, string_get_cstr(file_name)); + FURI_LOG_I(TAG, "(render) drawing current file %s", string_get_cstr(file_name)); + string_clear(file_name); + } + + break; + } + + furi_mutex_release(app->mutex); } +static void input_callback(InputEvent* event, void* ctx) { + Playlist* app = ctx; + furi_message_queue_put(app->input_queue, event, 0); +} + +//////////////////////////////////////////////////////////////////////////////// + Playlist* playlist_alloc() { Playlist* app = malloc(sizeof(Playlist)); app->state = 0; + string_init(app->file_path); + + app->worker = NULL; app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); @@ -219,9 +247,6 @@ int32_t playlist_app(void* p) { // create app Playlist* app = playlist_alloc(); - // init app - string_init(app->file_path); - // create playlist folder Storage* storage = furi_record_open(RECORD_STORAGE); if(!storage_simply_mkdir(storage, PLAYLIST_FOLDER)) { @@ -237,7 +262,6 @@ int32_t playlist_app(void* p) { dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); furi_record_close(RECORD_DIALOGS); - PlaylistWorker* worker = NULL; do { // check if a file was selected if(!res) { @@ -248,8 +272,8 @@ int32_t playlist_app(void* p) { app->state = STATE_OVERVIEW; FURI_LOG_I(TAG, "Starting thread ..."); - worker = playlist_worker_alloc(); - playlist_worker_start(worker, string_get_cstr(app->file_path)); + app->worker = playlist_worker_alloc(); + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); bool exit_loop = false; InputEvent input; @@ -284,12 +308,12 @@ int32_t playlist_app(void* p) { } } while(0); - if(worker != NULL) { - if(playlist_worker_running(worker)) { + if(app->worker != NULL) { + if(playlist_worker_running(app->worker)) { FURI_LOG_I(TAG, "Thread is still running. Requesting thread to finish ..."); - playlist_worker_stop(worker); + playlist_worker_stop(app->worker); } - playlist_worker_free(worker); + playlist_worker_free(app->worker); } playlist_free(app); From 7c1a48a3779833b22592bf88c0798dbe7ad432e1 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Mon, 8 Aug 2022 19:19:51 +0200 Subject: [PATCH 05/18] refactor[playlist]: remove unused includes --- applications/playlist/playlist.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 637910c6a..4a70a98d6 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -12,11 +11,6 @@ #include #include -#include -#include -#include -#include - #define PLAYLIST_FOLDER "/ext/playlist" #define PLAYLIST_EXT ".txt" #define TAG "Playlist" From 9b89acfec03e76ed1a8409a893553db5023eeb37 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Tue, 9 Aug 2022 00:12:06 +0200 Subject: [PATCH 06/18] feat[playlist]: basic controls --- applications/playlist/playlist.c | 317 +++++++++++++++++--------- applications/playlist/playlist_file.c | 20 ++ applications/playlist/playlist_file.h | 7 + 3 files changed, 235 insertions(+), 109 deletions(-) create mode 100644 applications/playlist/playlist_file.c create mode 100644 applications/playlist/playlist_file.h diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 4a70a98d6..9dd9547de 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -11,6 +11,11 @@ #include #include +#include "flipper_format_stream.h" +#include "flipper_format_stream_i.h" + +#include "playlist_file.h" + #define PLAYLIST_FOLDER "/ext/playlist" #define PLAYLIST_EXT ".txt" #define TAG "Playlist" @@ -20,14 +25,27 @@ #define WIDTH 128 #define HEIGHT 64 +typedef struct { + int current_count; // Number of processed files + int total_count; // Number of items in the playlist + + // last 3 files + string_t prev_0_path; // current file + string_t prev_1_path; // previous file + string_t prev_2_path; + string_t prev_3_path; +} DisplayMeta; + typedef struct { FuriThread* thread; Storage* storage; FlipperFormat* format; + DisplayMeta* meta; + string_t file_path; // Path to the playlist file - string_t current_file; // Path to the current file - bool running; // True if the worker is running + bool running; // indicates if the worker is running + bool paused; // can be set to true to pause worker } PlaylistWorker; typedef struct { @@ -36,6 +54,7 @@ typedef struct { ViewPort* view_port; Gui* gui; + DisplayMeta* meta; PlaylistWorker* worker; string_t file_path; // Path to the playlist file @@ -47,57 +66,84 @@ typedef struct { static int32_t playlist_worker_thread(void* ctx) { PlaylistWorker* worker = ctx; - FURI_LOG_I(TAG, "(worker) Worker start"); - if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { - FURI_LOG_E(TAG, "(worker) Could not open file %s", string_get_cstr(worker->file_path)); worker->running = false; return 0; } - FURI_LOG_I(TAG, "(worker) Opened file %s", string_get_cstr(worker->file_path)); + // reset worker meta string_t data; string_init(data); - - int count = 0; - - Stream* stream = flipper_format_get_raw_stream(worker->format); - while(worker->running && stream_read_line(stream, data)) { - string_strim(data); - FURI_LOG_I(TAG, "(worker) Read line %s", string_get_cstr(data)); - - char* str; - str = strstr(string_get_cstr(data), "SUB: "); - if(str != NULL) { - str = strchr(str, ' '); - - while(strchr(str, ' ') != NULL) { - str = strchr(str, ' '); - str += 1; - - count++; - FURI_LOG_I(TAG, "(worker) data %d: %s", count, str); - - // show current file - string_set_str(worker->current_file, str); - FURI_LOG_I(TAG, "(worker) current_file: %s", worker->current_file); - - furi_delay_ms(3000); // TODO: remove this delay - } + while(worker->running && flipper_format_read_string(worker->format, "sub", data)) { + // wait if paused + while(worker->paused) { + furi_delay_ms(100); } + + // send .sub files + ++worker->meta->current_count; + const char* str = string_get_cstr(data); + FURI_LOG_I(TAG, "(worker) data #%d: %s", worker->meta->current_count, str); + + // it's not fancy, but it works :) + string_reset(worker->meta->prev_3_path); + string_set_str(worker->meta->prev_3_path, string_get_cstr(worker->meta->prev_2_path)); + string_reset(worker->meta->prev_2_path); + string_set_str(worker->meta->prev_2_path, string_get_cstr(worker->meta->prev_1_path)); + string_reset(worker->meta->prev_1_path); + string_set_str(worker->meta->prev_1_path, string_get_cstr(worker->meta->prev_0_path)); + string_reset(worker->meta->prev_0_path); + string_set_str(worker->meta->prev_0_path, str); + + FURI_LOG_I(TAG, ""); + FURI_LOG_I(TAG, "(worker) prev_3: %s", string_get_cstr(worker->meta->prev_3_path)); + FURI_LOG_I(TAG, "(worker) prev_2: %s", string_get_cstr(worker->meta->prev_2_path)); + FURI_LOG_I(TAG, "(worker) prev_1: %s", string_get_cstr(worker->meta->prev_1_path)); + FURI_LOG_I(TAG, "(worker) prev_0: %s", string_get_cstr(worker->meta->prev_0_path)); + FURI_LOG_I(TAG, ""); + + furi_delay_ms(1500); // TODO: remove this delay } flipper_format_file_close(worker->format); - string_clear(data); - FURI_LOG_I(TAG, "Done reading. Read %d data lines.", count); + FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->running = false; - return 0; } -PlaylistWorker* playlist_worker_alloc() { +//////////////////////////////////////////////////////////////////////////////// + +void playlist_meta_reset(DisplayMeta* instance) { + instance->current_count = 0; + string_clear(instance->prev_0_path); + string_clear(instance->prev_1_path); + string_clear(instance->prev_2_path); + string_clear(instance->prev_3_path); +} + +DisplayMeta* playlist_meta_alloc() { + DisplayMeta* instance = malloc(sizeof(DisplayMeta)); + string_init(instance->prev_0_path); + string_init(instance->prev_1_path); + string_init(instance->prev_2_path); + string_init(instance->prev_3_path); + playlist_meta_reset(instance); + return instance; +} + +void playlist_meta_free(DisplayMeta* instance) { + string_clear(instance->prev_0_path); + string_clear(instance->prev_1_path); + string_clear(instance->prev_2_path); + string_clear(instance->prev_3_path); + free(instance); +} + +//////////////////////////////////////////////////////////////////////////////// + +PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) { PlaylistWorker* instance = malloc(sizeof(PlaylistWorker)); instance->thread = furi_thread_alloc(); @@ -108,9 +154,11 @@ PlaylistWorker* playlist_worker_alloc() { instance->storage = furi_record_open(RECORD_STORAGE); instance->format = flipper_format_file_alloc(instance->storage); + instance->meta = meta; + + instance->paused = true; // require the user to manually start the worker string_init(instance->file_path); - string_init(instance->current_file); return instance; } @@ -123,7 +171,6 @@ void playlist_worker_free(PlaylistWorker* instance) { furi_record_close(RECORD_STORAGE); string_clear(instance->file_path); - string_clear(instance->current_file); free(instance); } @@ -148,6 +195,9 @@ void playlist_worker_start(PlaylistWorker* instance, const char* file_path) { string_set_str(instance->file_path, file_path); instance->running = true; + // reset meta (current/total) + playlist_meta_reset(instance->meta); + furi_thread_start(instance->thread); } @@ -161,32 +211,52 @@ static void render_callback(Canvas* canvas, void* ctx) { switch(app->state) { case STATE_OVERVIEW: - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, HEIGHT - 5, AlignLeft, AlignBottom, "FILE:"); - - // extract file name from file_path + // draw progress bar { - int padL = canvas_string_width(canvas, "FILE: "); + double progress = (double)app->meta->current_count / (double)app->meta->total_count; + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 1, HEIGHT - 12, WIDTH - 2, 11, 2); - string_t file_name; - string_init(file_name); - path_extract_filename(app->file_path, file_name, true); + if(progress > 0) { + int progress_width = (int)(progress * (double)(WIDTH - 2)); + canvas_draw_rbox(canvas, 1, HEIGHT - 12, progress_width, 11, 2); + } + + // draw progress text + string_t progress_text; + string_init(progress_text); + string_printf( + progress_text, "%d/%d", app->meta->current_count, app->meta->total_count); + + if(progress >= (double).5) { + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned( - canvas, 5 + padL, HEIGHT - 5, AlignLeft, AlignBottom, string_get_cstr(file_name)); - string_clear(file_name); + canvas, + WIDTH / 2, + HEIGHT - 3, + AlignCenter, + AlignBottom, + string_get_cstr(progress_text)); + + string_clear(progress_text); } - if(app->worker != NULL && app->worker->running == true && - !string_empty_p(app->worker->current_file)) { - string_t file_name; - string_init(file_name); - path_extract_filename(app->worker->current_file, file_name, true); - canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, string_get_cstr(file_name)); - FURI_LOG_I(TAG, "(render) drawing current file %s", string_get_cstr(file_name)); - string_clear(file_name); + // draw controls + { + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + if(!app->worker->running) { + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Start"); + } else if(app->worker->paused) { + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Resume"); + } else { + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Pause"); + } } - break; } @@ -200,11 +270,14 @@ static void input_callback(InputEvent* event, void* ctx) { //////////////////////////////////////////////////////////////////////////////// -Playlist* playlist_alloc() { +Playlist* playlist_alloc(DisplayMeta* meta) { Playlist* app = malloc(sizeof(Playlist)); app->state = 0; - string_init(app->file_path); + string_init(app->file_path); + string_set_str(app->file_path, PLAYLIST_FOLDER); + + app->meta = meta; app->worker = NULL; app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -232,76 +305,102 @@ void playlist_free(Playlist* app) { furi_message_queue_free(app->input_queue); furi_mutex_free(app->mutex); + playlist_meta_free(app->meta); + free(app); } int32_t playlist_app(void* p) { UNUSED(p); - // create app - Playlist* app = playlist_alloc(); - // create playlist folder - Storage* storage = furi_record_open(RECORD_STORAGE); - if(!storage_simply_mkdir(storage, PLAYLIST_FOLDER)) { - FURI_LOG_E(TAG, "Could not create folder %s", PLAYLIST_FOLDER); + { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, PLAYLIST_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", PLAYLIST_FOLDER); + } + furi_record_close(RECORD_STORAGE); } - furi_record_close(RECORD_STORAGE); - string_set_str(app->file_path, PLAYLIST_FOLDER); + // create app + DisplayMeta* meta = playlist_meta_alloc(); + Playlist* app = playlist_alloc(meta); // select playlist file - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - const bool res = dialog_file_browser_show( - dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); - furi_record_close(RECORD_DIALOGS); - - do { + { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + const bool res = dialog_file_browser_show( + dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); + furi_record_close(RECORD_DIALOGS); // check if a file was selected if(!res) { FURI_LOG_E(TAG, "No file selected"); + goto exit_cleanup; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + FURI_LOG_I(TAG, "Starting thread ..."); + 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, string_get_cstr(app->file_path)); + FURI_LOG_I(TAG, "Selected file contains %d playlist items.", app->meta->total_count); + furi_record_close(RECORD_STORAGE); + } + + // start thread + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); + + app->state = STATE_OVERVIEW; + + bool exit_loop = false; + InputEvent input; + while(1) { // close application if no file was selected + FURI_LOG_I(TAG, "Checking queue"); + furi_check( + furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); + + FURI_LOG_I( + TAG, + "Key: %s, Type: %s", + input_get_key_name(input.key), + input_get_type_name(input.type)); + + switch(input.key) { + case InputKeyOk: + // toggle pause state + if(!app->worker->running) { + FURI_LOG_I(TAG, "Worker is NOT running. Starting worker."); + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); + } else { + FURI_LOG_I(TAG, "Worker IS running. Toggled pause state."); + app->worker->paused = !app->worker->paused; + } + break; + case InputKeyBack: + FURI_LOG_I(TAG, "Pressed Back button. Application will exit"); + exit_loop = true; + break; + default: break; } - app->state = STATE_OVERVIEW; + furi_mutex_release(app->mutex); - FURI_LOG_I(TAG, "Starting thread ..."); - app->worker = playlist_worker_alloc(); - playlist_worker_start(app->worker, string_get_cstr(app->file_path)); - - bool exit_loop = false; - InputEvent input; - while(res) { // close application if no file was selected - FURI_LOG_I(TAG, "Checking queue"); - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - - FURI_LOG_I( - TAG, - "Key: %s, Type: %s", - input_get_key_name(input.key), - input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyBack: - FURI_LOG_I(TAG, "Pressed Back button. Application will exit"); - exit_loop = true; - break; - default: - break; - } - - furi_mutex_release(app->mutex); - - // exit application - if(exit_loop == true) { - break; - } - - view_port_update(app->view_port); + // exit application + if(exit_loop == true) { + break; } - } while(0); + view_port_update(app->view_port); + } + +exit_cleanup: if(app->worker != NULL) { if(playlist_worker_running(app->worker)) { FURI_LOG_I(TAG, "Thread is still running. Requesting thread to finish ..."); diff --git a/applications/playlist/playlist_file.c b/applications/playlist/playlist_file.c new file mode 100644 index 000000000..64d50d0ad --- /dev/null +++ b/applications/playlist/playlist_file.c @@ -0,0 +1,20 @@ +#include + +#include +#include + +int playlist_count_playlist_items(Storage* storage, const char* file_path) { + FlipperFormat* format = flipper_format_file_alloc(storage); + if(!flipper_format_file_open_existing(format, file_path)) { + return -1; + } + int count = 0; + string_t data; + string_init(data); + while(flipper_format_read_string(format, "sub", data)) { + ++count; + } + flipper_format_file_close(format); + string_clear(data); + return count; +} \ No newline at end of file diff --git a/applications/playlist/playlist_file.h b/applications/playlist/playlist_file.h new file mode 100644 index 000000000..53577f7d9 --- /dev/null +++ b/applications/playlist/playlist_file.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include + +int playlist_count_playlist_items(Storage* storage, const char* file_path); \ No newline at end of file From b2ba7b5e5925ea82dd0be04bebd3405b7347b35f Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Tue, 9 Aug 2022 01:14:28 +0200 Subject: [PATCH 07/18] feat[playlist]: display history --- applications/playlist/playlist.c | 81 ++++++++++++++++++++++++--- applications/playlist/playlist_file.c | 2 +- applications/playlist/playlist_file.h | 2 +- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 9dd9547de..99c7d48d7 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -5,10 +5,10 @@ #include #include +#include #include #include -#include #include #include "flipper_format_stream.h" @@ -26,14 +26,14 @@ #define HEIGHT 64 typedef struct { - int current_count; // Number of processed files - int total_count; // Number of items in the playlist + int current_count; // number of processed files + int total_count; // number of items in the playlist // last 3 files string_t prev_0_path; // current file string_t prev_1_path; // previous file - string_t prev_2_path; - string_t prev_3_path; + string_t prev_2_path; // previous previous file + string_t prev_3_path; // you get the idea } DisplayMeta; typedef struct { @@ -43,7 +43,7 @@ typedef struct { DisplayMeta* meta; - string_t file_path; // Path to the playlist file + string_t file_path; // path to the playlist file bool running; // indicates if the worker is running bool paused; // can be set to true to pause worker } PlaylistWorker; @@ -245,16 +245,79 @@ static void render_callback(Canvas* canvas, void* ctx) { string_clear(progress_text); } + // draw last and current file + { + canvas_set_color(canvas, ColorBlack); + + string_t path; + string_init(path); + + canvas_set_font(canvas, FontSecondary); + + // current + if(!string_empty_p(app->meta->prev_0_path)) { + path_extract_filename(app->meta->prev_0_path, path, true); + int w = canvas_string_width(canvas, string_get_cstr(path)); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rbox(canvas, 1, 1, w + 4, 12, 2); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, string_get_cstr(path)); + string_reset(path); + } + + // last 3 + canvas_set_color(canvas, ColorBlack); + + if(!string_empty_p(app->meta->prev_1_path)) { + path_extract_filename(app->meta->prev_1_path, path, true); + canvas_draw_str_aligned(canvas, 3, 15, AlignLeft, AlignTop, string_get_cstr(path)); + string_reset(path); + } + + if(!string_empty_p(app->meta->prev_2_path)) { + path_extract_filename(app->meta->prev_2_path, path, true); + canvas_draw_str_aligned(canvas, 6, 26, AlignLeft, AlignTop, string_get_cstr(path)); + string_reset(path); + } + + if(!string_empty_p(app->meta->prev_3_path)) { + path_extract_filename(app->meta->prev_3_path, path, true); + canvas_draw_str_aligned(canvas, 9, 37, AlignLeft, AlignTop, string_get_cstr(path)); + string_reset(path); + } + + string_clear(path); + } + // draw controls { canvas_set_font(canvas, FontSecondary); + + const int ctl_w = 24; + const int ctl_h = 18; + + // draw background canvas_set_color(canvas, ColorBlack); + canvas_draw_rbox(canvas, WIDTH - ctl_w, HEIGHT / 2 - ctl_h / 2, ctl_w, ctl_h, 3); + canvas_draw_box(canvas, WIDTH - 3, HEIGHT / 2 - ctl_h / 2, 3, ctl_h); // right corner + + // draw circle (OK) + canvas_set_color(canvas, ColorWhite); + + const int disc_r = 3; + canvas_draw_disc( + canvas, WIDTH - ctl_w / 2, HEIGHT / 2 - ctl_h / 2 + disc_r + 1, disc_r); + + // draw texts if(!app->worker->running) { - canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Start"); + canvas_draw_str_aligned( + canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "STA"); } else if(app->worker->paused) { - canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Resume"); + canvas_draw_str_aligned( + canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "RES"); } else { - canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Pause"); + canvas_draw_str_aligned( + canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "PAU"); } } break; diff --git a/applications/playlist/playlist_file.c b/applications/playlist/playlist_file.c index 64d50d0ad..3f7e28180 100644 --- a/applications/playlist/playlist_file.c +++ b/applications/playlist/playlist_file.c @@ -17,4 +17,4 @@ int playlist_count_playlist_items(Storage* storage, const char* file_path) { flipper_format_file_close(format); string_clear(data); return count; -} \ No newline at end of file +} diff --git a/applications/playlist/playlist_file.h b/applications/playlist/playlist_file.h index 53577f7d9..fb708edc7 100644 --- a/applications/playlist/playlist_file.h +++ b/applications/playlist/playlist_file.h @@ -4,4 +4,4 @@ #include -int playlist_count_playlist_items(Storage* storage, const char* file_path); \ No newline at end of file +int playlist_count_playlist_items(Storage* storage, const char* file_path); From 91c7441fa874d87f1a19b4f1f3a428598a250988 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Aug 2022 22:46:12 +0300 Subject: [PATCH 08/18] Update Frequency analyzer by @ClusterM OFW PR #1557 --- .../subghz_frequency_analyzer_worker.c | 18 +++- .../subghz_frequency_analyzer_worker.h | 16 ++++ .../subghz/views/subghz_frequency_analyzer.c | 95 ++++++++----------- 3 files changed, 72 insertions(+), 57 deletions(-) diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index 0b91a1f4f..260676a24 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -28,6 +28,7 @@ struct SubGhzFrequencyAnalyzerWorker { SubGhzSetting* setting; float filVal; + float trigger_level; SubGhzFrequencyAnalyzerWorkerPairCallback pair_callback; void* context; @@ -154,7 +155,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { (double)rssi_min); // Second stage: fine scan - if(frequency_rssi.rssi_coarse > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { + if(frequency_rssi.rssi_coarse > instance->trigger_level) { furi_hal_subghz_idle(); subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_58khz); //for example -0.3 ... 433.92 ... +0.3 step 20KHz @@ -189,7 +190,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } // Deliver results fine - if(frequency_rssi.rssi_fine > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { + if(frequency_rssi.rssi_fine > instance->trigger_level) { FURI_LOG_D( TAG, "=:%u:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); @@ -205,7 +206,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { instance->context, frequency_rssi.frequency_fine, frequency_rssi.rssi_fine); } } else if( // Deliver results coarse - (frequency_rssi.rssi_coarse > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) && + (frequency_rssi.rssi_coarse > instance->trigger_level) && (instance->sample_hold_counter < 10)) { FURI_LOG_D( TAG, @@ -255,6 +256,7 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont SubGhz* subghz = context; instance->setting = subghz->setting; + instance->trigger_level = SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD; return instance; } @@ -297,3 +299,13 @@ bool subghz_frequency_analyzer_worker_is_running(SubGhzFrequencyAnalyzerWorker* furi_assert(instance); return instance->worker_running; } + +void subghz_frequency_analyzer_worker_set_trigger_level( + SubGhzFrequencyAnalyzerWorker* instance, + float value) { + instance->trigger_level = value; +} + +float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance) { + return instance->trigger_level; +} \ No newline at end of file diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/subghz/helpers/subghz_frequency_analyzer_worker.h index 50687c76d..3b93f60ad 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -56,3 +56,19 @@ void subghz_frequency_analyzer_worker_stop(SubGhzFrequencyAnalyzerWorker* instan * @return bool - true if running */ bool subghz_frequency_analyzer_worker_is_running(SubGhzFrequencyAnalyzerWorker* instance); + +/** Set RSSI trigger level + * + * @param instance SubGhzFrequencyAnalyzerWorker instance + * @param value RSSI level + */ +void subghz_frequency_analyzer_worker_set_trigger_level( + SubGhzFrequencyAnalyzerWorker* instance, + float value); + +/** Get RSSI trigger level + * + * @param instance SubGhzFrequencyAnalyzerWorker instance + * @return RSSI trigger level + */ +float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance); \ No newline at end of file diff --git a/applications/subghz/views/subghz_frequency_analyzer.c b/applications/subghz/views/subghz_frequency_analyzer.c index ebc912d70..14942fc75 100644 --- a/applications/subghz/views/subghz_frequency_analyzer.c +++ b/applications/subghz/views/subghz_frequency_analyzer.c @@ -13,11 +13,10 @@ #define TAG "frequency_analyzer" -#define RSSI_MIN -101 +#define RSSI_MIN -97 #define RSSI_MAX -60 #define RSSI_SCALE 2 #define TRIGGER_STEP 1 -#define TRIGGER_MIN RSSI_MIN + RSSI_SCALE * 2 static const NotificationSequence sequence_hw_blink = { &message_blink_start_10, @@ -43,8 +42,7 @@ struct SubGhzFrequencyAnalyzer { bool locked; float rssi_last; uint32_t frequency_last; - float trigger; - bool triggered; + uint32_t frequency_last_vis; NotificationApp* notifications; }; @@ -75,11 +73,11 @@ void subghz_frequency_analyzer_draw_rssi( uint8_t y) { // Current RSSI if(rssi) { + if(rssi > RSSI_MAX) rssi = RSSI_MAX; rssi = (rssi - RSSI_MIN) / RSSI_SCALE; - if(rssi > 20) rssi = 20; uint8_t column_number = 0; - for(size_t i = 1; i < (uint8_t)rssi; i++) { - if(i % 4) { + for(size_t i = 0; i <= (uint8_t)rssi; i++) { + if((i + 1) % 4) { column_number++; canvas_draw_box(canvas, x + 2 * i, y - column_number, 2, 4 + column_number); } @@ -88,26 +86,21 @@ void subghz_frequency_analyzer_draw_rssi( // Last RSSI if(rssi_last) { - int max_x = (int)((rssi_last - RSSI_MIN) / RSSI_SCALE - 1) * 2; + if(rssi_last > RSSI_MAX) rssi_last = RSSI_MAX; + int max_x = (int)((rssi_last - RSSI_MIN) / RSSI_SCALE) * 2; //if(!(max_x % 8)) max_x -= 2; - int max_h = (int)((rssi_last - RSSI_MIN) / RSSI_SCALE - 1) + 4; + int max_h = (int)((rssi_last - RSSI_MIN) / RSSI_SCALE) + 4; max_h -= (max_h / 4) + 3; - if(max_x > 38) max_h = 38; - if(max_h > 19) max_h = 19; - if(max_x >= 0 && max_h > 0) { - canvas_draw_line(canvas, x + max_x + 1, y - max_h, x + max_x + 1, y + 3); - } + canvas_draw_line(canvas, x + max_x + 1, y - max_h, x + max_x + 1, y + 3); } // Trigger cursor - if(trigger >= RSSI_MIN + RSSI_SCALE * 2) { - trigger = (trigger - RSSI_MIN) / RSSI_SCALE; - uint8_t tr_x = x + 2 * trigger - 2; - canvas_draw_dot(canvas, tr_x, y + 4); - canvas_draw_line(canvas, tr_x - 1, y + 5, tr_x + 1, y + 5); - } + trigger = (trigger - RSSI_MIN) / RSSI_SCALE; + uint8_t tr_x = x + 2 * trigger; + canvas_draw_dot(canvas, tr_x, y + 4); + canvas_draw_line(canvas, tr_x - 1, y + 5, tr_x + 1, y + 5); - canvas_draw_line(canvas, x + 2, y + 3, x + 39, y + 3); + canvas_draw_line(canvas, x, y + 3, x + (RSSI_MAX - RSSI_MIN) * 2 / RSSI_SCALE, y + 3); } void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { @@ -121,7 +114,7 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel // RSSI canvas_draw_str(canvas, 33, 62, "RSSI"); subghz_frequency_analyzer_draw_rssi( - canvas, model->rssi, model->rssi_last, model->trigger, 55, 58); + canvas, model->rssi, model->rssi_last, model->trigger, 57, 58); // Frequency canvas_set_font(canvas, FontBigNumbers); @@ -164,24 +157,20 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { if(((event->type == InputTypePress) || (event->type == InputTypeRepeat)) && ((event->key == InputKeyLeft) || (event->key == InputKeyRight))) { // Trigger setup + float trigger_level = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker); switch(event->key) { case InputKeyLeft: - instance->trigger -= TRIGGER_STEP; - if(instance->trigger < RSSI_MIN + RSSI_SCALE * 2) instance->trigger = TRIGGER_MIN; + trigger_level -= TRIGGER_STEP; + if(trigger_level < RSSI_MIN) trigger_level = RSSI_MIN; break; default: case InputKeyRight: - if(instance->trigger < RSSI_MIN + RSSI_SCALE * 2) - instance->trigger = TRIGGER_MIN; - else - instance->trigger += TRIGGER_STEP; - if(instance->trigger > RSSI_MAX) instance->trigger = RSSI_MAX; + trigger_level += TRIGGER_STEP; + if(trigger_level > RSSI_MAX) trigger_level = RSSI_MAX; break; } - if(instance->trigger > RSSI_MIN) - FURI_LOG_I(TAG, "trigger = %.1f", (double)instance->trigger); - else - FURI_LOG_I(TAG, "trigger disabled"); + subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, trigger_level); + FURI_LOG_I(TAG, "trigger = %.1f", (double)trigger_level); need_redraw = true; } @@ -191,7 +180,8 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi_last = instance->rssi_last; model->frequency_last = instance->frequency_last; - model->trigger = instance->trigger; + model->trigger = + subghz_frequency_analyzer_worker_get_trigger_level(instance->worker); return true; }); } @@ -217,27 +207,24 @@ void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, if((rssi == 0.f) && (instance->locked)) { notification_message(instance->notifications, &sequence_hw_blink); - instance->triggered = false; + instance->frequency_last_vis = instance->frequency_last; } if((rssi != 0.f) && (frequency != 0)) { // Threre is some signal FURI_LOG_I(TAG, "rssi = %.2f, frequency = %d Hz", (double)rssi, frequency); frequency = round_int(frequency, 3); // Round 299999990Hz to 300000000Hz - if((instance->trigger <= RSSI_MIN + RSSI_SCALE * 2) || (rssi >= instance->trigger)) { - if(!instance->triggered) { - // Triggered! - instance->triggered = true; - instance->rssi_last = rssi; - notification_message(instance->notifications, &sequence_hw_blink_stop); - notification_message(instance->notifications, &sequence_success); - FURI_LOG_D(TAG, "triggered"); - } - // Update values - if(rssi > instance->rssi_last) instance->rssi_last = rssi; + if(!instance->locked) { + // Triggered! + instance->rssi_last = rssi; + notification_message(instance->notifications, &sequence_hw_blink_stop); + notification_message(instance->notifications, &sequence_success); + FURI_LOG_D(TAG, "triggered"); + } + // Update values + if(rssi >= instance->rssi_last) { + instance->rssi_last = rssi; instance->frequency_last = frequency; - } else { - instance->triggered = false; } } @@ -247,8 +234,8 @@ void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, model->rssi = rssi; model->rssi_last = instance->rssi_last; model->frequency = frequency; - model->frequency_last = instance->frequency_last; - model->trigger = instance->trigger; + model->frequency_last = instance->frequency_last_vis; + model->trigger = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker); return true; }); } @@ -273,8 +260,8 @@ void subghz_frequency_analyzer_enter(void* context) { instance->rssi_last = 0; instance->frequency_last = 0; - instance->trigger = TRIGGER_MIN; - instance->triggered = false; + instance->frequency_last_vis = 0; + subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, RSSI_MIN); with_view_model( instance->view, (SubGhzFrequencyAnalyzerModel * model) { @@ -282,7 +269,7 @@ void subghz_frequency_analyzer_enter(void* context) { model->rssi_last = 0; model->frequency = 0; model->frequency_last = 0; - model->trigger = instance->trigger; + model->trigger = RSSI_MIN; return true; }); } @@ -330,4 +317,4 @@ void subghz_frequency_analyzer_free(SubGhzFrequencyAnalyzer* instance) { View* subghz_frequency_analyzer_get_view(SubGhzFrequencyAnalyzer* instance) { furi_assert(instance); return instance->view; -} +} \ No newline at end of file From d13b4e3bd5d0b71eb2f0251c0e012408f6715a47 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 11 Aug 2022 00:44:35 +0200 Subject: [PATCH 09/18] feat[playlist]: implementd sending --- applications/playlist/playlist.c | 194 ++++++++++++++++++++++++------- 1 file changed, 153 insertions(+), 41 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 99c7d48d7..9e74b5e6c 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -14,6 +14,9 @@ #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" +#include +#include + #include "playlist_file.h" #define PLAYLIST_FOLDER "/ext/playlist" @@ -44,8 +47,11 @@ typedef struct { DisplayMeta* meta; string_t file_path; // path to the playlist file - bool running; // indicates if the worker is running - bool paused; // can be set to true to pause worker + + 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 + + bool is_running; // indicates if the worker is running } PlaylistWorker; typedef struct { @@ -67,26 +73,39 @@ typedef struct { static int32_t playlist_worker_thread(void* ctx) { PlaylistWorker* worker = ctx; if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { - worker->running = false; + worker->is_running = false; return 0; } - // reset worker meta + // allocate subghz environment + SubGhzEnvironment* environment = subghz_environment_alloc(); - string_t data; + string_t data, preset, protocol; string_init(data); - while(worker->running && flipper_format_read_string(worker->format, "sub", data)) { + string_init(preset); + string_init(protocol); + + FlipperFormat* fff_data = flipper_format_string_alloc(); + + while(flipper_format_read_string(worker->format, "sub", data)) { // wait if paused - while(worker->paused) { + while(!worker->ctl_request_exit && worker->ctl_pause) { + FURI_LOG_I(TAG, "Just Paused. Waiting..."); furi_delay_ms(100); } + // exit loop if requested to stop + if(worker->ctl_request_exit) { + FURI_LOG_I(TAG, "Requested to exit. Exiting loop..."); + break; + } // send .sub files ++worker->meta->current_count; const char* str = string_get_cstr(data); - FURI_LOG_I(TAG, "(worker) data #%d: %s", worker->meta->current_count, str); - // it's not fancy, but it works :) + FURI_LOG_I(TAG, "(worker) Sending %s", str); + + // it's not fancy, but it works for now :) string_reset(worker->meta->prev_3_path); string_set_str(worker->meta->prev_3_path, string_get_cstr(worker->meta->prev_2_path)); string_reset(worker->meta->prev_2_path); @@ -96,20 +115,124 @@ static int32_t playlist_worker_thread(void* ctx) { string_reset(worker->meta->prev_0_path); string_set_str(worker->meta->prev_0_path, str); - FURI_LOG_I(TAG, ""); - FURI_LOG_I(TAG, "(worker) prev_3: %s", string_get_cstr(worker->meta->prev_3_path)); - FURI_LOG_I(TAG, "(worker) prev_2: %s", string_get_cstr(worker->meta->prev_2_path)); - FURI_LOG_I(TAG, "(worker) prev_1: %s", string_get_cstr(worker->meta->prev_1_path)); - FURI_LOG_I(TAG, "(worker) prev_0: %s", string_get_cstr(worker->meta->prev_0_path)); - FURI_LOG_I(TAG, ""); + // actual sending of .sub file + { + // open .sub file + FlipperFormat* fff_file = flipper_format_file_alloc(worker->storage); - furi_delay_ms(1500); // TODO: remove this delay - } - flipper_format_file_close(worker->format); + if(!flipper_format_file_open_existing(fff_file, str)) { + FURI_LOG_E(TAG, " (TX) Failed to open %s", str); + flipper_format_free(fff_file); + continue; + } + + // 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; + } + FURI_LOG_I(TAG, " (TX) Frequency: %u", frequency); + // TODO: check if freq is allowed to transmit + + // check if preset is present + if(!flipper_format_read_string(fff_file, "Preset", preset)) { + FURI_LOG_E(TAG, " (TX) Missing Preset"); + flipper_format_free(fff_file); + continue; + } + + FuriHalSubGhzPreset enum_preset; + if(string_cmp_str(preset, "FuriHalSubGhzPresetOok270Async") == 0) { + enum_preset = FuriHalSubGhzPresetOok270Async; + FURI_LOG_I(TAG, " (TX) Preset: Ook270Async"); + } else if(string_cmp_str(preset, "FuriHalSubGhzPresetOok650Async") == 0) { + enum_preset = FuriHalSubGhzPresetOok650Async; + FURI_LOG_I(TAG, " (TX) Preset: Ook650Async"); + } else if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev238Async") == 0) { + enum_preset = FuriHalSubGhzPreset2FSKDev238Async; + FURI_LOG_I(TAG, " (TX) Preset: 2FSKDev238Async"); + } else if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev476Async") == 0) { + enum_preset = FuriHalSubGhzPreset2FSKDev476Async; + FURI_LOG_I(TAG, " (TX) Preset: 2FSKDev476Async"); + } else if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) { + enum_preset = FuriHalSubGhzPresetMSK99_97KbAsync; + FURI_LOG_I(TAG, " (TX) Preset: MSK99_97KbAsync"); + } else if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) { + enum_preset = FuriHalSubGhzPresetMSK99_97KbAsync; + FURI_LOG_I(TAG, " (TX) Preset: MSK99_97KbAsync"); + } else if(string_cmp_str(preset, "FuriHalSubGhzPresetCustom") == 0) { + enum_preset = FuriHalSubGhzPresetCustom; + FURI_LOG_I(TAG, " (TX) Preset: Custom"); + } else { + FURI_LOG_E(TAG, " (TX) Invalid Preset"); + flipper_format_free(fff_file); + continue; + } + + // check if protocol is present + if(!flipper_format_read_string(fff_file, "Protocol", protocol)) { + FURI_LOG_E(TAG, " (TX) Missing Protocol"); + flipper_format_free(fff_file); + continue; + } + FURI_LOG_I(TAG, " (TX) Protocol: %s", string_get_cstr(protocol)); + + if(!string_cmp_str(protocol, "RAW")) { + subghz_protocol_raw_gen_fff_data(fff_data, str); + } else { + stream_copy_full( + flipper_format_get_raw_stream(fff_file), + flipper_format_get_raw_stream(fff_data)); + } + flipper_format_free(fff_file); + + // (try to) send file + SubGhzTransmitter* transmitter = + subghz_transmitter_alloc_init(environment, string_get_cstr(protocol)); + + subghz_transmitter_deserialize(transmitter, fff_data); + + furi_hal_subghz_reset(); + furi_hal_subghz_load_preset(enum_preset); + + frequency = furi_hal_subghz_set_frequency_and_path(frequency); + + furi_hal_power_suppress_charge_enter(); + + FURI_LOG_I(TAG, " (TX) Start sending ..."); + + furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); + while(!furi_hal_subghz_is_async_tx_complete()) { + if(worker->ctl_request_exit || worker->ctl_pause) { + FURI_LOG_I(TAG, " (TX) Requested to exit. Cancelling sending..."); + break; + } + FURI_LOG_I(TAG, " (TX) Sending..."); + furi_delay_ms(100); + } + FURI_LOG_I(TAG, " (TX) Done sending."); + furi_hal_subghz_stop_async_tx(); + furi_hal_subghz_sleep(); + + furi_hal_power_suppress_charge_exit(); + + subghz_transmitter_free(transmitter); + } // end of start_send section + } // end of loop + + FURI_LOG_I(TAG, "Exited Loop. Clean Up."); string_clear(data); + string_clear(preset); + string_clear(protocol); + + FURI_LOG_I(TAG, " Cleaning up TX"); + subghz_environment_free(environment); + + flipper_format_file_close(worker->format); FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); - worker->running = false; + worker->is_running = false; return 0; } @@ -156,7 +279,7 @@ PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) { instance->format = flipper_format_file_alloc(instance->storage); instance->meta = meta; - instance->paused = true; // require the user to manually start the worker + instance->ctl_pause = true; // require the user to manually start the worker string_init(instance->file_path); @@ -177,23 +300,23 @@ void playlist_worker_free(PlaylistWorker* instance) { void playlist_worker_stop(PlaylistWorker* worker) { furi_assert(worker); - furi_assert(worker->running); + furi_assert(worker->is_running); - worker->running = false; + worker->ctl_request_exit = true; furi_thread_join(worker->thread); } bool playlist_worker_running(PlaylistWorker* worker) { furi_assert(worker); - return worker->running; + return worker->is_running; } void playlist_worker_start(PlaylistWorker* instance, const char* file_path) { furi_assert(instance); - furi_assert(!instance->running); + furi_assert(!instance->is_running); string_set_str(instance->file_path, file_path); - instance->running = true; + instance->is_running = true; // reset meta (current/total) playlist_meta_reset(instance->meta); @@ -309,10 +432,10 @@ static void render_callback(Canvas* canvas, void* ctx) { canvas, WIDTH - ctl_w / 2, HEIGHT / 2 - ctl_h / 2 + disc_r + 1, disc_r); // draw texts - if(!app->worker->running) { + if(!app->worker->is_running) { canvas_draw_str_aligned( canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "STA"); - } else if(app->worker->paused) { + } else if(app->worker->ctl_pause) { canvas_draw_str_aligned( canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "RES"); } else { @@ -404,7 +527,6 @@ int32_t playlist_app(void* p) { //////////////////////////////////////////////////////////////////////////////// - FURI_LOG_I(TAG, "Starting thread ..."); app->worker = playlist_worker_alloc(meta); // count playlist items @@ -412,7 +534,6 @@ int32_t playlist_app(void* p) { Storage* storage = furi_record_open(RECORD_STORAGE); app->meta->total_count = playlist_count_playlist_items(storage, string_get_cstr(app->file_path)); - FURI_LOG_I(TAG, "Selected file contains %d playlist items.", app->meta->total_count); furi_record_close(RECORD_STORAGE); } @@ -424,25 +545,16 @@ int32_t playlist_app(void* p) { bool exit_loop = false; InputEvent input; while(1) { // close application if no file was selected - FURI_LOG_I(TAG, "Checking queue"); furi_check( furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - FURI_LOG_I( - TAG, - "Key: %s, Type: %s", - input_get_key_name(input.key), - input_get_type_name(input.type)); - switch(input.key) { case InputKeyOk: // toggle pause state - if(!app->worker->running) { - FURI_LOG_I(TAG, "Worker is NOT running. Starting worker."); + if(!app->worker->is_running) { playlist_worker_start(app->worker, string_get_cstr(app->file_path)); } else { - FURI_LOG_I(TAG, "Worker IS running. Toggled pause state."); - app->worker->paused = !app->worker->paused; + app->worker->ctl_pause = !app->worker->ctl_pause; } break; case InputKeyBack: @@ -474,4 +586,4 @@ exit_cleanup: playlist_free(app); return 0; -} \ No newline at end of file +} From 4c2e38b51cf6fd8f2f34d34cd4f6237d8b45946e Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 11 Aug 2022 01:33:41 +0200 Subject: [PATCH 10/18] refactor[playlist]: repetitions --- applications/playlist/playlist.c | 281 +++++++++++++++++++------------ 1 file changed, 170 insertions(+), 111 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 9e74b5e6c..e2c8bef0f 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -32,6 +32,9 @@ typedef struct { int current_count; // number of processed files int total_count; // number of items in the playlist + int single_repetitions; // number of times to repeat items in the playlist + int current_single_repetition; // current single repetition + // last 3 files string_t prev_0_path; // current file string_t prev_1_path; // previous file @@ -70,6 +73,125 @@ typedef struct { //////////////////////////////////////////////////////////////////////////////// +static FuriHalSubGhzPreset str_to_preset(string_t preset) { + if(string_cmp_str(preset, "FuriHalSubGhzPresetOok270Async") == 0) { + return FuriHalSubGhzPresetOok270Async; + } + if(string_cmp_str(preset, "FuriHalSubGhzPresetOok650Async") == 0) { + return FuriHalSubGhzPresetOok650Async; + } + if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev238Async") == 0) { + return FuriHalSubGhzPreset2FSKDev238Async; + } + if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev476Async") == 0) { + return FuriHalSubGhzPreset2FSKDev476Async; + } + if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) { + return FuriHalSubGhzPresetMSK99_97KbAsync; + } + if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) { + return FuriHalSubGhzPresetMSK99_97KbAsync; + } + return FuriHalSubGhzPresetCustom; +} + +// -4: missing protocol +// -3: missing preset +// -2: transmit error +// -1: error +// 0: ok +// 1: resend +// 2: exited +static int playlist_worker_process( + PlaylistWorker* worker, + FlipperFormat* fff_file, + FlipperFormat* fff_data, + const char* path, + string_t preset, + string_t protocol) { + // 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; + } + + // 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(!furi_hal_subghz_is_tx_allowed(frequency)) { + return -2; + } + FURI_LOG_I(TAG, " (TX) Frequency: %u", frequency); + + // check if preset is present + if(!flipper_format_read_string(fff_file, "Preset", preset)) { + FURI_LOG_E(TAG, " (TX) Missing Preset"); + return -3; + } + + // check if protocol is present + if(!flipper_format_read_string(fff_file, "Protocol", protocol)) { + FURI_LOG_E(TAG, " (TX) Missing Protocol"); + return -4; + } + FURI_LOG_I(TAG, " (TX) Protocol: %s", string_get_cstr(protocol)); + + if(!string_cmp_str(protocol, "RAW")) { + subghz_protocol_raw_gen_fff_data(fff_data, path); + } else { + stream_copy_full( + flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); + } + flipper_format_free(fff_file); + + // (try to) send file + SubGhzEnvironment* environment = subghz_environment_alloc(); + SubGhzTransmitter* transmitter = + subghz_transmitter_alloc_init(environment, string_get_cstr(protocol)); + + subghz_transmitter_deserialize(transmitter, fff_data); + + furi_hal_subghz_reset(); + furi_hal_subghz_load_preset(str_to_preset(preset)); + + frequency = furi_hal_subghz_set_frequency_and_path(frequency); + + furi_hal_power_suppress_charge_enter(); + + FURI_LOG_I(TAG, " (TX) Start sending ..."); + int status = 0; + + furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); + while(!furi_hal_subghz_is_async_tx_complete()) { + if(worker->ctl_request_exit) { + FURI_LOG_I(TAG, " (TX) Requested to exit. Cancelling sending..."); + status = 2; + break; + } + if(worker->ctl_pause) { + FURI_LOG_I(TAG, " (TX) Requested to pause. Cancelling and resending..."); + status = 1; + break; + } + furi_delay_ms(50); + } + + FURI_LOG_I(TAG, " (TX) Done sending."); + + furi_hal_subghz_stop_async_tx(); + furi_hal_subghz_sleep(); + + furi_hal_power_suppress_charge_exit(); + + subghz_transmitter_free(transmitter); + + return status; +} + static int32_t playlist_worker_thread(void* ctx) { PlaylistWorker* worker = ctx; if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { @@ -77,21 +199,19 @@ static int32_t playlist_worker_thread(void* ctx) { return 0; } - // allocate subghz environment - SubGhzEnvironment* environment = subghz_environment_alloc(); + FlipperFormat* fff_data = flipper_format_string_alloc(); string_t data, preset, protocol; string_init(data); string_init(preset); string_init(protocol); - FlipperFormat* fff_data = flipper_format_string_alloc(); - while(flipper_format_read_string(worker->format, "sub", data)) { + worker->meta->current_single_repetition = 0; + // wait if paused - while(!worker->ctl_request_exit && worker->ctl_pause) { - FURI_LOG_I(TAG, "Just Paused. Waiting..."); - furi_delay_ms(100); + while(worker->ctl_pause && !worker->ctl_request_exit) { + furi_delay_ms(50); } // exit loop if requested to stop if(worker->ctl_request_exit) { @@ -99,7 +219,6 @@ static int32_t playlist_worker_thread(void* ctx) { break; } - // send .sub files ++worker->meta->current_count; const char* str = string_get_cstr(data); @@ -115,110 +234,37 @@ static int32_t playlist_worker_thread(void* ctx) { string_reset(worker->meta->prev_0_path); string_set_str(worker->meta->prev_0_path, str); - // actual sending of .sub file - { - // open .sub file + for(int i = 0; i < MAX(1, worker->meta->single_repetitions); i++) { + ++worker->meta->current_single_repetition; + + FURI_LOG_I( + TAG, + "(worker) Sending %s (%d/%d)", + str, + worker->meta->current_single_repetition, + worker->meta->single_repetitions); + FlipperFormat* fff_file = flipper_format_file_alloc(worker->storage); - if(!flipper_format_file_open_existing(fff_file, str)) { - FURI_LOG_E(TAG, " (TX) Failed to open %s", str); + int status = + playlist_worker_process(worker, fff_file, fff_data, str, preset, protocol); + + // if there was an error, fff_file is not already freed + if(status < 0) { flipper_format_free(fff_file); - continue; } - // 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; + // re-send file is paused mid-send + if(status == 1) { + i -= 1; + // errored, skip to next file + } else if(status < 0) { + break; + // exited, exit loop + } else if(status == 2) { + break; } - FURI_LOG_I(TAG, " (TX) Frequency: %u", frequency); - // TODO: check if freq is allowed to transmit - - // check if preset is present - if(!flipper_format_read_string(fff_file, "Preset", preset)) { - FURI_LOG_E(TAG, " (TX) Missing Preset"); - flipper_format_free(fff_file); - continue; - } - - FuriHalSubGhzPreset enum_preset; - if(string_cmp_str(preset, "FuriHalSubGhzPresetOok270Async") == 0) { - enum_preset = FuriHalSubGhzPresetOok270Async; - FURI_LOG_I(TAG, " (TX) Preset: Ook270Async"); - } else if(string_cmp_str(preset, "FuriHalSubGhzPresetOok650Async") == 0) { - enum_preset = FuriHalSubGhzPresetOok650Async; - FURI_LOG_I(TAG, " (TX) Preset: Ook650Async"); - } else if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev238Async") == 0) { - enum_preset = FuriHalSubGhzPreset2FSKDev238Async; - FURI_LOG_I(TAG, " (TX) Preset: 2FSKDev238Async"); - } else if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev476Async") == 0) { - enum_preset = FuriHalSubGhzPreset2FSKDev476Async; - FURI_LOG_I(TAG, " (TX) Preset: 2FSKDev476Async"); - } else if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) { - enum_preset = FuriHalSubGhzPresetMSK99_97KbAsync; - FURI_LOG_I(TAG, " (TX) Preset: MSK99_97KbAsync"); - } else if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) { - enum_preset = FuriHalSubGhzPresetMSK99_97KbAsync; - FURI_LOG_I(TAG, " (TX) Preset: MSK99_97KbAsync"); - } else if(string_cmp_str(preset, "FuriHalSubGhzPresetCustom") == 0) { - enum_preset = FuriHalSubGhzPresetCustom; - FURI_LOG_I(TAG, " (TX) Preset: Custom"); - } else { - FURI_LOG_E(TAG, " (TX) Invalid Preset"); - flipper_format_free(fff_file); - continue; - } - - // check if protocol is present - if(!flipper_format_read_string(fff_file, "Protocol", protocol)) { - FURI_LOG_E(TAG, " (TX) Missing Protocol"); - flipper_format_free(fff_file); - continue; - } - FURI_LOG_I(TAG, " (TX) Protocol: %s", string_get_cstr(protocol)); - - if(!string_cmp_str(protocol, "RAW")) { - subghz_protocol_raw_gen_fff_data(fff_data, str); - } else { - stream_copy_full( - flipper_format_get_raw_stream(fff_file), - flipper_format_get_raw_stream(fff_data)); - } - flipper_format_free(fff_file); - - // (try to) send file - SubGhzTransmitter* transmitter = - subghz_transmitter_alloc_init(environment, string_get_cstr(protocol)); - - subghz_transmitter_deserialize(transmitter, fff_data); - - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(enum_preset); - - frequency = furi_hal_subghz_set_frequency_and_path(frequency); - - furi_hal_power_suppress_charge_enter(); - - FURI_LOG_I(TAG, " (TX) Start sending ..."); - - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); - while(!furi_hal_subghz_is_async_tx_complete()) { - if(worker->ctl_request_exit || worker->ctl_pause) { - FURI_LOG_I(TAG, " (TX) Requested to exit. Cancelling sending..."); - break; - } - FURI_LOG_I(TAG, " (TX) Sending..."); - furi_delay_ms(100); - } - FURI_LOG_I(TAG, " (TX) Done sending."); - furi_hal_subghz_stop_async_tx(); - furi_hal_subghz_sleep(); - - furi_hal_power_suppress_charge_exit(); - - subghz_transmitter_free(transmitter); - } // end of start_send section + } } // end of loop FURI_LOG_I(TAG, "Exited Loop. Clean Up."); @@ -226,10 +272,9 @@ static int32_t playlist_worker_thread(void* ctx) { string_clear(preset); string_clear(protocol); - FURI_LOG_I(TAG, " Cleaning up TX"); - subghz_environment_free(environment); - + FURI_LOG_I(TAG, " Cleaning up FFF"); flipper_format_file_close(worker->format); + flipper_format_file_close(fff_data); FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->is_running = false; @@ -240,6 +285,8 @@ static int32_t playlist_worker_thread(void* ctx) { void playlist_meta_reset(DisplayMeta* instance) { instance->current_count = 0; + instance->current_single_repetition = 0; + string_clear(instance->prev_0_path); string_clear(instance->prev_1_path); string_clear(instance->prev_2_path); @@ -253,6 +300,7 @@ DisplayMeta* playlist_meta_alloc() { string_init(instance->prev_2_path); string_init(instance->prev_3_path); playlist_meta_reset(instance); + instance->single_repetitions = 2; return instance; } @@ -380,6 +428,17 @@ static void render_callback(Canvas* canvas, void* ctx) { // current if(!string_empty_p(app->meta->prev_0_path)) { path_extract_filename(app->meta->prev_0_path, path, true); + + // add repetition to current file + if(app->meta->single_repetitions > 1) { + string_printf( + path, + "%s [%d/%d]", + string_get_cstr(path), + app->meta->current_single_repetition, + app->meta->single_repetitions); + } + int w = canvas_string_width(canvas, string_get_cstr(path)); canvas_set_color(canvas, ColorBlack); canvas_draw_rbox(canvas, 1, 1, w + 4, 12, 2); @@ -399,13 +458,13 @@ static void render_callback(Canvas* canvas, void* ctx) { if(!string_empty_p(app->meta->prev_2_path)) { path_extract_filename(app->meta->prev_2_path, path, true); - canvas_draw_str_aligned(canvas, 6, 26, AlignLeft, AlignTop, string_get_cstr(path)); + canvas_draw_str_aligned(canvas, 3, 26, AlignLeft, AlignTop, string_get_cstr(path)); string_reset(path); } if(!string_empty_p(app->meta->prev_3_path)) { path_extract_filename(app->meta->prev_3_path, path, true); - canvas_draw_str_aligned(canvas, 9, 37, AlignLeft, AlignTop, string_get_cstr(path)); + canvas_draw_str_aligned(canvas, 3, 37, AlignLeft, AlignTop, string_get_cstr(path)); string_reset(path); } From 5cb074fc24b9c6d5c29b6f1dc8d4b96c852ef3f3 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:24:35 +0200 Subject: [PATCH 11/18] fix[playlist]: crash & mass send on pause --- applications/playlist/playlist.c | 41 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index e2c8bef0f..ce53d1529 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -192,6 +192,23 @@ static int playlist_worker_process( return status; } +// true - the worker can continue +// false - the worker should exit +static bool playlist_worker_wait_pause(PlaylistWorker* worker) { + // wait if paused + while(worker->ctl_pause && !worker->ctl_request_exit) { + furi_delay_ms(50); + } + // exit loop if requested to stop + if(worker->ctl_request_exit) { + FURI_LOG_I(TAG, "Requested to exit. Exiting loop..."); + return false; + } + return true; +} + +// TODO: - starten crasht +// TODO: - exit waerend Senden crasht static int32_t playlist_worker_thread(void* ctx) { PlaylistWorker* worker = ctx; if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { @@ -207,19 +224,13 @@ static int32_t playlist_worker_thread(void* ctx) { string_init(protocol); while(flipper_format_read_string(worker->format, "sub", data)) { - worker->meta->current_single_repetition = 0; - - // wait if paused - while(worker->ctl_pause && !worker->ctl_request_exit) { - furi_delay_ms(50); - } - // exit loop if requested to stop - if(worker->ctl_request_exit) { - FURI_LOG_I(TAG, "Requested to exit. Exiting loop..."); + if(!playlist_worker_wait_pause(worker)) { break; } + worker->meta->current_single_repetition = 0; ++worker->meta->current_count; + const char* str = string_get_cstr(data); FURI_LOG_I(TAG, "(worker) Sending %s", str); @@ -235,6 +246,10 @@ static int32_t playlist_worker_thread(void* ctx) { string_set_str(worker->meta->prev_0_path, str); for(int i = 0; i < MAX(1, worker->meta->single_repetitions); i++) { + if(!playlist_worker_wait_pause(worker)) { + break; + } + ++worker->meta->current_single_repetition; FURI_LOG_I( @@ -272,9 +287,7 @@ static int32_t playlist_worker_thread(void* ctx) { string_clear(preset); string_clear(protocol); - FURI_LOG_I(TAG, " Cleaning up FFF"); - flipper_format_file_close(worker->format); - flipper_format_file_close(fff_data); + flipper_format_free(fff_data); FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->is_running = false; @@ -300,7 +313,7 @@ DisplayMeta* playlist_meta_alloc() { string_init(instance->prev_2_path); string_init(instance->prev_3_path); playlist_meta_reset(instance); - instance->single_repetitions = 2; + instance->single_repetitions = 1; return instance; } @@ -640,9 +653,11 @@ exit_cleanup: FURI_LOG_I(TAG, "Thread is still running. Requesting thread to finish ..."); playlist_worker_stop(app->worker); } + FURI_LOG_I(TAG, "Freeing Worker ..."); playlist_worker_free(app->worker); } + FURI_LOG_I(TAG, "Freeing Playlist ..."); playlist_free(app); return 0; } From a8eb53af4ee3722e68229de420c024a40dbd0027 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:48:02 +0200 Subject: [PATCH 12/18] fix[playlist]: resending playlist --- applications/playlist/playlist.c | 70 ++++++++++++++++---------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index ce53d1529..0661114d5 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -125,7 +125,6 @@ static int playlist_worker_process( if(!furi_hal_subghz_is_tx_allowed(frequency)) { return -2; } - FURI_LOG_I(TAG, " (TX) Frequency: %u", frequency); // check if preset is present if(!flipper_format_read_string(fff_file, "Preset", preset)) { @@ -138,7 +137,6 @@ static int playlist_worker_process( FURI_LOG_E(TAG, " (TX) Missing Protocol"); return -4; } - FURI_LOG_I(TAG, " (TX) Protocol: %s", string_get_cstr(protocol)); if(!string_cmp_str(protocol, "RAW")) { subghz_protocol_raw_gen_fff_data(fff_data, path); @@ -207,12 +205,17 @@ static bool playlist_worker_wait_pause(PlaylistWorker* worker) { return true; } -// TODO: - starten crasht -// TODO: - exit waerend Senden crasht 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; - if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { + if(!flipper_format_file_open_existing(fff_head, string_get_cstr(worker->file_path))) { + FURI_LOG_E(TAG, "Failed to open %s", string_get_cstr(worker->file_path)); worker->is_running = false; + + furi_record_close(RECORD_STORAGE); + flipper_format_free(fff_head); return 0; } @@ -223,7 +226,7 @@ static int32_t playlist_worker_thread(void* ctx) { string_init(preset); string_init(protocol); - while(flipper_format_read_string(worker->format, "sub", data)) { + while(flipper_format_read_string(fff_head, "sub", data)) { if(!playlist_worker_wait_pause(worker)) { break; } @@ -233,8 +236,6 @@ static int32_t playlist_worker_thread(void* ctx) { const char* str = string_get_cstr(data); - FURI_LOG_I(TAG, "(worker) Sending %s", str); - // it's not fancy, but it works for now :) string_reset(worker->meta->prev_3_path); string_set_str(worker->meta->prev_3_path, string_get_cstr(worker->meta->prev_2_path)); @@ -259,7 +260,7 @@ static int32_t playlist_worker_thread(void* ctx) { worker->meta->current_single_repetition, worker->meta->single_repetitions); - FlipperFormat* fff_file = flipper_format_file_alloc(worker->storage); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); int status = playlist_worker_process(worker, fff_file, fff_data, str, preset, protocol); @@ -282,7 +283,9 @@ static int32_t playlist_worker_thread(void* ctx) { } } // end of loop - FURI_LOG_I(TAG, "Exited Loop. Clean Up."); + furi_record_close(RECORD_STORAGE); + flipper_format_free(fff_head); + string_clear(data); string_clear(preset); string_clear(protocol); @@ -291,6 +294,7 @@ static int32_t playlist_worker_thread(void* ctx) { FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->is_running = false; + return 0; } @@ -300,10 +304,10 @@ void playlist_meta_reset(DisplayMeta* instance) { instance->current_count = 0; instance->current_single_repetition = 0; - string_clear(instance->prev_0_path); - string_clear(instance->prev_1_path); - string_clear(instance->prev_2_path); - string_clear(instance->prev_3_path); + string_reset(instance->prev_0_path); + string_reset(instance->prev_1_path); + string_reset(instance->prev_2_path); + string_reset(instance->prev_3_path); } DisplayMeta* playlist_meta_alloc() { @@ -336,10 +340,7 @@ PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) { furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, playlist_worker_thread); - instance->storage = furi_record_open(RECORD_STORAGE); - instance->format = flipper_format_file_alloc(instance->storage); instance->meta = meta; - instance->ctl_pause = true; // require the user to manually start the worker string_init(instance->file_path); @@ -349,13 +350,8 @@ PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) { void playlist_worker_free(PlaylistWorker* instance) { furi_assert(instance); - furi_thread_free(instance->thread); - flipper_format_free(instance->format); - furi_record_close(RECORD_STORAGE); - string_clear(instance->file_path); - free(instance); } @@ -382,6 +378,7 @@ void playlist_worker_start(PlaylistWorker* instance, const char* file_path) { // reset meta (current/total) playlist_meta_reset(instance->meta); + FURI_LOG_I(TAG, "Starting thread..."); furi_thread_start(instance->thread); } @@ -553,6 +550,19 @@ Playlist* playlist_alloc(DisplayMeta* meta) { return app; } +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, string_get_cstr(app->file_path)); + furi_record_close(RECORD_STORAGE); + + // start thread + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); +} + void playlist_free(Playlist* app) { string_clear(app->file_path); @@ -599,19 +609,7 @@ int32_t playlist_app(void* p) { //////////////////////////////////////////////////////////////////////////////// - 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, string_get_cstr(app->file_path)); - furi_record_close(RECORD_STORAGE); - } - - // start thread - playlist_worker_start(app->worker, string_get_cstr(app->file_path)); - + playlist_start_worker(app, meta); app->state = STATE_OVERVIEW; bool exit_loop = false; @@ -624,6 +622,8 @@ int32_t playlist_app(void* p) { case InputKeyOk: // toggle pause state if(!app->worker->is_running) { + app->worker->ctl_pause = false; + app->worker->ctl_request_exit = false; playlist_worker_start(app->worker, string_get_cstr(app->file_path)); } else { app->worker->ctl_pause = !app->worker->ctl_pause; From 1a60093689f5eed35608e14e40d4e7e813bd24a4 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 11 Aug 2022 20:05:56 +0200 Subject: [PATCH 13/18] feat[playlist]: overview view --- applications/playlist/canvas_helper.c | 81 ++++++++++++++++ applications/playlist/canvas_helper.h | 5 + applications/playlist/example_playlist.txt | 5 - applications/playlist/playlist.c | 106 ++++++++++++++++++--- 4 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 applications/playlist/canvas_helper.c create mode 100644 applications/playlist/canvas_helper.h delete mode 100644 applications/playlist/example_playlist.txt diff --git a/applications/playlist/canvas_helper.c b/applications/playlist/canvas_helper.c new file mode 100644 index 000000000..ecb2eed8b --- /dev/null +++ b/applications/playlist/canvas_helper.c @@ -0,0 +1,81 @@ +#include + +#define WIDTH 128 +#define HEIGHT 64 + +void draw_centered_boxed_str(Canvas* canvas, int x, int y, int height, int pad, const char* text) { + // get width of text + int w = canvas_string_width(canvas, text); + canvas_draw_rframe(canvas, x, y, w + pad, height, 2); + canvas_draw_str_aligned(canvas, x + pad / 2, y + height / 2, AlignLeft, AlignCenter, text); +} + +void draw_corner_aligned(Canvas* canvas, int width, int height, Align horizontal, Align vertical) { + canvas_set_color(canvas, ColorBlack); + switch(horizontal) { + case AlignLeft: + switch(vertical) { + case AlignTop: + canvas_draw_rbox(canvas, 0, 0, width, height, 3); + canvas_draw_box(canvas, 0, 0, width, 3); + canvas_draw_box(canvas, 0, 0, 3, height); + break; + case AlignCenter: + canvas_draw_rbox(canvas, 0, HEIGHT - height / 2, width, height, 3); + canvas_draw_box(canvas, 0, HEIGHT - height / 2, 3, height); + break; + case AlignBottom: + canvas_draw_rbox(canvas, 0, HEIGHT - height, width, height, 3); + canvas_draw_box(canvas, 0, HEIGHT - height, 3, height); + canvas_draw_box(canvas, 0, HEIGHT - 3, width, 3); + break; + default: + break; + } + break; + case AlignRight: + switch(vertical) { + case AlignTop: + canvas_draw_rbox(canvas, WIDTH - width, 0, width, height, 3); + canvas_draw_box(canvas, WIDTH - width, 0, width, 3); // bottom corner + canvas_draw_box(canvas, WIDTH - 3, 0, 3, height); // right corner + break; + case AlignCenter: + canvas_draw_rbox(canvas, WIDTH - width, HEIGHT / 2 - height / 2, width, height, 3); + canvas_draw_box(canvas, WIDTH - 3, HEIGHT / 2 - height / 2, 3, height); // right corner + break; + case AlignBottom: + canvas_draw_rbox(canvas, WIDTH - width, HEIGHT - height, width, height, 3); + canvas_draw_box(canvas, WIDTH - 3, HEIGHT - height, 3, height); // right corner + canvas_draw_box(canvas, WIDTH - width, HEIGHT - 3, width, 3); // bottom corner + break; + default: + break; + } + break; + case AlignCenter: + switch(vertical) { + case AlignTop: + canvas_draw_rbox(canvas, WIDTH / 2 - width / 2, 0, width, height, 3); + canvas_draw_box(canvas, WIDTH / 2 - width / 2, 0, width, 3); // bottom corner + canvas_draw_box(canvas, WIDTH / 2 - 3, 0, 3, height); // right corner + break; + case AlignCenter: + canvas_draw_rbox( + canvas, WIDTH / 2 - width / 2, HEIGHT / 2 - height / 2, width, height, 3); + canvas_draw_box( + canvas, WIDTH / 2 - 3, HEIGHT / 2 - height / 2, 3, height); // right corner + break; + case AlignBottom: + canvas_draw_rbox(canvas, WIDTH / 2 - width / 2, HEIGHT - height, width, height, 3); + canvas_draw_box(canvas, WIDTH / 2 - 3, HEIGHT - height, 3, height); // right corner + canvas_draw_box(canvas, WIDTH / 2 - width / 2, HEIGHT - 3, width, 3); // bottom corner + break; + default: + break; + } + break; + default: + break; + } +} \ No newline at end of file diff --git a/applications/playlist/canvas_helper.h b/applications/playlist/canvas_helper.h new file mode 100644 index 000000000..cf73bdb32 --- /dev/null +++ b/applications/playlist/canvas_helper.h @@ -0,0 +1,5 @@ +#include + +void draw_centered_boxed_str(Canvas* canvas, int x, int y, int height, int pad, const char* text); + +void draw_corner_aligned(Canvas* canvas, int width, int height, Align horizontal, Align vertical); \ No newline at end of file diff --git a/applications/playlist/example_playlist.txt b/applications/playlist/example_playlist.txt deleted file mode 100644 index 7c7a05191..000000000 --- a/applications/playlist/example_playlist.txt +++ /dev/null @@ -1,5 +0,0 @@ -SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn300AllBit.sub -SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn310AllBit.sub -SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn315AllBit.sub -SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn318AllBit.sub -SUB: /ext/subghz/Garages/deBruijn/Binary/deBruijn390AllBit.sub \ No newline at end of file diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 0661114d5..83ae75dc6 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -18,12 +18,15 @@ #include #include "playlist_file.h" +#include "canvas_helper.h" #define PLAYLIST_FOLDER "/ext/playlist" #define PLAYLIST_EXT ".txt" #define TAG "Playlist" -#define STATE_OVERVIEW 2 +#define STATE_NONE 0 +#define STATE_OVERVIEW 1 +#define STATE_SENDING 2 #define WIDTH 128 #define HEIGHT 64 @@ -40,6 +43,8 @@ typedef struct { string_t prev_1_path; // previous file string_t prev_2_path; // previous previous file string_t prev_3_path; // you get the idea + + int state; // current state } DisplayMeta; typedef struct { @@ -67,8 +72,6 @@ typedef struct { PlaylistWorker* worker; string_t file_path; // Path to the playlist file - - int state; // Current state for rendering } Playlist; //////////////////////////////////////////////////////////////////////////////// @@ -231,6 +234,9 @@ static int32_t playlist_worker_thread(void* ctx) { break; } + // update state to sending + worker->meta->state = STATE_SENDING; + worker->meta->current_single_repetition = 0; ++worker->meta->current_count; @@ -295,6 +301,9 @@ static int32_t playlist_worker_thread(void* ctx) { FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->is_running = false; + // update state to overview + worker->meta->state = STATE_OVERVIEW; + return 0; } @@ -318,6 +327,7 @@ DisplayMeta* playlist_meta_alloc() { string_init(instance->prev_3_path); playlist_meta_reset(instance); instance->single_repetitions = 1; + instance->state = STATE_NONE; return instance; } @@ -389,9 +399,65 @@ static void render_callback(Canvas* canvas, void* ctx) { furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + switch(app->meta->state) { + case STATE_NONE: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, WIDTH / 2, HEIGHT / 2, AlignCenter, AlignCenter, "No playlist loaded"); + break; - switch(app->state) { case STATE_OVERVIEW: + // draw file name + { + string_t playlist_name; + string_init(playlist_name); + path_extract_filename(app->file_path, playlist_name, true); + + canvas_set_font(canvas, FontPrimary); + draw_centered_boxed_str(canvas, 1, 1, 15, 6, string_get_cstr(playlist_name)); + + string_clear(playlist_name); + } + + canvas_set_font(canvas, FontSecondary); + + // draw loaded count + { + string_t str; + string_init_printf(str, "%d Items in playlist", app->meta->total_count); + canvas_draw_str_aligned(canvas, 1, 19, AlignLeft, AlignTop, string_get_cstr(str)); + string_printf(str, "Repetitions: (single) %d", app->meta->single_repetitions); + canvas_draw_str_aligned(canvas, 1, 29, AlignLeft, AlignTop, string_get_cstr(str)); + string_clear(str); + } + + // draw buttons + draw_corner_aligned(canvas, 40, 15, AlignCenter, AlignBottom); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned(canvas, WIDTH / 2 - 7, HEIGHT - 11, AlignLeft, AlignTop, "Start"); + canvas_draw_disc(canvas, WIDTH / 2 - 14, HEIGHT - 8, 3); + + // + canvas_set_color(canvas, ColorBlack); + draw_corner_aligned(canvas, 20, 15, AlignLeft, AlignBottom); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned(canvas, 4, HEIGHT - 11, AlignLeft, AlignTop, "R-"); + + // + canvas_set_color(canvas, ColorBlack); + draw_corner_aligned(canvas, 20, 15, AlignRight, AlignBottom); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned(canvas, WIDTH - 4, HEIGHT - 11, AlignRight, AlignTop, "R+"); + + canvas_set_color(canvas, ColorBlack); + + break; + case STATE_SENDING: // draw progress bar { double progress = (double)app->meta->current_count / (double)app->meta->total_count; @@ -527,8 +593,6 @@ static void input_callback(InputEvent* event, void* ctx) { Playlist* playlist_alloc(DisplayMeta* meta) { Playlist* app = malloc(sizeof(Playlist)); - app->state = 0; - string_init(app->file_path); string_set_str(app->file_path, PLAYLIST_FOLDER); @@ -610,7 +674,7 @@ int32_t playlist_app(void* p) { //////////////////////////////////////////////////////////////////////////////// playlist_start_worker(app, meta); - app->state = STATE_OVERVIEW; + app->meta->state = STATE_OVERVIEW; bool exit_loop = false; InputEvent input; @@ -619,14 +683,28 @@ int32_t playlist_app(void* p) { furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); switch(input.key) { + case InputKeyLeft: + if(input.type == InputTypeShort && app->meta->single_repetitions > 1) { + --app->meta->single_repetitions; + } + break; + + case InputKeyRight: + if(input.type == InputTypeShort) { + ++app->meta->single_repetitions; + } + break; + case InputKeyOk: - // toggle pause state - if(!app->worker->is_running) { - app->worker->ctl_pause = false; - app->worker->ctl_request_exit = false; - playlist_worker_start(app->worker, string_get_cstr(app->file_path)); - } else { - app->worker->ctl_pause = !app->worker->ctl_pause; + if(input.type == InputTypeShort) { + // toggle pause state + if(!app->worker->is_running) { + app->worker->ctl_pause = false; + app->worker->ctl_request_exit = false; + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); + } else { + app->worker->ctl_pause = !app->worker->ctl_pause; + } } break; case InputKeyBack: From f84d5e9b85d072ac0f420b3a348d4ae7dfba0ffa Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 11 Aug 2022 20:47:08 +0200 Subject: [PATCH 14/18] refactor[playlist]: made app a plugin --- applications/meta/application.fam | 2 +- applications/playlist/application.fam | 14 +++++--------- applications/playlist/playlist.c | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/applications/meta/application.fam b/applications/meta/application.fam index f97cdbf36..e238ea761 100644 --- a/applications/meta/application.fam +++ b/applications/meta/application.fam @@ -59,7 +59,6 @@ App( "clock", "spectrum_analyzer", "unirfremix", - "playlist" ], ) @@ -79,5 +78,6 @@ App( "wifi_scanner", "wav_player", "dec_hex_converter", + "sub_playlist", ], ) \ No newline at end of file diff --git a/applications/playlist/application.fam b/applications/playlist/application.fam index 3b0ee1c7d..6b9858d86 100644 --- a/applications/playlist/application.fam +++ b/applications/playlist/application.fam @@ -1,14 +1,10 @@ App( - appid="playlist", - name="Playlist", - apptype=FlipperAppType.APP, + appid="sub_playlist", + name=".sub Playlist", + apptype=FlipperAppType.PLUGIN, entry_point="playlist_app", cdefines=["APP_PLAYLIST"], - requires=[ - "gui", - "dialogs", - ], - icon="A_UniRFRemix_14", + requires=["storage", "gui", "dialogs", "subghz"], stack_size=2 * 1024, - order=11, + order=31, ) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 83ae75dc6..f850dcf10 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -45,6 +45,8 @@ typedef struct { string_t prev_3_path; // you get the idea int state; // current state + + ViewPort* view_port; } DisplayMeta; typedef struct { @@ -76,6 +78,11 @@ typedef struct { //////////////////////////////////////////////////////////////////////////////// +void meta_set_state(DisplayMeta* meta, int state) { + meta->state = state; + view_port_update(meta->view_port); +} + static FuriHalSubGhzPreset str_to_preset(string_t preset) { if(string_cmp_str(preset, "FuriHalSubGhzPresetOok270Async") == 0) { return FuriHalSubGhzPresetOok270Async; @@ -235,7 +242,7 @@ static int32_t playlist_worker_thread(void* ctx) { } // update state to sending - worker->meta->state = STATE_SENDING; + meta_set_state(worker->meta, STATE_SENDING); worker->meta->current_single_repetition = 0; ++worker->meta->current_count; @@ -251,6 +258,7 @@ static int32_t playlist_worker_thread(void* ctx) { string_set_str(worker->meta->prev_1_path, string_get_cstr(worker->meta->prev_0_path)); string_reset(worker->meta->prev_0_path); string_set_str(worker->meta->prev_0_path, str); + view_port_update(worker->meta->view_port); for(int i = 0; i < MAX(1, worker->meta->single_repetitions); i++) { if(!playlist_worker_wait_pause(worker)) { @@ -258,6 +266,7 @@ static int32_t playlist_worker_thread(void* ctx) { } ++worker->meta->current_single_repetition; + view_port_update(worker->meta->view_port); FURI_LOG_I( TAG, @@ -302,7 +311,7 @@ static int32_t playlist_worker_thread(void* ctx) { worker->is_running = false; // update state to overview - worker->meta->state = STATE_OVERVIEW; + meta_set_state(worker->meta, STATE_OVERVIEW); return 0; } @@ -657,6 +666,7 @@ int32_t playlist_app(void* p) { // create app DisplayMeta* meta = playlist_meta_alloc(); Playlist* app = playlist_alloc(meta); + meta->view_port = app->view_port; // select playlist file { @@ -674,7 +684,7 @@ int32_t playlist_app(void* p) { //////////////////////////////////////////////////////////////////////////////// playlist_start_worker(app, meta); - app->meta->state = STATE_OVERVIEW; + meta_set_state(app->meta, STATE_OVERVIEW); bool exit_loop = false; InputEvent input; From 365d2977efccccf655c8022692fd815501dcc24d Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 18 Aug 2022 00:50:19 +0200 Subject: [PATCH 15/18] feat[playlist]: repeat (implements #1) --- applications/playlist/playlist.c | 143 ++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 51 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index f850dcf10..c62c912e9 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -35,8 +35,8 @@ typedef struct { int current_count; // number of processed files int total_count; // number of items in the playlist - int single_repetitions; // number of times to repeat items in the playlist - int current_single_repetition; // current single repetition + int playlist_repetitions; // number of times to repeat the whole playlist + int current_playlist_repetition; // current playlist repetition // last 3 files string_t prev_0_path; // current file @@ -215,27 +215,19 @@ static bool playlist_worker_wait_pause(PlaylistWorker* worker) { return true; } -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; - if(!flipper_format_file_open_existing(fff_head, string_get_cstr(worker->file_path))) { - FURI_LOG_E(TAG, "Failed to open %s", string_get_cstr(worker->file_path)); - worker->is_running = false; - - furi_record_close(RECORD_STORAGE); - flipper_format_free(fff_head); - return 0; +static bool playlist_worker_play_playlist_once( + PlaylistWorker* worker, + Storage* storage, + FlipperFormat* fff_head, + FlipperFormat* fff_data, + string_t data, + string_t preset, + string_t protocol) { + // + if(!flipper_format_rewind(fff_head)) { + FURI_LOG_E(TAG, "Failed to rewind file"); + return false; } - - FlipperFormat* fff_data = flipper_format_string_alloc(); - - string_t data, preset, protocol; - string_init(data); - string_init(preset); - string_init(protocol); - while(flipper_format_read_string(fff_head, "sub", data)) { if(!playlist_worker_wait_pause(worker)) { break; @@ -244,9 +236,7 @@ static int32_t playlist_worker_thread(void* ctx) { // update state to sending meta_set_state(worker->meta, STATE_SENDING); - worker->meta->current_single_repetition = 0; ++worker->meta->current_count; - const char* str = string_get_cstr(data); // it's not fancy, but it works for now :) @@ -260,20 +250,14 @@ static int32_t playlist_worker_thread(void* ctx) { string_set_str(worker->meta->prev_0_path, str); view_port_update(worker->meta->view_port); - for(int i = 0; i < MAX(1, worker->meta->single_repetitions); i++) { + for(int i = 0; i < 1; i++) { if(!playlist_worker_wait_pause(worker)) { break; } - ++worker->meta->current_single_repetition; view_port_update(worker->meta->view_port); - FURI_LOG_I( - TAG, - "(worker) Sending %s (%d/%d)", - str, - worker->meta->current_single_repetition, - worker->meta->single_repetitions); + FURI_LOG_I(TAG, "(worker) Sending %s", str); FlipperFormat* fff_file = flipper_format_file_alloc(storage); @@ -293,10 +277,56 @@ static int32_t playlist_worker_thread(void* ctx) { break; // exited, exit loop } else if(status == 2) { - break; + return false; } } } // end of loop + return true; +} + +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; + if(!flipper_format_file_open_existing(fff_head, string_get_cstr(worker->file_path))) { + FURI_LOG_E(TAG, "Failed to open %s", 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(); + + string_t data, preset, protocol; + string_init(data); + string_init(preset); + string_init(protocol); + + for(int i = 0; i < MAX(1, worker->meta->playlist_repetitions); i++) { + // infinite repetitions if playlist_repetitions is 0 + if(worker->meta->playlist_repetitions <= 0) { + --i; + } + ++worker->meta->current_playlist_repetition; + // send playlist + worker->meta->current_count = 0; + + FURI_LOG_I( + TAG, + "Sending playlist (i %d rep %d b %d)", + i, + worker->meta->current_playlist_repetition, + worker->meta->playlist_repetitions); + + if(!playlist_worker_play_playlist_once( + worker, storage, fff_head, fff_data, data, preset, protocol)) { + break; + } + } furi_record_close(RECORD_STORAGE); flipper_format_free(fff_head); @@ -320,7 +350,6 @@ static int32_t playlist_worker_thread(void* ctx) { void playlist_meta_reset(DisplayMeta* instance) { instance->current_count = 0; - instance->current_single_repetition = 0; string_reset(instance->prev_0_path); string_reset(instance->prev_1_path); @@ -335,7 +364,6 @@ DisplayMeta* playlist_meta_alloc() { string_init(instance->prev_2_path); string_init(instance->prev_3_path); playlist_meta_reset(instance); - instance->single_repetitions = 1; instance->state = STATE_NONE; return instance; } @@ -438,7 +466,14 @@ static void render_callback(Canvas* canvas, void* ctx) { string_t str; string_init_printf(str, "%d Items in playlist", app->meta->total_count); canvas_draw_str_aligned(canvas, 1, 19, AlignLeft, AlignTop, string_get_cstr(str)); - string_printf(str, "Repetitions: (single) %d", app->meta->single_repetitions); + + if(app->meta->playlist_repetitions <= 0) { + string_printf(str, "Repeat: yes", app->meta->playlist_repetitions); + } else if(app->meta->playlist_repetitions == 1) { + string_printf(str, "Repeat: no", app->meta->playlist_repetitions); + } else { + string_printf(str, "Repeat: %dx", app->meta->playlist_repetitions); + } canvas_draw_str_aligned(canvas, 1, 29, AlignLeft, AlignTop, string_get_cstr(str)); string_clear(str); } @@ -498,6 +533,19 @@ static void render_callback(Canvas* canvas, void* ctx) { AlignBottom, string_get_cstr(progress_text)); + canvas_set_color(canvas, ColorBlack); + if(app->meta->playlist_repetitions <= 0) { + string_printf(progress_text, "[%d/Inf]", app->meta->current_playlist_repetition); + } else { + string_printf( + progress_text, + "[%d/%d]", + app->meta->current_playlist_repetition, + app->meta->playlist_repetitions); + } + canvas_draw_str_aligned( + canvas, WIDTH - 1, 1, AlignRight, AlignTop, string_get_cstr(progress_text)); + string_clear(progress_text); } @@ -513,17 +561,6 @@ static void render_callback(Canvas* canvas, void* ctx) { // current if(!string_empty_p(app->meta->prev_0_path)) { path_extract_filename(app->meta->prev_0_path, path, true); - - // add repetition to current file - if(app->meta->single_repetitions > 1) { - string_printf( - path, - "%s [%d/%d]", - string_get_cstr(path), - app->meta->current_single_repetition, - app->meta->single_repetitions); - } - int w = canvas_string_width(canvas, string_get_cstr(path)); canvas_set_color(canvas, ColorBlack); canvas_draw_rbox(canvas, 1, 1, w + 4, 12, 2); @@ -694,14 +731,18 @@ int32_t playlist_app(void* p) { switch(input.key) { case InputKeyLeft: - if(input.type == InputTypeShort && app->meta->single_repetitions > 1) { - --app->meta->single_repetitions; + if(app->meta->state == STATE_OVERVIEW) { + if(input.type == InputTypeShort && app->meta->playlist_repetitions > 0) { + --app->meta->playlist_repetitions; + } } break; case InputKeyRight: - if(input.type == InputTypeShort) { - ++app->meta->single_repetitions; + if(app->meta->state == STATE_OVERVIEW) { + if(input.type == InputTypeShort) { + ++app->meta->playlist_repetitions; + } } break; From 58cc7c1814545f5ef9a98808c1f9ccfd01611d10 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Thu, 18 Aug 2022 01:39:25 +0200 Subject: [PATCH 16/18] feat[playlist]: redesign (implements #2) --- applications/playlist/playlist.c | 152 +++++++++++-------------------- 1 file changed, 55 insertions(+), 97 deletions(-) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index c62c912e9..ea56f3624 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -350,6 +350,7 @@ static int32_t playlist_worker_thread(void* ctx) { void playlist_meta_reset(DisplayMeta* instance) { instance->current_count = 0; + instance->current_playlist_repetition = 0; string_reset(instance->prev_0_path); string_reset(instance->prev_1_path); @@ -365,6 +366,7 @@ DisplayMeta* playlist_meta_alloc() { string_init(instance->prev_3_path); playlist_meta_reset(instance); instance->state = STATE_NONE; + instance->playlist_repetitions = 1; return instance; } @@ -439,6 +441,9 @@ static void render_callback(Canvas* canvas, void* ctx) { canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); + string_t temp_str; + string_init(temp_str); + switch(app->meta->state) { case STATE_NONE: canvas_set_font(canvas, FontPrimary); @@ -449,33 +454,27 @@ static void render_callback(Canvas* canvas, void* ctx) { case STATE_OVERVIEW: // draw file name { - string_t playlist_name; - string_init(playlist_name); - path_extract_filename(app->file_path, playlist_name, true); + path_extract_filename(app->file_path, temp_str, true); canvas_set_font(canvas, FontPrimary); - draw_centered_boxed_str(canvas, 1, 1, 15, 6, string_get_cstr(playlist_name)); - - string_clear(playlist_name); + draw_centered_boxed_str(canvas, 1, 1, 15, 6, string_get_cstr(temp_str)); } canvas_set_font(canvas, FontSecondary); // draw loaded count { - string_t str; - string_init_printf(str, "%d Items in playlist", app->meta->total_count); - canvas_draw_str_aligned(canvas, 1, 19, AlignLeft, AlignTop, string_get_cstr(str)); + string_printf(temp_str, "%d Items in playlist", app->meta->total_count); + canvas_draw_str_aligned(canvas, 1, 19, AlignLeft, AlignTop, string_get_cstr(temp_str)); if(app->meta->playlist_repetitions <= 0) { - string_printf(str, "Repeat: yes", app->meta->playlist_repetitions); + string_printf(temp_str, "Repeat: yes", app->meta->playlist_repetitions); } else if(app->meta->playlist_repetitions == 1) { - string_printf(str, "Repeat: no", app->meta->playlist_repetitions); + string_printf(temp_str, "Repeat: no", app->meta->playlist_repetitions); } else { - string_printf(str, "Repeat: %dx", app->meta->playlist_repetitions); + string_printf(temp_str, "Repeat: %dx", app->meta->playlist_repetitions); } - canvas_draw_str_aligned(canvas, 1, 29, AlignLeft, AlignTop, string_get_cstr(str)); - string_clear(str); + canvas_draw_str_aligned(canvas, 1, 29, AlignLeft, AlignTop, string_get_cstr(temp_str)); } // draw buttons @@ -502,131 +501,90 @@ static void render_callback(Canvas* canvas, void* ctx) { break; case STATE_SENDING: - // draw progress bar + canvas_set_color(canvas, ColorBlack); + if(app->worker->ctl_pause) { + canvas_draw_icon(canvas, 2, HEIGHT - 8, &I_ButtonRight_4x7); + } else { + canvas_draw_box(canvas, 2, HEIGHT - 8, 2, 7); + canvas_draw_box(canvas, 5, HEIGHT - 8, 2, 7); + } + + // draw progress text { - double progress = (double)app->meta->current_count / (double)app->meta->total_count; - canvas_set_color(canvas, ColorBlack); - canvas_draw_rframe(canvas, 1, HEIGHT - 12, WIDTH - 2, 11, 2); - - if(progress > 0) { - int progress_width = (int)(progress * (double)(WIDTH - 2)); - canvas_draw_rbox(canvas, 1, HEIGHT - 12, progress_width, 11, 2); - } - - // draw progress text - string_t progress_text; - string_init(progress_text); - string_printf( - progress_text, "%d/%d", app->meta->current_count, app->meta->total_count); - - if(progress >= (double).5) { - canvas_set_color(canvas, ColorWhite); - } else { - canvas_set_color(canvas, ColorBlack); - } canvas_set_font(canvas, FontSecondary); + string_printf(temp_str, "[%d/%d]", app->meta->current_count, app->meta->total_count); canvas_draw_str_aligned( - canvas, - WIDTH / 2, - HEIGHT - 3, - AlignCenter, - AlignBottom, - string_get_cstr(progress_text)); + canvas, 11, HEIGHT - 8, AlignLeft, AlignTop, string_get_cstr(temp_str)); - canvas_set_color(canvas, ColorBlack); + int h = canvas_string_width(canvas, string_get_cstr(temp_str)); + int xs = 11 + h + 2; + int w = WIDTH - xs - 1; + canvas_draw_box(canvas, xs, HEIGHT - 5, w, 1); + + float progress = (float)app->meta->current_count / (float)app->meta->total_count; + int wp = (int)(progress * w); + canvas_draw_box(canvas, xs + wp - 1, HEIGHT - 7, 2, 5); + } + + { if(app->meta->playlist_repetitions <= 0) { - string_printf(progress_text, "[%d/Inf]", app->meta->current_playlist_repetition); + string_printf(temp_str, "[%d/Inf]", app->meta->current_playlist_repetition); } else { string_printf( - progress_text, + temp_str, "[%d/%d]", app->meta->current_playlist_repetition, app->meta->playlist_repetitions); } + canvas_set_color(canvas, ColorBlack); + int w = canvas_string_width(canvas, string_get_cstr(temp_str)); + draw_corner_aligned(canvas, w + 6, 13, AlignRight, AlignTop); + canvas_set_color(canvas, ColorWhite); canvas_draw_str_aligned( - canvas, WIDTH - 1, 1, AlignRight, AlignTop, string_get_cstr(progress_text)); - - string_clear(progress_text); + canvas, WIDTH - 3, 3, AlignRight, AlignTop, string_get_cstr(temp_str)); } // draw last and current file { canvas_set_color(canvas, ColorBlack); - - string_t path; - string_init(path); - canvas_set_font(canvas, FontSecondary); // current if(!string_empty_p(app->meta->prev_0_path)) { - path_extract_filename(app->meta->prev_0_path, path, true); - int w = canvas_string_width(canvas, string_get_cstr(path)); + path_extract_filename(app->meta->prev_0_path, temp_str, true); + int w = canvas_string_width(canvas, string_get_cstr(temp_str)); canvas_set_color(canvas, ColorBlack); canvas_draw_rbox(canvas, 1, 1, w + 4, 12, 2); canvas_set_color(canvas, ColorWhite); - canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, string_get_cstr(path)); - string_reset(path); + canvas_draw_str_aligned( + canvas, 3, 3, AlignLeft, AlignTop, string_get_cstr(temp_str)); } // last 3 canvas_set_color(canvas, ColorBlack); if(!string_empty_p(app->meta->prev_1_path)) { - path_extract_filename(app->meta->prev_1_path, path, true); - canvas_draw_str_aligned(canvas, 3, 15, AlignLeft, AlignTop, string_get_cstr(path)); - string_reset(path); + path_extract_filename(app->meta->prev_1_path, temp_str, true); + canvas_draw_str_aligned( + canvas, 3, 15, AlignLeft, AlignTop, string_get_cstr(temp_str)); } if(!string_empty_p(app->meta->prev_2_path)) { - path_extract_filename(app->meta->prev_2_path, path, true); - canvas_draw_str_aligned(canvas, 3, 26, AlignLeft, AlignTop, string_get_cstr(path)); - string_reset(path); + path_extract_filename(app->meta->prev_2_path, temp_str, true); + canvas_draw_str_aligned( + canvas, 3, 26, AlignLeft, AlignTop, string_get_cstr(temp_str)); } if(!string_empty_p(app->meta->prev_3_path)) { - path_extract_filename(app->meta->prev_3_path, path, true); - canvas_draw_str_aligned(canvas, 3, 37, AlignLeft, AlignTop, string_get_cstr(path)); - string_reset(path); - } - - string_clear(path); - } - - // draw controls - { - canvas_set_font(canvas, FontSecondary); - - const int ctl_w = 24; - const int ctl_h = 18; - - // draw background - canvas_set_color(canvas, ColorBlack); - canvas_draw_rbox(canvas, WIDTH - ctl_w, HEIGHT / 2 - ctl_h / 2, ctl_w, ctl_h, 3); - canvas_draw_box(canvas, WIDTH - 3, HEIGHT / 2 - ctl_h / 2, 3, ctl_h); // right corner - - // draw circle (OK) - canvas_set_color(canvas, ColorWhite); - - const int disc_r = 3; - canvas_draw_disc( - canvas, WIDTH - ctl_w / 2, HEIGHT / 2 - ctl_h / 2 + disc_r + 1, disc_r); - - // draw texts - if(!app->worker->is_running) { + path_extract_filename(app->meta->prev_3_path, temp_str, true); canvas_draw_str_aligned( - canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "STA"); - } else if(app->worker->ctl_pause) { - canvas_draw_str_aligned( - canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "RES"); - } else { - canvas_draw_str_aligned( - canvas, WIDTH - ctl_w / 2, HEIGHT / 2 + 4, AlignCenter, AlignCenter, "PAU"); + canvas, 3, 37, AlignLeft, AlignTop, string_get_cstr(temp_str)); } } break; } + string_clear(temp_str); furi_mutex_release(app->mutex); } From b10727435a012efac78f35812637817b66a89861 Mon Sep 17 00:00:00 2001 From: Daniel <71837281+darmiel@users.noreply.github.com> Date: Wed, 24 Aug 2022 15:36:30 +0200 Subject: [PATCH 17/18] fix: removed converter app (missed that while merging) --- applications/meta/application.fam | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/meta/application.fam b/applications/meta/application.fam index d8c152783..6e8194f6a 100644 --- a/applications/meta/application.fam +++ b/applications/meta/application.fam @@ -76,7 +76,6 @@ App( "esp8266_deauth", "wifi_scanner", "wav_player", - "dec_hex_converter", "sub_playlist", ], ) \ No newline at end of file From e0efb2bf3747014a46529c53a5ec947af9285826 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Sep 2022 17:04:15 +0300 Subject: [PATCH 18/18] change log type and move power suppress --- applications/playlist/application.fam | 2 +- applications/playlist/playlist.c | 39 ++++++++++++++------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/applications/playlist/application.fam b/applications/playlist/application.fam index 6b9858d86..df750d2c4 100644 --- a/applications/playlist/application.fam +++ b/applications/playlist/application.fam @@ -6,5 +6,5 @@ App( cdefines=["APP_PLAYLIST"], requires=["storage", "gui", "dialogs", "subghz"], stack_size=2 * 1024, - order=31, + order=14, ) diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index ea56f3624..246774598 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -20,7 +20,7 @@ #include "playlist_file.h" #include "canvas_helper.h" -#define PLAYLIST_FOLDER "/ext/playlist" +#define PLAYLIST_FOLDER "/ext/subplaylist" #define PLAYLIST_EXT ".txt" #define TAG "Playlist" @@ -168,33 +168,29 @@ static int playlist_worker_process( frequency = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_power_suppress_charge_enter(); - - FURI_LOG_I(TAG, " (TX) Start sending ..."); + FURI_LOG_D(TAG, " (TX) Start sending ..."); int status = 0; furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); while(!furi_hal_subghz_is_async_tx_complete()) { if(worker->ctl_request_exit) { - FURI_LOG_I(TAG, " (TX) Requested to exit. Cancelling sending..."); + FURI_LOG_D(TAG, " (TX) Requested to exit. Cancelling sending..."); status = 2; break; } if(worker->ctl_pause) { - FURI_LOG_I(TAG, " (TX) Requested to pause. Cancelling and resending..."); + FURI_LOG_D(TAG, " (TX) Requested to pause. Cancelling and resending..."); status = 1; break; } furi_delay_ms(50); } - FURI_LOG_I(TAG, " (TX) Done sending."); + FURI_LOG_D(TAG, " (TX) Done sending."); furi_hal_subghz_stop_async_tx(); furi_hal_subghz_sleep(); - furi_hal_power_suppress_charge_exit(); - subghz_transmitter_free(transmitter); return status; @@ -209,7 +205,7 @@ static bool playlist_worker_wait_pause(PlaylistWorker* worker) { } // exit loop if requested to stop if(worker->ctl_request_exit) { - FURI_LOG_I(TAG, "Requested to exit. Exiting loop..."); + FURI_LOG_D(TAG, "Requested to exit. Exiting loop..."); return false; } return true; @@ -257,7 +253,7 @@ static bool playlist_worker_play_playlist_once( view_port_update(worker->meta->view_port); - FURI_LOG_I(TAG, "(worker) Sending %s", str); + FURI_LOG_D(TAG, "(worker) Sending %s", str); FlipperFormat* fff_file = flipper_format_file_alloc(storage); @@ -315,7 +311,7 @@ static int32_t playlist_worker_thread(void* ctx) { // send playlist worker->meta->current_count = 0; - FURI_LOG_I( + FURI_LOG_D( TAG, "Sending playlist (i %d rep %d b %d)", i, @@ -337,7 +333,7 @@ static int32_t playlist_worker_thread(void* ctx) { flipper_format_free(fff_data); - FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); + FURI_LOG_D(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->is_running = false; // update state to overview @@ -427,7 +423,7 @@ void playlist_worker_start(PlaylistWorker* instance, const char* file_path) { // reset meta (current/total) playlist_meta_reset(instance->meta); - FURI_LOG_I(TAG, "Starting thread..."); + FURI_LOG_D(TAG, "Starting thread..."); furi_thread_start(instance->thread); } @@ -663,11 +659,13 @@ int32_t playlist_app(void* p) { Playlist* app = playlist_alloc(meta); meta->view_port = app->view_port; + furi_hal_power_suppress_charge_enter(); + // select playlist file { DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const bool res = dialog_file_browser_show( - dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); + dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, false); furi_record_close(RECORD_DIALOGS); // check if a file was selected if(!res) { @@ -717,7 +715,7 @@ int32_t playlist_app(void* p) { } break; case InputKeyBack: - FURI_LOG_I(TAG, "Pressed Back button. Application will exit"); + FURI_LOG_D(TAG, "Pressed Back button. Application will exit"); exit_loop = true; break; default: @@ -735,16 +733,19 @@ int32_t playlist_app(void* p) { } exit_cleanup: + + furi_hal_power_suppress_charge_exit(); + if(app->worker != NULL) { if(playlist_worker_running(app->worker)) { - FURI_LOG_I(TAG, "Thread is still running. Requesting thread to finish ..."); + FURI_LOG_D(TAG, "Thread is still running. Requesting thread to finish ..."); playlist_worker_stop(app->worker); } - FURI_LOG_I(TAG, "Freeing Worker ..."); + FURI_LOG_D(TAG, "Freeing Worker ..."); playlist_worker_free(app->worker); } - FURI_LOG_I(TAG, "Freeing Playlist ..."); + FURI_LOG_D(TAG, "Freeing Playlist ..."); playlist_free(app); return 0; }