diff --git a/ReadMe.md b/ReadMe.md index d3bf7f56d..332f522d0 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -95,6 +95,7 @@ Also check changelog in releases for latest updates! - i2c Tools [(By NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) - C0 -> SCL / C1 -> SDA / GND -> GND | 3v3 logic levels only! - Temperature Sensor Plugin - HTU21D / SI7021 [(By Mywk)](https://github.com/Mywk/FlipperTemperatureSensor) - [How to Connect](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/applications/plugins/temperature_sensor/Readme.md) - HC-SR04 Distance sensor - Ported and modified by @xMasterX [(original by Sanqui)](https://github.com/Sanqui/flipperzero-firmware/tree/hc_sr04) - How to connect -> (5V -> VCC) / (GND -> GND) / (13|TX -> Trig) / (14|RX -> Echo) +- Morse Code - [(by wh00hw)](https://github.com/wh00hw/MorseCodeFAP) Games: - DOOM (fixed) [(By p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) diff --git a/applications/plugins/morse_code/application.fam b/applications/plugins/morse_code/application.fam new file mode 100644 index 000000000..47a986233 --- /dev/null +++ b/applications/plugins/morse_code/application.fam @@ -0,0 +1,15 @@ +App( + appid="morse_code", + name="Morse Code", + apptype=FlipperAppType.EXTERNAL, + entry_point="morse_code_app", + cdefines=["APP_MORSE_CODE"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + order=20, + fap_icon="morse_code_10px.png", + fap_category="Music" + +) \ No newline at end of file diff --git a/applications/plugins/morse_code/morse_code.c b/applications/plugins/morse_code/morse_code.c new file mode 100644 index 000000000..0b4790721 --- /dev/null +++ b/applications/plugins/morse_code/morse_code.c @@ -0,0 +1,161 @@ +#include "morse_code_worker.h" +#include +#include +#include +#include +#include +#include +#include + +static const float MORSE_CODE_VOLUMES[] = {0, .25, .5, .75, 1}; + +typedef struct { + FuriString* words; + uint8_t volume; + uint32_t dit_delta; +} MorseCodeModel; + +typedef struct { + MorseCodeModel* model; + FuriMutex** model_mutex; + + FuriMessageQueue* input_queue; + + ViewPort* view_port; + Gui* gui; + + MorseCodeWorker* worker; +} MorseCode; + + +static void render_callback(Canvas* const canvas, void* ctx) { + MorseCode* morse_code = ctx; + furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); + // border around the edge of the screen + canvas_set_font(canvas, FontPrimary); + + //write words + elements_multiline_text_aligned(canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words)); + + // volume view_port + uint8_t vol_bar_x_pos = 124; + uint8_t vol_bar_y_pos = 0; + const uint8_t volume_h = + (64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume; + canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64); + canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h); + + //dit bpm + canvas_draw_str_aligned( + canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta))); + + //button info + elements_button_center(canvas, "Press/Hold"); + furi_mutex_release(morse_code->model_mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + MorseCode* morse_code = ctx; + furi_message_queue_put(morse_code->input_queue, input_event, FuriWaitForever); +} + +static void morse_code_worker_callback( + FuriString* words, + void* context) { + MorseCode* morse_code = context; + furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); + morse_code->model->words = words; + furi_mutex_release(morse_code->model_mutex); + view_port_update(morse_code->view_port); +} + +MorseCode* morse_code_alloc() { + MorseCode* instance = malloc(sizeof(MorseCode)); + + instance->model = malloc(sizeof(MorseCodeModel)); + instance->model->words = furi_string_alloc_set_str(""); + instance->model->volume = 3; + instance->model->dit_delta = 150; + instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->worker = morse_code_worker_alloc(); + + morse_code_worker_set_callback(instance->worker, morse_code_worker_callback, instance); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, render_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + // Open GUI and register view_port + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + return instance; +} + +void morse_code_free(MorseCode* instance) { + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + morse_code_worker_free(instance->worker); + + furi_message_queue_free(instance->input_queue); + + furi_mutex_free(instance->model_mutex); + + free(instance->model); + free(instance); +} + +int32_t morse_code_app() { + MorseCode* morse_code = morse_code_alloc(); + InputEvent input; + morse_code_worker_start(morse_code->worker); + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) == FuriStatusOk){ + furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); + if(input.key == InputKeyBack) { + furi_mutex_release(morse_code->model_mutex); + break; + }else if(input.key == InputKeyOk){ + if(input.type == InputTypePress) + morse_code_worker_play(morse_code->worker, true); + else if(input.type == InputTypeRelease) + morse_code_worker_play(morse_code->worker, false); + }else if(input.key == InputKeyUp && input.type == InputTypePress){ + if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1) + morse_code->model->volume++; + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + }else if(input.key == InputKeyDown && input.type == InputTypePress){ + if(morse_code->model->volume > 0) + morse_code->model->volume--; + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + }else if(input.key == InputKeyLeft && input.type == InputTypePress){ + if(morse_code->model->dit_delta > 10) + morse_code->model->dit_delta-=10; + morse_code_worker_set_dit_delta( + morse_code->worker, morse_code->model->dit_delta); + } + else if(input.key == InputKeyRight && input.type == InputTypePress){ + if(morse_code->model->dit_delta >= 10) + morse_code->model->dit_delta+=10; + morse_code_worker_set_dit_delta( + morse_code->worker, morse_code->model->dit_delta); + } + + FURI_LOG_D("Input", "%s %s %ld", input_get_key_name(input.key), input_get_type_name(input.type), input.sequence); + + furi_mutex_release(morse_code->model_mutex); + view_port_update(morse_code->view_port); + } + morse_code_worker_stop(morse_code->worker); + morse_code_free(morse_code); + return 0; +} \ No newline at end of file diff --git a/applications/plugins/morse_code/morse_code_10px.png b/applications/plugins/morse_code/morse_code_10px.png new file mode 100644 index 000000000..087c5b239 Binary files /dev/null and b/applications/plugins/morse_code/morse_code_10px.png differ diff --git a/applications/plugins/morse_code/morse_code_worker.c b/applications/plugins/morse_code/morse_code_worker.c new file mode 100644 index 000000000..54ee747c5 --- /dev/null +++ b/applications/plugins/morse_code/morse_code_worker.c @@ -0,0 +1,164 @@ +#include "morse_code_worker.h" +#include +#include + + +#define TAG "MorseCodeWorker" + +#define MORSE_CODE_VERSION 0 + +//A-Z0-1 +const char morse_array[36][6] ={ + ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", + "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", ".----", "..---", "...--", "....-", ".....", + "-....", "--...", "---..", "----.", "-----" + }; +const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + +struct MorseCodeWorker { + FuriThread* thread; + MorseCodeWorkerCallback callback; + void* callback_context; + bool is_running; + bool play; + float volume; + uint32_t dit_delta; + FuriString* buffer; + FuriString* words; +}; + +void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration){ + FURI_LOG_D("MorseCode: Duration", "%ld", duration); + if( duration <= instance->dit_delta) + furi_string_push_back(instance->buffer, *DOT); + else if(duration <= (instance->dit_delta * 3)) + furi_string_push_back(instance->buffer, *LINE); + if(furi_string_size(instance->buffer) > 5) + furi_string_reset(instance->buffer); + FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer)); +} + +void morse_code_worker_fill_letter(MorseCodeWorker* instance){ + if(furi_string_size(instance->words) > 63) + furi_string_reset(instance->words); + for (size_t i = 0; i < sizeof(morse_array); i++){ + if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0){ + furi_string_push_back(instance->words, symbol_array[i]); + furi_string_reset(instance->buffer); + break; + } + } + FURI_LOG_D("MorseCode: Words", "%s", furi_string_get_cstr(instance->words)); +} + + +static int32_t morse_code_worker_thread_callback(void* context) { + furi_assert(context); + MorseCodeWorker* instance = context; + bool was_playing = false; + uint32_t start_tick = 0; + uint32_t end_tick = 0; + bool pushed = true; + bool spaced = true; + while(instance->is_running){ + furi_delay_ms(SLEEP); + if(instance->play){ + if(!was_playing){ + start_tick = furi_get_tick(); + furi_hal_speaker_start(FREQUENCY, instance->volume); + was_playing = true; + } + }else{ + if(was_playing){ + pushed = false; + spaced = false; + furi_hal_speaker_stop(); + end_tick = furi_get_tick(); + was_playing = false; + morse_code_worker_fill_buffer(instance, end_tick - start_tick); + start_tick = 0; + } + } + if(!pushed){ + if(end_tick + (instance->dit_delta * 3) < furi_get_tick()){ + //NEW LETTER + morse_code_worker_fill_letter(instance); + if(instance->callback) + instance->callback(instance->words, instance->callback_context); + pushed = true; + } + } + if(!spaced){ + if(end_tick + (instance->dit_delta * 7) < furi_get_tick()){ + //NEW WORD + furi_string_push_back(instance->words, *SPACE); + if(instance->callback) + instance->callback(instance->words, instance->callback_context); + spaced = true; + } + } + } + return 0; +} + +MorseCodeWorker* morse_code_worker_alloc() { + MorseCodeWorker* instance = malloc(sizeof(MorseCodeWorker)); + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "MorseCodeWorker"); + furi_thread_set_stack_size(instance->thread, 1024); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, morse_code_worker_thread_callback); + instance->play = false; + instance->volume = 1.0f; + instance->dit_delta = 150; + instance->buffer = furi_string_alloc_set_str(""); + instance->words = furi_string_alloc_set_str(""); + return instance; +} + +void morse_code_worker_free(MorseCodeWorker* instance) { + furi_assert(instance); + furi_thread_free(instance->thread); + free(instance); +} + +void morse_code_worker_set_callback( + MorseCodeWorker* instance, + MorseCodeWorkerCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->callback_context = context; +} + +void morse_code_worker_play(MorseCodeWorker* instance, bool play){ + furi_assert(instance); + instance->play = play; +} + +void morse_code_worker_set_volume(MorseCodeWorker* instance, float level){ + furi_assert(instance); + instance->volume = level; +} + +void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta){ + furi_assert(instance); + instance->dit_delta = delta; +} + +void morse_code_worker_start(MorseCodeWorker* instance) { + furi_assert(instance); + furi_assert(instance->is_running == false); + instance->is_running = true; + furi_thread_start(instance->thread); + FURI_LOG_D("MorseCode: Start", "is Running"); +} + +void morse_code_worker_stop(MorseCodeWorker* instance) { + furi_assert(instance); + furi_assert(instance->is_running == true); + instance->is_running = false; + furi_thread_join(instance->thread); + FURI_LOG_D("MorseCode: Stop", "Stop"); +} diff --git a/applications/plugins/morse_code/morse_code_worker.h b/applications/plugins/morse_code/morse_code_worker.h new file mode 100644 index 000000000..cc84a2674 --- /dev/null +++ b/applications/plugins/morse_code/morse_code_worker.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#define FREQUENCY 261.63f +#define SLEEP 10 +#define DOT "." +#define LINE "-" +#define SPACE " " + +typedef void (*MorseCodeWorkerCallback)( + FuriString* buffer, + void* context); + +typedef struct MorseCodeWorker MorseCodeWorker; + +MorseCodeWorker* morse_code_worker_alloc(); + +void morse_code_worker_free(MorseCodeWorker* instance); + +void morse_code_worker_set_callback( + MorseCodeWorker* instance, + MorseCodeWorkerCallback callback, + void* context); + +void morse_code_worker_start(MorseCodeWorker* instance); + +void morse_code_worker_stop(MorseCodeWorker* instance); + +void morse_code_worker_play(MorseCodeWorker* instance, bool play); + +void morse_code_worker_set_volume(MorseCodeWorker* instance, float level); + +void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta); + + + + + +