From 25ce710b6e734f6325a35dc3c630ea0f23408c55 Mon Sep 17 00:00:00 2001 From: VerstreuteSeele Date: Wed, 8 Feb 2023 04:32:23 +0100 Subject: [PATCH] Added multiple apps Added Geiger Counter, Nightstand, Scrambler, Pomodoro --- .../plugins/brainfuck/application.fam | 15 + applications/plugins/brainfuck/bfico.png | Bin 0 -> 1822 bytes applications/plugins/brainfuck/brainfuck.c | 149 +++++++ applications/plugins/brainfuck/brainfuck.h | 3 + applications/plugins/brainfuck/brainfuck_i.h | 89 ++++ .../brainfuck/icons/ButtonRightSmall_3x5.png | Bin 0 -> 1738 bytes .../icons/KeyBackspaceSelected_24x11.png | Bin 0 -> 1977 bytes .../brainfuck/icons/KeyBackspace_24x11.png | Bin 0 -> 1979 bytes .../icons/KeyInputSelected_30x11.png | Bin 0 -> 1992 bytes .../brainfuck/icons/KeyInput_30x11.png | Bin 0 -> 1994 bytes .../brainfuck/icons/KeyRunSelected_24x11.png | Bin 0 -> 1984 bytes .../plugins/brainfuck/icons/KeyRun_24x11.png | Bin 0 -> 1984 bytes .../brainfuck/icons/KeySaveSelected_24x11.png | Bin 0 -> 1853 bytes .../plugins/brainfuck/icons/KeySave_24x11.png | Bin 0 -> 1863 bytes .../plugins/brainfuck/icons/bfico.png | Bin 0 -> 1822 bytes .../brainfuck/scenes/brainfuck_scene.c | 30 ++ .../brainfuck/scenes/brainfuck_scene.h | 29 ++ .../brainfuck/scenes/brainfuck_scene_config.h | 6 + .../brainfuck/scenes/brainfuck_scene_dev.c | 16 + .../brainfuck/scenes/brainfuck_scene_exec.c | 16 + .../scenes/brainfuck_scene_file_create.c | 50 +++ .../scenes/brainfuck_scene_file_select.c | 34 ++ .../scenes/brainfuck_scene_set_input.c | 35 ++ .../brainfuck/scenes/brainfuck_scene_start.c | 55 +++ .../plugins/brainfuck/views/bf_dev_env.c | 419 ++++++++++++++++++ .../plugins/brainfuck/views/bf_dev_env.h | 15 + applications/plugins/brainfuck/worker.c | 276 ++++++++++++ applications/plugins/brainfuck/worker.h | 9 + .../plugins/geigercounter/application.fam | 13 + .../plugins/geigercounter/flipper_geiger.c | 227 ++++++++++ applications/plugins/geigercounter/geiger.png | Bin 0 -> 8048 bytes applications/plugins/nightstand/ClockIcon.png | Bin 0 -> 7920 bytes .../plugins/nightstand/application.fam | 13 + applications/plugins/nightstand/clock_app.c | 338 ++++++++++++++ applications/plugins/nightstand/clock_app.h | 39 ++ applications/plugins/pomodoro/application.fam | 12 + .../plugins/pomodoro/flipp_pomodoro_10.png | Bin 0 -> 157 bytes .../plugins/pomodoro/flipp_pomodoro_app.c | 101 +++++ .../plugins/pomodoro/flipp_pomodoro_app.h | 32 ++ .../plugins/pomodoro/flipp_pomodoro_app_i.h | 31 ++ applications/plugins/pomodoro/helpers/debug.h | 5 + .../plugins/pomodoro/helpers/notifications.c | 49 ++ .../plugins/pomodoro/helpers/notifications.h | 14 + applications/plugins/pomodoro/helpers/time.c | 20 + applications/plugins/pomodoro/helpers/time.h | 24 + .../flipp_pomodoro_focus_64/frame_00.png | Bin 0 -> 1242 bytes .../flipp_pomodoro_focus_64/frame_01.png | Bin 0 -> 1215 bytes .../images/flipp_pomodoro_focus_64/frame_rate | 1 + .../flipp_pomodoro_rest_64/frame_00.png | Bin 0 -> 1083 bytes .../flipp_pomodoro_rest_64/frame_01.png | Bin 0 -> 1080 bytes .../images/flipp_pomodoro_rest_64/frame_rate | 1 + .../plugins/pomodoro/modules/flipp_pomodoro.c | 94 ++++ .../plugins/pomodoro/modules/flipp_pomodoro.h | 54 +++ applications/plugins/pomodoro/scenes/.keep | 0 .../config/flipp_pomodoro_scene_config.h | 1 + .../pomodoro/scenes/flipp_pomodoro_scene.c | 30 ++ .../pomodoro/scenes/flipp_pomodoro_scene.h | 27 ++ .../scenes/flipp_pomodoro_scene_timer.c | 71 +++ applications/plugins/pomodoro/views/.keep | 0 .../views/flipp_pomodoro_timer_view.c | 195 ++++++++ .../views/flipp_pomodoro_timer_view.h | 21 + applications/plugins/scrambler/LICENSE | 21 + applications/plugins/scrambler/README.md | 16 + .../plugins/scrambler/application.fam | 20 + applications/plugins/scrambler/assets/1.png | Bin 0 -> 1964 bytes applications/plugins/scrambler/cube.png | Bin 0 -> 96 bytes .../plugins/scrambler/rubiks_cube_scrambler.c | 115 +++++ applications/plugins/scrambler/scrambler.c | 102 +++++ applications/plugins/scrambler/scrambler.h | 3 + 69 files changed, 2936 insertions(+) create mode 100644 applications/plugins/brainfuck/application.fam create mode 100644 applications/plugins/brainfuck/bfico.png create mode 100644 applications/plugins/brainfuck/brainfuck.c create mode 100644 applications/plugins/brainfuck/brainfuck.h create mode 100644 applications/plugins/brainfuck/brainfuck_i.h create mode 100644 applications/plugins/brainfuck/icons/ButtonRightSmall_3x5.png create mode 100644 applications/plugins/brainfuck/icons/KeyBackspaceSelected_24x11.png create mode 100644 applications/plugins/brainfuck/icons/KeyBackspace_24x11.png create mode 100644 applications/plugins/brainfuck/icons/KeyInputSelected_30x11.png create mode 100644 applications/plugins/brainfuck/icons/KeyInput_30x11.png create mode 100644 applications/plugins/brainfuck/icons/KeyRunSelected_24x11.png create mode 100644 applications/plugins/brainfuck/icons/KeyRun_24x11.png create mode 100644 applications/plugins/brainfuck/icons/KeySaveSelected_24x11.png create mode 100644 applications/plugins/brainfuck/icons/KeySave_24x11.png create mode 100644 applications/plugins/brainfuck/icons/bfico.png create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene.c create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene.h create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_config.h create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_dev.c create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_exec.c create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_file_create.c create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_file_select.c create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_set_input.c create mode 100644 applications/plugins/brainfuck/scenes/brainfuck_scene_start.c create mode 100644 applications/plugins/brainfuck/views/bf_dev_env.c create mode 100644 applications/plugins/brainfuck/views/bf_dev_env.h create mode 100644 applications/plugins/brainfuck/worker.c create mode 100644 applications/plugins/brainfuck/worker.h create mode 100644 applications/plugins/geigercounter/application.fam create mode 100644 applications/plugins/geigercounter/flipper_geiger.c create mode 100644 applications/plugins/geigercounter/geiger.png create mode 100644 applications/plugins/nightstand/ClockIcon.png create mode 100644 applications/plugins/nightstand/application.fam create mode 100644 applications/plugins/nightstand/clock_app.c create mode 100644 applications/plugins/nightstand/clock_app.h create mode 100644 applications/plugins/pomodoro/application.fam create mode 100644 applications/plugins/pomodoro/flipp_pomodoro_10.png create mode 100644 applications/plugins/pomodoro/flipp_pomodoro_app.c create mode 100644 applications/plugins/pomodoro/flipp_pomodoro_app.h create mode 100644 applications/plugins/pomodoro/flipp_pomodoro_app_i.h create mode 100644 applications/plugins/pomodoro/helpers/debug.h create mode 100644 applications/plugins/pomodoro/helpers/notifications.c create mode 100644 applications/plugins/pomodoro/helpers/notifications.h create mode 100644 applications/plugins/pomodoro/helpers/time.c create mode 100644 applications/plugins/pomodoro/helpers/time.h create mode 100644 applications/plugins/pomodoro/images/flipp_pomodoro_focus_64/frame_00.png create mode 100644 applications/plugins/pomodoro/images/flipp_pomodoro_focus_64/frame_01.png create mode 100644 applications/plugins/pomodoro/images/flipp_pomodoro_focus_64/frame_rate create mode 100644 applications/plugins/pomodoro/images/flipp_pomodoro_rest_64/frame_00.png create mode 100644 applications/plugins/pomodoro/images/flipp_pomodoro_rest_64/frame_01.png create mode 100644 applications/plugins/pomodoro/images/flipp_pomodoro_rest_64/frame_rate create mode 100644 applications/plugins/pomodoro/modules/flipp_pomodoro.c create mode 100644 applications/plugins/pomodoro/modules/flipp_pomodoro.h create mode 100644 applications/plugins/pomodoro/scenes/.keep create mode 100644 applications/plugins/pomodoro/scenes/config/flipp_pomodoro_scene_config.h create mode 100644 applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.c create mode 100644 applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.h create mode 100644 applications/plugins/pomodoro/scenes/flipp_pomodoro_scene_timer.c create mode 100644 applications/plugins/pomodoro/views/.keep create mode 100644 applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.c create mode 100644 applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.h create mode 100644 applications/plugins/scrambler/LICENSE create mode 100644 applications/plugins/scrambler/README.md create mode 100644 applications/plugins/scrambler/application.fam create mode 100644 applications/plugins/scrambler/assets/1.png create mode 100644 applications/plugins/scrambler/cube.png create mode 100644 applications/plugins/scrambler/rubiks_cube_scrambler.c create mode 100644 applications/plugins/scrambler/scrambler.c create mode 100644 applications/plugins/scrambler/scrambler.h diff --git a/applications/plugins/brainfuck/application.fam b/applications/plugins/brainfuck/application.fam new file mode 100644 index 000000000..6e2b6d1f9 --- /dev/null +++ b/applications/plugins/brainfuck/application.fam @@ -0,0 +1,15 @@ +App( + appid="Brainfuck", + name="Brainfuck", + apptype=FlipperAppType.EXTERNAL, + entry_point="brainfuck_app", + requires=[ + "storage", + "gui", + ], + stack_size=8 * 1024, + fap_icon="bfico.png", + fap_category="Misc", + fap_icon_assets="icons", + fap_icon_assets_symbol="brainfuck", +) diff --git a/applications/plugins/brainfuck/bfico.png b/applications/plugins/brainfuck/bfico.png new file mode 100644 index 0000000000000000000000000000000000000000..b25368fb53e03b73878a9aa17a044c2d8dffea4a GIT binary patch literal 1822 zcma)6eM}o=7(YOmKyZGbBNNT#*c_9!cReT{7dirMfhxy3z6O4=Yws<+(e}=}D=ip7*(DuemTUCTeXIf*>)bd_xg@E9&+&;9yN=+jdK75 zNgO}WHlAeI8at9@wST^cjE;Wo&AI%pY})Zgdviz1zO505+S5*dbUq^E9Q0s*?vo}AfY~MGw zu|Mzolpned^&i|}@9r|UW{rFobKix^-<~<~`{a9D*2O#=KX^8w_sE*&4b4qS;g_z_ z%>ItO>znt#TrbxizB$-4;%pp9ch2lSSGO*{^-4$cf!Wn9^V_xE*PDC9E7G@MaPIMA z4|>R~WYsWTmK4E!G2Km^j|*v-P2Ti%9v(J(z9sA0(ByRSCR5*&W!-zmFpiA<@d0Pp79Ia%{^SR_JA^Z1H*V=KHF3l8&g!tuPanB6%y+!KYUx`BAxQK?$`gWgbjBe_ zcqCU+E|=2M$QM1=orh$50Da& zB0#ljt(w4KIErS9tc@x%g0It(iCNdsPPiQFXx3II!iD;`{ux2YWdl}(wAWi=})ahK5Ey>0jm~fzeL6R4UrIK0! z;6*^<-J%s3Yk*5mz)+0+$5K3LV69PsLrH#lrZ> zXr6^!$r!q1hqEFuDs-4sl$V2=jQUhfMrx{xRKr7+N>L^!Qw0iut4g-}4_vZtbMH0)HCkr@LcL%}2g`%FWsNTP($QYj>* z(IycY6aiEBfq9m*)&3XM*WD=mWx^Pmrt&#S=Ed4YbD(F7!HdgG0i%Oc4uV09RY^^U z2_nzBt$_6jfOd(u$sR@o*;VlVbXc~{#=-P!QM1yws79?J)Ts+tj~zgU;G8rm6p_A= z6TYGVUoWj7z;FM50{qyO1OydzxqwVu`Er+6B@scene_manager, event); +} + +bool brainfuck_back_event_callback(void* context) { + furi_assert(context); + BFApp* brainfuck = context; + return scene_manager_handle_back_event(brainfuck->scene_manager); +} + +BFApp* brainfuck_alloc() { + BFApp* brainfuck = malloc(sizeof(BFApp)); + + brainfuck->dataSize = 0; + brainfuck->view_dispatcher = view_dispatcher_alloc(); + brainfuck->scene_manager = scene_manager_alloc(&brainfuck_scene_handlers, brainfuck); + view_dispatcher_enable_queue(brainfuck->view_dispatcher); + view_dispatcher_set_event_callback_context(brainfuck->view_dispatcher, brainfuck); + view_dispatcher_set_custom_event_callback( + brainfuck->view_dispatcher, brainfuck_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + brainfuck->view_dispatcher, brainfuck_back_event_callback); + + // Open GUI record + brainfuck->gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui( + brainfuck->view_dispatcher, brainfuck->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + brainfuck->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Submenu + brainfuck->submenu = submenu_alloc(); + view_dispatcher_add_view( + brainfuck->view_dispatcher, brainfuckViewMenu, submenu_get_view(brainfuck->submenu)); + + // Popup + brainfuck->popup = popup_alloc(); + view_dispatcher_add_view( + brainfuck->view_dispatcher, brainfuckViewPopup, popup_get_view(brainfuck->popup)); + + // Text Input + brainfuck->text_input = text_input_alloc(); + view_dispatcher_add_view( + brainfuck->view_dispatcher, + brainfuckViewTextInput, + text_input_get_view(brainfuck->text_input)); + + // Textbox + brainfuck->text_box = text_box_alloc(); + view_dispatcher_add_view( + brainfuck->view_dispatcher, brainfuckViewTextBox, text_box_get_view(brainfuck->text_box)); + brainfuck->text_box_store = furi_string_alloc(); + + // Dev environment + brainfuck->BF_dev_env = bf_dev_env_alloc(brainfuck); + view_dispatcher_add_view( + brainfuck->view_dispatcher, brainfuckViewDev, bf_dev_env_get_view(brainfuck->BF_dev_env)); + + // File path + brainfuck->BF_file_path = furi_string_alloc(); + + return brainfuck; +} + +void brainfuck_free(BFApp* brainfuck) { + furi_assert(brainfuck); + + // Submenu + view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewMenu); + submenu_free(brainfuck->submenu); + + // Popup + view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewPopup); + popup_free(brainfuck->popup); + + // TextInput + view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewTextInput); + text_input_free(brainfuck->text_input); + + // TextBox + view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewTextBox); + text_box_free(brainfuck->text_box); + furi_string_free(brainfuck->text_box_store); + + //dev env + view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewDev); + bf_dev_env_free(brainfuck->BF_dev_env); + + // View Dispatcher + view_dispatcher_free(brainfuck->view_dispatcher); + + // Scene Manager + scene_manager_free(brainfuck->scene_manager); + + // GUI + furi_record_close(RECORD_GUI); + brainfuck->gui = NULL; + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + brainfuck->notifications = NULL; + + free(brainfuck); +} + +void brainfuck_show_loading_popup(void* context, bool show) { + BFApp* brainfuck = context; + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(brainfuck->view_dispatcher, brainfuckViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + +int32_t brainfuck_app(void* p) { + UNUSED(p); + BFApp* brainfuck = brainfuck_alloc(); + if(!brainfuck) { + return 0; + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, "/ext/brainfuck"); + + scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneStart); + + view_dispatcher_run(brainfuck->view_dispatcher); + + brainfuck_free(brainfuck); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/brainfuck/brainfuck.h b/applications/plugins/brainfuck/brainfuck.h new file mode 100644 index 000000000..2e58321a6 --- /dev/null +++ b/applications/plugins/brainfuck/brainfuck.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct BFApp BFApp; \ No newline at end of file diff --git a/applications/plugins/brainfuck/brainfuck_i.h b/applications/plugins/brainfuck/brainfuck_i.h new file mode 100644 index 000000000..d3d27dcbd --- /dev/null +++ b/applications/plugins/brainfuck/brainfuck_i.h @@ -0,0 +1,89 @@ +#pragma once + +typedef struct BFDevEnv BFDevEnv; +typedef struct BFExecEnv BFExecEnv; +typedef unsigned char byte; + +#include "brainfuck.h" +#include "worker.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "scenes/brainfuck_scene.h" + +#include "views/bf_dev_env.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define BF_INST_BUFFER_SIZE 2048 +#define BF_OUTPUT_SIZE 512 +#define BF_STACK_INITIAL_SIZE 128 +#define BF_INPUT_BUFFER_SIZE 64 +#define BF_STACK_STEP_SIZE 32 + +enum brainfuckCustomEvent { + // Reserve first 100 events for button types and indexes, starting from 0 + brainfuckCustomEventReserved = 100, + + brainfuckCustomEventViewExit, + brainfuckCustomEventWorkerExit, + brainfuckCustomEventByteInputDone, + brainfuckCustomEventTextInputDone, +}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +struct BFApp { + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notifications; + SceneManager* scene_manager; + Submenu* submenu; + Popup* popup; + TextInput* text_input; + TextBox* text_box; + FuriString* text_box_store; + FuriString* BF_file_path; + BFDevEnv* BF_dev_env; + int dataSize; + char dataBuffer[BF_INST_BUFFER_SIZE]; + char inputBuffer[BF_INPUT_BUFFER_SIZE]; +}; + +typedef enum { + brainfuckViewMenu, + brainfuckViewPopup, + brainfuckViewLoading, + brainfuckViewTextInput, + brainfuckViewTextBox, + brainfuckViewWidget, + brainfuckViewDev, + brainfuckViewExec, +} brainfuckView; diff --git a/applications/plugins/brainfuck/icons/ButtonRightSmall_3x5.png b/applications/plugins/brainfuck/icons/ButtonRightSmall_3x5.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d5f87db1ca55141449cfcc3bf054417eaa84e1 GIT binary patch literal 1738 zcmcIl%Wm676lEHu%>zLLWYHwZf?zfc+Tjd`l=wgx!?E02K7gZd!)A>b-AnNYDb z=UD-0O?$90FBm_PwI0iHnuo^kzrHc_RD{NpUPPi|OHR_A(^5V@-5v4MBkl`h`li))oId$h zr-Twrdf1}K>IcLLELU%T22?9W66_DYYiq$%XiVz52r!<_X6DQ`RXN6%@B5fgOeq2c zsup?8<|wc3bqoVp@iHyyRONcZ$YOO|hXyEJO(84Rw0YIq1cu=`E3jpfW=b6}iq3{+ z*&1Ed+b2+^)%#K6YP2XM-j|g+F1g%3k$HWuD^^TYt*VLogtqnH|4=CSx?pi!PM7uw zj^$Klz+C~>TIwr;tx~dDl_RC5T~K>nMV(qE)xUm{=0eS?`;DS@fE=(|h6bc&Ap((k zBdZr!eqejwSR^211&yE&1gqKkz)Gaa;ylnO3Wj-Avz*J}AT&UfnWiG(Hm6?C6^L<1 zqL@1bP64XW zqKrwxT^V~c?$~}TQ}}Y&^h4H0l>o-Xk=)_jK{NqDuJ0r$_IQFAaV$sJiQn_7p}()Y zrKYNklmK^aLl-wVr`&ul_BH)&R_4UgD(ZOFr}nOx%X#N!gVeJ4h)=Wyh? zQXrf4V!h1m}&`|!B-3a0&FC= whJ($~<(FI>9{%<2-NwbcKNmOR7sY+;Hox}g7dMW6Yj&IA_U_=9M~Bb;1{rl65&!@I literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/KeyBackspaceSelected_24x11.png b/applications/plugins/brainfuck/icons/KeyBackspaceSelected_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..c79cfb6c61feb86ebd442c0ed12f4b64f54fad54 GIT binary patch literal 1977 zcmcIlPmkL~6n8~;E4ErxK@Syhu)IBh%Gfjh8(T@MY_jY|s+VY+s>`jD@i?(I@fh2? z$u1HH-~bYW8wkWF0I3oeB;bHV#g$9H09Q_Y0mSlbCuxwD?OurFjOY2y@BRC|c^`Lo zx7XKRUXvtgy|dHagY^bHS1&&Y|6jb){Tr4`+1~c1^ys%&|A5WqLDO$a(s!R-{fn-^ zezkkIw{?1Y3a|HndGa1C&nG)?X8`e1@m!L=`WgVQtVF&2eBZz8F)>zYAo^ULjuX(9 zr23s{LfHY&))cu^$qyPzv#==d`(3#uQZCynMhs(Jc3hR%rfK6hWF6~R z!`oEh~hv@veyfbW(Rni=DeXm z^$5Z`!lXw`Psg5FyG5{vai!`59|D@wiz$mB3Xdib?N&T<+v-f1@!a7vA$z+;YK z5sTIkCmbUZm>9XlaFJ~}w(dBVV}zvi3(mI%J1jC$oUiB`2nHC7YxGqH)7?S=$1 z%OIAc$WQII#7LyR2q3%0vb@u3byE>WBlu9<4030yWfRx7k*-2m0rNtUjwui=8q)#S zl6WxZKCmCWfv9()3`%`m_EptnWp~};H1T|2wL^{2d=bF&FuQ?MXg%2dgMO*&W-Em&~-?Dnv0yx+GSj;J1TEH zrrxbiyV<+(ggw08mR6hQ3mY$POJ6>gR^(5mPac1ye(=qeU(K8EUQ@4Km!#jV)gM0m S{KuQcwCrr{wjXUieE%Qfkx0w{ literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/KeyBackspace_24x11.png b/applications/plugins/brainfuck/icons/KeyBackspace_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..00e66428d76e298f0c2c784e1fad66b98d832b8a GIT binary patch literal 1979 zcmcIl&2Jk;6yK<6DpJCQ9tu=wwY`8~yfgdhUD+*;Z5m76V5P1aZW+(c+N;<*+ucp< zM2JIgNJ#XCg!l_Us>B6Fa6sb3iA(+pBqYRvTSa)gYr94YX)ah=&(7O7zxVZf^FHft z-`iM!b6t|8jm}nU2iBYLTwlEc|6jh<{Tr5*bm!it^z7GLzrkj8(C`|P^xYTNpVOB!4C3F%ToBi#Ebo`P!g8W;Uzv%+b1HDT zSWDQ+kOSxi1S-{15o8mZ@WueD^>Tu$mk1=KhZl!&IU@a^VU*ZxNTUIV8yQ-4Ik1|V zUtY^qtEuM~xb}iqb8a@EYjPH(OPMNIut^6?q6G`?oZlW+mG}FW%@NfCjvTXS4^2l^ zN;T|gRa@)x($7)21Y3%hP1V$w`&gb@y1fg;QVOF1c94MiT*`Ng*~TMCUjmcs+q3T< z!qh{ZR-)xFint52(+|8nc(HThcfMEc>?Z{EIf`t9YlHa)U U{I8QgKgs81r@h^Jwz>D@AE;?dx&QzG literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/KeyInputSelected_30x11.png b/applications/plugins/brainfuck/icons/KeyInputSelected_30x11.png new file mode 100644 index 0000000000000000000000000000000000000000..4c04a08566958747a8977da87a884723241f8ad2 GIT binary patch literal 1992 zcmcIl&5zqe6!)s#!fv&vUJ62Bxg4lq>=}Q?R?;e)EZs=!CEBLy!UZPwII%YI7~8wa z?x9GnIB-BcajEzZkcuiq#mAwv7sRDPs?;8P=ERW;((-I4X^@ufUWnw3=lRX=ef{3N zFWQ?|R+e9176f6Xwcgx<^-Xxro<0TtcNWDTVOdDGuB-}oAHVSjY)<#;Ze0-W-9Gy_ zU4;Fa_C{yz@bC~`uRVHl1D2(D{lgR>&gIX7aOdmug7C^>*xAmu-3`a%Lz()#%jEGe z25mv8ULMEP+hLj5WxX(}Nq_$QKoUd0CS5mN?8Xh&57!S8cJ*Mh;~ngHwl7`2w6a_s zJAg1`85PIFL6kb-HQ%3@gI!JPXIbnh%4jr_N1Du&otcVdV2sdNKECa)32y z#%dB&k07ifOgcn&RP5-LcL{bdE>&INgHJPhGG*?E!Tw29UDs(vX+|TDwVE{voRY)P zcRWioh(Rle5rz@*b&PDH*~m03Q?)F^(gITW1?QXG+s!kPpD*d_a}O|P*>p7KRhX}L zQJ`yGWL0z@b#;v*tP|pUR5MLOpQ3$`gwR5CFprvhxRIe% z=L4%M`Q-dS`1M&V8}L# z_E0rcp;X1TTCvnFEBzdVbFigo-c(h2xsT0UlOTWg!myQ`nv{0Aa^SG)iK literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/KeyInput_30x11.png b/applications/plugins/brainfuck/icons/KeyInput_30x11.png new file mode 100644 index 0000000000000000000000000000000000000000..d23e24aaf79f293d10680c29a582ecb0fbecd6ed GIT binary patch literal 1994 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/KeySaveSelected_24x11.png b/applications/plugins/brainfuck/icons/KeySaveSelected_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb3569d3accc5a5c56829b12c85079172b56729 GIT binary patch literal 1853 zcmcIlPiW*+7#}^zw%YZuf+B)3d$6*;fxiXAXK-X$#&ks`?Z1FR>QX z26aVbT~%`&N5w=X1OWo&yGcQZD9KMx7+O3JvM4Pgkw_&Y^~HA4kU?pcLYz)%lYCqz zD405=sj4ZsOlbo2yrZFUJVocl(hfu!>phe>@9d^rUFW&j&H}!)!;|9lBv{%Lg~)s2 z4%()#|Dlit(}3xA)*qFJ1uF0J7`Su5Y9oEA+srsEMAi|aKWWt3B%(w#g-G)oQNqL^ zf1*@0Ucg(l;0+nNrXfra);gN*63r#X84bG_S5Oapz-U2_2No;}caH=0Jhz?X1x*6p zZZ%{Or9=^P?a(oNj2+}NPLO5lb>xS(hK#yzQ(Fto(z;~|u)ZaN?XnW(`pULU1i&$^ zrW-ON3~kFtm|7MJL)}ES1tUU3N-T@l>$)*vdoGLM%c1>)tfeXjj8tQ`U&jXGx~+pC zJw&zve}0HDBg6^Jz?Y@lahswqGEXq5ZvEhVyV+dJL>TqqMZUhgD7BZGrskL?B8nzU zEO0}S#T1Md#k9-SH0hSMuhLzKa_I5y_(QtDUmB14ku-9rOM~*GXvjh71`cJarlUj3 ze7uCJ^@AP<(j#0_!EzB61Df%LF0|x7U8vqkd`@?cmVP{k{EyPdWes{X>2la%Rk=(? zE%&0TDeAxbb=w#db1i`F%Wmf5GAz>Wv>@jW_p)ho-#0CeC9cGbyZ*s9Cn^o)Rq=_$h#NIZix%+wt GFa8a7)lmol literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/KeySave_24x11.png b/applications/plugins/brainfuck/icons/KeySave_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..e7dba987a04dad7dd96001913c55566dbde96c8e GIT binary patch literal 1863 zcmcIlO^6&t6dpxnjjSF75f9pQJZy|LUDdzSZ6$n-*6-2D5s-9_fx~uK( zotfQBVDX?Q(UXLzXF6aX)%U*l z-d9y`w^q+Do_PF3p-@5qOL5h2N9RU z^i-~BIziNECdw*wjUcQeOxncsbnKa>(*%1MPoPck0jC)~9$50g-#!ks+4LGwn$d`f zMy;%ZsA3Rsk2!`#ELuW>2#g3fF>;CFBHMCo-El0(@X1&g%&$qdl~*F4Kd~*B3^?Z1 z^bEmDf}0)Wn??sYC6l9$X;6esLO7#_ZCmDy?ZqU3l|%anS#wn!7%f39-Qp(l9fyJ- z(?=x}n~2%2PcX9#VmYdECvH{tWzv)!s%sn^Z&a(TMEXG=KBQ~smzBm!)h4cOBfSV| zapw6l2`LyY2x(Vnan#Li4>BO#dXPeox2Fr~f_P*4)DM)gJ3Y$sMNw8+?gqit>2PpJ znU9yygm%~yKzf8rCa_fc*^nlp(uJ1%rwg^aiBIX^Xz9mu$p0vPT2|JhQCGkYtEqW1 zTD})enxg%?Uw4c#Ggk#{pLa8zmSLH8=LI=?xR>pc=yYsHAgcQUxz^arx`9fR>e$sw zj@}Uy75!kQXF{tT9e=F+z^*!*3|n>nI6oucWq!(t2og`=40-O!tAE1z^ID@;X)nEd zVK7xqj`wg%Ed2Op{k=a*YZpKHX787$ zzhKuN9(?M5ojmn%xcU1}OP>$^`ZD?cW@lIaTn=x3xBtP#z16o~{qVMZ>hecsD?jQQ ME3387mS5lf8`39Il>h($ literal 0 HcmV?d00001 diff --git a/applications/plugins/brainfuck/icons/bfico.png b/applications/plugins/brainfuck/icons/bfico.png new file mode 100644 index 0000000000000000000000000000000000000000..b25368fb53e03b73878a9aa17a044c2d8dffea4a GIT binary patch literal 1822 zcma)6eM}o=7(YOmKyZGbBNNT#*c_9!cReT{7dirMfhxy3z6O4=Yws<+(e}=}D=ip7*(DuemTUCTeXIf*>)bd_xg@E9&+&;9yN=+jdK75 zNgO}WHlAeI8at9@wST^cjE;Wo&AI%pY})Zgdviz1zO505+S5*dbUq^E9Q0s*?vo}AfY~MGw zu|Mzolpned^&i|}@9r|UW{rFobKix^-<~<~`{a9D*2O#=KX^8w_sE*&4b4qS;g_z_ z%>ItO>znt#TrbxizB$-4;%pp9ch2lSSGO*{^-4$cf!Wn9^V_xE*PDC9E7G@MaPIMA z4|>R~WYsWTmK4E!G2Km^j|*v-P2Ti%9v(J(z9sA0(ByRSCR5*&W!-zmFpiA<@d0Pp79Ia%{^SR_JA^Z1H*V=KHF3l8&g!tuPanB6%y+!KYUx`BAxQK?$`gWgbjBe_ zcqCU+E|=2M$QM1=orh$50Da& zB0#ljt(w4KIErS9tc@x%g0It(iCNdsPPiQFXx3II!iD;`{ux2YWdl}(wAWi=})ahK5Ey>0jm~fzeL6R4UrIK0! z;6*^<-J%s3Yk*5mz)+0+$5K3LV69PsLrH#lrZ> zXr6^!$r!q1hqEFuDs-4sl$V2=jQUhfMrx{xRKr7+N>L^!Qw0iut4g-}4_vZtbMH0)HCkr@LcL%}2g`%FWsNTP($QYj>* z(IycY6aiEBfq9m*)&3XM*WD=mWx^Pmrt&#S=Ed4YbD(F7!HdgG0i%Oc4uV09RY^^U z2_nzBt$_6jfOd(u$sR@o*;VlVbXc~{#=-P!QM1yws79?J)Ts+tj~zgU;G8rm6p_A= z6TYGVUoWj7z;FM50{qyO1OydzxqwVu`Er+6B@ + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) brainfuckScene##id, +typedef enum { +#include "brainfuck_scene_config.h" + brainfuckSceneNum, +} brainfuckScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers brainfuck_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "brainfuck_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "brainfuck_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "brainfuck_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_config.h b/applications/plugins/brainfuck/scenes/brainfuck_scene_config.h new file mode 100644 index 000000000..0efc41641 --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(brainfuck, start, Start) +ADD_SCENE(brainfuck, file_select, FileSelect) +ADD_SCENE(brainfuck, file_create, FileCreate) +ADD_SCENE(brainfuck, dev_env, DevEnv) +ADD_SCENE(brainfuck, exec_env, ExecEnv) +ADD_SCENE(brainfuck, set_input, SetInput) \ No newline at end of file diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_dev.c b/applications/plugins/brainfuck/scenes/brainfuck_scene_dev.c new file mode 100644 index 000000000..475e9e573 --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_dev.c @@ -0,0 +1,16 @@ +#include "../brainfuck_i.h" + +void brainfuck_scene_dev_env_on_enter(void* context) { + BFApp* app = context; + view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewDev); +} + +bool brainfuck_scene_dev_env_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void brainfuck_scene_dev_env_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_exec.c b/applications/plugins/brainfuck/scenes/brainfuck_scene_exec.c new file mode 100644 index 000000000..d344f7271 --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_exec.c @@ -0,0 +1,16 @@ +#include "../brainfuck_i.h" + +void brainfuck_scene_exec_env_on_enter(void* context) { + BFApp* app = context; + view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewTextBox); +} + +bool brainfuck_scene_exec_env_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void brainfuck_scene_exec_env_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_file_create.c b/applications/plugins/brainfuck/scenes/brainfuck_scene_file_create.c new file mode 100644 index 000000000..9f8885977 --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_file_create.c @@ -0,0 +1,50 @@ +#include "../brainfuck_i.h" + +void file_name_text_input_callback(void* context) { + BFApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, brainfuckCustomEventTextInputDone); +} + +char tmpName[64] = {}; +byte empty[1] = {0x00}; +void brainfuck_scene_file_create_on_enter(void* context) { + BFApp* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "New script name"); + text_input_set_result_callback( + text_input, file_name_text_input_callback, app, tmpName, 64, true); + + view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewTextInput); +} + +bool brainfuck_scene_file_create_on_event(void* context, SceneManagerEvent event) { + BFApp* app = context; + UNUSED(app); + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == brainfuckCustomEventTextInputDone) { + furi_string_cat_printf(app->BF_file_path, "/ext/brainfuck/%s.b", tmpName); + + //remove old file + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_remove(storage, furi_string_get_cstr(app->BF_file_path)); + + //save new file + Stream* stream = buffered_file_stream_alloc(storage); + buffered_file_stream_open( + stream, furi_string_get_cstr(app->BF_file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS); + stream_write(stream, (const uint8_t*)empty, 1); + buffered_file_stream_close(stream); + + //scene_manager_next_scene(app->scene_manager, brainfuckSceneFileSelect); + scene_manager_next_scene(app->scene_manager, brainfuckSceneDevEnv); + } + } + return consumed; +} + +void brainfuck_scene_file_create_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_file_select.c b/applications/plugins/brainfuck/scenes/brainfuck_scene_file_select.c new file mode 100644 index 000000000..33c06ee81 --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_file_select.c @@ -0,0 +1,34 @@ +#include "../brainfuck_i.h" + +void brainfuck_scene_file_select_on_enter(void* context) { + BFApp* app = context; + + DialogsApp* dialogs = furi_record_open("dialogs"); + FuriString* path; + path = furi_string_alloc(); + furi_string_set(path, "/ext/brainfuck"); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".b", &I_bfico); + browser_options.base_path = "/ext/brainfuck"; + browser_options.hide_ext = false; + + bool selected = dialog_file_browser_show(dialogs, path, path, &browser_options); + + if(selected) { + furi_string_set(app->BF_file_path, path); + scene_manager_next_scene(app->scene_manager, brainfuckSceneDevEnv); + } else { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, brainfuckSceneStart); + } +} + +bool brainfuck_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void brainfuck_scene_file_select_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_set_input.c b/applications/plugins/brainfuck/scenes/brainfuck_scene_set_input.c new file mode 100644 index 000000000..efb9237cb --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_set_input.c @@ -0,0 +1,35 @@ +#include "../brainfuck_i.h" + +void set_input_text_input_callback(void* context) { + BFApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, brainfuckCustomEventTextInputDone); +} + +void brainfuck_scene_set_input_on_enter(void* context) { + BFApp* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Edit input buffer"); + text_input_set_result_callback( + text_input, set_input_text_input_callback, app, app->inputBuffer, 64, true); + + view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewTextInput); +} + +bool brainfuck_scene_set_input_on_event(void* context, SceneManagerEvent event) { + BFApp* app = context; + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == brainfuckCustomEventTextInputDone) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, brainfuckSceneDevEnv); + } + } + return consumed; +} + +void brainfuck_scene_set_input_on_exit(void* context) { + BFApp* app = context; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, brainfuckSceneDevEnv); +} diff --git a/applications/plugins/brainfuck/scenes/brainfuck_scene_start.c b/applications/plugins/brainfuck/scenes/brainfuck_scene_start.c new file mode 100644 index 000000000..8eaaf751a --- /dev/null +++ b/applications/plugins/brainfuck/scenes/brainfuck_scene_start.c @@ -0,0 +1,55 @@ +#include "../brainfuck_i.h" +enum SubmenuIndex { + SubmenuIndexNew, + SubmenuIndexOpen, + SubmenuIndexAbout, +}; + +void brainfuck_scene_start_submenu_callback(void* context, uint32_t index) { + BFApp* brainfuck = context; + view_dispatcher_send_custom_event(brainfuck->view_dispatcher, index); +} +void brainfuck_scene_start_on_enter(void* context) { + BFApp* brainfuck = context; + + Submenu* submenu = brainfuck->submenu; + submenu_add_item( + submenu, "New", SubmenuIndexNew, brainfuck_scene_start_submenu_callback, brainfuck); + submenu_add_item( + submenu, "Open", SubmenuIndexOpen, brainfuck_scene_start_submenu_callback, brainfuck); + submenu_add_item( + submenu, "About", SubmenuIndexAbout, brainfuck_scene_start_submenu_callback, brainfuck); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(brainfuck->scene_manager, brainfuckSceneStart)); + view_dispatcher_switch_to_view(brainfuck->view_dispatcher, brainfuckViewMenu); +} + +bool brainfuck_scene_start_on_event(void* context, SceneManagerEvent event) { + BFApp* brainfuck = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexNew) { + scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneFileCreate); + consumed = true; + } else if(event.event == SubmenuIndexOpen) { + scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexAbout) { + text_box_set_text( + brainfuck->text_box, + "FlipperBrainfuck\n\nAn F0 brainfuck intepretor\nBy github.com/Nymda"); + scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneExecEnv); + consumed = true; + } + scene_manager_set_scene_state(brainfuck->scene_manager, brainfuckSceneStart, event.event); + } + + return consumed; +} + +void brainfuck_scene_start_on_exit(void* context) { + BFApp* brainfuck = context; + submenu_reset(brainfuck->submenu); +} diff --git a/applications/plugins/brainfuck/views/bf_dev_env.c b/applications/plugins/brainfuck/views/bf_dev_env.c new file mode 100644 index 000000000..c5f194500 --- /dev/null +++ b/applications/plugins/brainfuck/views/bf_dev_env.c @@ -0,0 +1,419 @@ +#include "bf_dev_env.h" +#include + +typedef struct BFDevEnv { + View* view; + DevEnvOkCallback callback; + void* context; + BFApp* appDev; +} BFDevEnv; + +typedef struct { + uint32_t row; + uint32_t col; +} BFDevEnvModel; + +typedef struct { + int up; + int down; + int left; + int right; +} bMapping; + +static bool bf_dev_process_up(BFDevEnv* devEnv); +static bool bf_dev_process_down(BFDevEnv* devEnv); +static bool bf_dev_process_left(BFDevEnv* devEnv); +static bool bf_dev_process_right(BFDevEnv* devEnv); +static bool bf_dev_process_ok(BFDevEnv* devEnv, InputEvent* event); + +BFApp* appDev; +FuriThread* workerThread; + +char bfChars[9] = {'<', '>', '[', ']', '+', '-', '.', ',', 0x00}; + +int selectedButton = 0; +int saveNotifyCountdown = 0; +int execCountdown = 0; + +char dspLine0[25] = {}; +char dspLine1[25] = {}; +char dspLine2[25] = {}; + +static bMapping buttonMappings[12] = { + {8, 8, 7, 1}, //0 + {8, 8, 0, 2}, //1 + {9, 9, 1, 3}, //2 + {9, 9, 2, 4}, //3 + {10, 10, 3, 5}, //4 + {10, 10, 4, 6}, //5 + {11, 11, 5, 7}, //6 + {11, 11, 6, 0}, //7 + + {0, 0, 11, 9}, //8 + {3, 3, 8, 10}, //9 + {5, 5, 9, 11}, //10 + {6, 6, 10, 8} //11 +}; + +#define BT_X 14 +#define BT_Y 14 +static void bf_dev_draw_button(Canvas* canvas, int x, int y, bool selected, const char* lbl) { + UNUSED(lbl); + + if(selected) { + canvas_draw_rbox(canvas, x, y, BT_X, BT_Y, 3); + canvas_invert_color(canvas); + canvas_set_font(canvas, FontBatteryPercent); + canvas_draw_str_aligned( + canvas, x + (BT_X / 2), y + (BT_Y / 2) - 1, AlignCenter, AlignCenter, lbl); + canvas_invert_color(canvas); + } else { + canvas_draw_rbox(canvas, x, y, BT_X, BT_Y, 3); + canvas_invert_color(canvas); + canvas_draw_rbox(canvas, x + 2, y - 1, BT_X - 2, BT_Y - 1, 3); + canvas_invert_color(canvas); + canvas_draw_rframe(canvas, x, y, BT_X, BT_Y, 3); + canvas_set_font(canvas, FontBatteryPercent); + canvas_draw_str_aligned( + canvas, x + (BT_X / 2), y + (BT_Y / 2) - 1, AlignCenter, AlignCenter, lbl); + } +} + +void bf_save_changes() { + //remove old file + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_remove(storage, furi_string_get_cstr(appDev->BF_file_path)); + + //save new file + Stream* stream = buffered_file_stream_alloc(storage); + buffered_file_stream_open( + stream, furi_string_get_cstr(appDev->BF_file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS); + stream_write(stream, (const uint8_t*)appDev->dataBuffer, appDev->dataSize); + buffered_file_stream_close(stream); +} + +static void bf_dev_draw_callback(Canvas* canvas, void* _model) { + UNUSED(_model); + + if(saveNotifyCountdown > 0) { + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "SAVED"); + saveNotifyCountdown--; + return; + } + + bf_dev_draw_button(canvas, 1, 36, (selectedButton == 0), "+"); //T 0 + bf_dev_draw_button(canvas, 17, 36, (selectedButton == 1), "-"); //T 1 + bf_dev_draw_button(canvas, 33, 36, (selectedButton == 2), "<"); //T 2 + bf_dev_draw_button(canvas, 49, 36, (selectedButton == 3), ">"); //T 3 + bf_dev_draw_button(canvas, 65, 36, (selectedButton == 4), "["); //B 0 + bf_dev_draw_button(canvas, 81, 36, (selectedButton == 5), "]"); //B 1 + bf_dev_draw_button(canvas, 97, 36, (selectedButton == 6), "."); //B 2 + bf_dev_draw_button(canvas, 113, 36, (selectedButton == 7), ","); //B 3 + + //backspace, input, run, save + canvas_draw_icon( + canvas, + 1, + 52, + (selectedButton == 8) ? &I_KeyBackspaceSelected_24x11 : &I_KeyBackspace_24x11); + canvas_draw_icon( + canvas, 45, 52, (selectedButton == 9) ? &I_KeyInputSelected_30x11 : &I_KeyInput_30x11); + canvas_draw_icon( + canvas, 77, 52, (selectedButton == 10) ? &I_KeyRunSelected_24x11 : &I_KeyRun_24x11); + canvas_draw_icon( + canvas, 103, 52, (selectedButton == 11) ? &I_KeySaveSelected_24x11 : &I_KeySave_24x11); + + if(saveNotifyCountdown > 0) { + canvas_draw_icon(canvas, 98, 54, &I_ButtonRightSmall_3x5); + saveNotifyCountdown--; + } + + //textbox + //grossly overcomplicated. not fixing it. + canvas_draw_rframe(canvas, 1, 1, 126, 33, 2); + canvas_set_font(canvas, FontBatteryPercent); + + int dbOffset = 0; + if(appDev->dataSize > 72) { + dbOffset = (appDev->dataSize - 72); + } + + memset(dspLine0, 0x00, 25); + memset(dspLine1, 0x00, 25); + memset(dspLine2, 0x00, 25); + + int tpM = 0; + int tp0 = 0; + int tp1 = 0; + int tp2 = 0; + + for(int p = dbOffset; p < appDev->dataSize; p++) { + if(tpM < 24 * 1) { + dspLine0[tp0] = appDev->dataBuffer[p]; + tp0++; + } else if(tpM < 24 * 2) { + dspLine1[tp1] = appDev->dataBuffer[p]; + tp1++; + } else if(tpM < 24 * 3) { + dspLine2[tp2] = appDev->dataBuffer[p]; + tp2++; + } + tpM++; + } + + canvas_draw_str_aligned(canvas, 3, 8, AlignLeft, AlignCenter, dspLine0); + canvas_draw_str_aligned(canvas, 3, 17, AlignLeft, AlignCenter, dspLine1); + canvas_draw_str_aligned(canvas, 3, 26, AlignLeft, AlignCenter, dspLine2); +} + +static bool bf_dev_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BFDevEnv* devEnv = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = bf_dev_process_right(devEnv); + } else if(event->key == InputKeyLeft) { + consumed = bf_dev_process_left(devEnv); + } else if(event->key == InputKeyUp) { + consumed = bf_dev_process_up(devEnv); + } else if(event->key == InputKeyDown) { + consumed = bf_dev_process_down(devEnv); + } + } else if(event->key == InputKeyOk) { + consumed = bf_dev_process_ok(devEnv, event); + } + + return consumed; +} + +static bool bf_dev_process_up(BFDevEnv* devEnv) { + UNUSED(devEnv); + selectedButton = buttonMappings[selectedButton].up; + return true; +} + +static bool bf_dev_process_down(BFDevEnv* devEnv) { + UNUSED(devEnv); + selectedButton = buttonMappings[selectedButton].down; + return true; +} + +static bool bf_dev_process_left(BFDevEnv* devEnv) { + UNUSED(devEnv); + selectedButton = buttonMappings[selectedButton].left; + return true; +} + +static bool bf_dev_process_right(BFDevEnv* devEnv) { + UNUSED(devEnv); + selectedButton = buttonMappings[selectedButton].right; + return true; +} + +static bool bf_dev_process_ok(BFDevEnv* devEnv, InputEvent* event) { + UNUSED(devEnv); + UNUSED(event); + + if(event->type != InputTypePress) { + return false; + } + + switch(selectedButton) { + case 0: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = '+'; + appDev->dataSize++; + } + break; + } + + case 1: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = '-'; + appDev->dataSize++; + } + break; + } + + case 2: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = '<'; + appDev->dataSize++; + } + break; + } + + case 3: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = '>'; + appDev->dataSize++; + } + break; + } + + case 4: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = '['; + appDev->dataSize++; + } + break; + } + + case 5: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = ']'; + appDev->dataSize++; + } + break; + } + + case 6: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = '.'; + appDev->dataSize++; + } + break; + } + + case 7: { + if(appDev->dataSize < BF_INST_BUFFER_SIZE) { + appDev->dataBuffer[appDev->dataSize] = ','; + appDev->dataSize++; + } + break; + } + + case 8: { + if(appDev->dataSize > 0) { + appDev->dataSize--; + appDev->dataBuffer[appDev->dataSize] = (uint32_t)0x00; + } + break; + } + + case 9: { + scene_manager_next_scene(appDev->scene_manager, brainfuckSceneSetInput); + break; + } + + case 10: { + if(getStatus() != 0) { + killThread(); + furi_thread_join(workerThread); + } + + bf_save_changes(); + + initWorker(appDev); + text_box_set_focus(appDev->text_box, TextBoxFocusEnd); + text_box_set_text(appDev->text_box, workerGetOutput()); + + workerThread = furi_thread_alloc_ex("Worker", 2048, (void*)beginWorker, NULL); + furi_thread_start(workerThread); + + scene_manager_next_scene(appDev->scene_manager, brainfuckSceneExecEnv); + break; + } + + case 11: { + bf_save_changes(); + saveNotifyCountdown = 3; + break; + } + } + + bool consumed = false; + return consumed; +} + +static void bf_dev_enter_callback(void* context) { + furi_assert(context); + BFDevEnv* devEnv = context; + + with_view_model( + devEnv->view, + BFDevEnvModel * model, + { + model->col = 0; + model->row = 0; + }, + true); + + appDev = devEnv->appDev; + selectedButton = 0; + + //exit the running thread if required + if(getStatus() != 0) { + killThread(); + furi_thread_join(workerThread); + } + + //clear the bf instruction buffer + memset(appDev->dataBuffer, 0x00, BF_INST_BUFFER_SIZE * sizeof(char)); + + //open the file + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + buffered_file_stream_open( + stream, furi_string_get_cstr(appDev->BF_file_path), FSAM_READ, FSOM_OPEN_EXISTING); + + //read into the buffer + appDev->dataSize = stream_size(stream); + stream_read(stream, (uint8_t*)appDev->dataBuffer, appDev->dataSize); + buffered_file_stream_close(stream); + + //replaces any invalid characters with an underscore. strips out newlines, comments, etc + for(int i = 0; i < appDev->dataSize; i++) { + if(!strchr(bfChars, appDev->dataBuffer[i])) { + appDev->dataBuffer[i] = '_'; + } + } + + //find the end of the file to begin editing + int tptr = 0; + while(appDev->dataBuffer[tptr] != 0x00) { + tptr++; + } + appDev->dataSize = tptr; +} + +BFDevEnv* bf_dev_env_alloc(BFApp* appDev) { + BFDevEnv* devEnv = malloc(sizeof(BFDevEnv)); + + devEnv->view = view_alloc(); + devEnv->appDev = appDev; + view_allocate_model(devEnv->view, ViewModelTypeLocking, sizeof(BFDevEnvModel)); + + with_view_model( + devEnv->view, + BFDevEnvModel * model, + { + model->col = 0; + model->row = 0; + }, + true); + + view_set_context(devEnv->view, devEnv); + view_set_draw_callback(devEnv->view, bf_dev_draw_callback); + view_set_input_callback(devEnv->view, bf_dev_input_callback); + view_set_enter_callback(devEnv->view, bf_dev_enter_callback); + return devEnv; +} + +void bf_dev_env_free(BFDevEnv* devEnv) { + if(getStatus() != 0) { + killThread(); + furi_thread_join(workerThread); + } + + furi_assert(devEnv); + view_free(devEnv->view); + free(devEnv); +} + +View* bf_dev_env_get_view(BFDevEnv* devEnv) { + furi_assert(devEnv); + return devEnv->view; +} diff --git a/applications/plugins/brainfuck/views/bf_dev_env.h b/applications/plugins/brainfuck/views/bf_dev_env.h new file mode 100644 index 000000000..31059544b --- /dev/null +++ b/applications/plugins/brainfuck/views/bf_dev_env.h @@ -0,0 +1,15 @@ +#pragma once +#include "../brainfuck_i.h" +#include + +typedef void (*DevEnvOkCallback)(InputType type, void* context); + +BFDevEnv* bf_dev_env_alloc(BFApp* application); + +void bf_dev_set_file_path(FuriString* path); + +void bf_dev_env_free(BFDevEnv* devEnv); + +View* bf_dev_env_get_view(BFDevEnv* devEnv); + +void bf_dev_env_set_ok(BFDevEnv* devEnv, DevEnvOkCallback callback, void* context); diff --git a/applications/plugins/brainfuck/worker.c b/applications/plugins/brainfuck/worker.c new file mode 100644 index 000000000..1b05ac3fd --- /dev/null +++ b/applications/plugins/brainfuck/worker.c @@ -0,0 +1,276 @@ +#include "worker.h" + +bool killswitch = false; + +int status = 0; //0: idle, 1: running, 2: failure + +char* inst = 0; +int instCount = 0; +int instPtr = 0; +int runOpCount = 0; + +char* wOutput = 0; +int wOutputPtr = 0; + +char* wInput = 0; +int wInputPtr = 0; + +uint8_t* bfStack = 0; +int stackPtr = 0; +int stackSize = BF_STACK_INITIAL_SIZE; +int stackSizeReal = 0; + +BFApp* wrkrApp = 0; + +void killThread() { + killswitch = true; +} + +bool validateInstPtr() { + if(instPtr > instCount || instPtr < 0) { + return false; + } + return true; +} + +bool validateStackPtr() { + if(stackPtr > stackSize || stackPtr < 0) { + return false; + } + return true; +} + +char* workerGetOutput() { + return wOutput; +} + +int getStackSize() { + return stackSizeReal; +} + +int getOpCount() { + return runOpCount; +} + +int getStatus() { + return status; +} + +void initWorker(BFApp* app) { + wrkrApp = app; + + //rebuild output + if(wOutput) { + free(wOutput); + } + wOutput = (char*)malloc(BF_OUTPUT_SIZE); + wOutputPtr = 0; + + //rebuild stack + if(bfStack) { + free(bfStack); + } + bfStack = (uint8_t*)malloc(BF_STACK_INITIAL_SIZE); + memset(bfStack, 0x00, BF_STACK_INITIAL_SIZE); + stackSize = BF_STACK_INITIAL_SIZE; + stackSizeReal = 0; + stackPtr = 0; + + //set instructions + inst = wrkrApp->dataBuffer; + instCount = wrkrApp->dataSize; + instPtr = 0; + runOpCount = 0; + + //set input + wInput = wrkrApp->inputBuffer; + wInputPtr = 0; + + //set status + status = 0; +} + +void rShift() { + runOpCount++; + stackPtr++; + if(!validateStackPtr()) { + status = 2; + return; + } + + while(stackPtr > stackSize) { + stackSize += BF_STACK_STEP_SIZE; + void* tmp = realloc(bfStack, stackSize); + + if(!tmp) { + status = 2; + return; + } + + memset((tmp + stackSize) - BF_STACK_STEP_SIZE, 0x00, BF_STACK_STEP_SIZE); + bfStack = (uint8_t*)tmp; + }; + if(stackPtr > stackSizeReal) { + stackSizeReal = stackPtr; + } +} + +void lShift() { + runOpCount++; + stackPtr--; + if(!validateStackPtr()) { + status = 2; + return; + } +} + +void inc() { + runOpCount++; + if(!validateStackPtr()) { + status = 2; + return; + } + bfStack[stackPtr]++; +} + +void dec() { + runOpCount++; + if(!validateStackPtr()) { + status = 2; + return; + } + bfStack[stackPtr]--; +} + +void print() { + runOpCount++; + wOutput[wOutputPtr] = bfStack[stackPtr]; + wOutputPtr++; + if(wOutputPtr > (BF_OUTPUT_SIZE - 1)) { + wOutputPtr = 0; + } +} + +void input() { + runOpCount++; + + bfStack[stackPtr] = (uint8_t)wInput[wInputPtr]; + if(wInput[wInputPtr] == 0x00 || wInputPtr >= 64) { + wInputPtr = 0; + } else { + wInputPtr++; + } +} + +void loop() { + runOpCount++; + if(bfStack[stackPtr] == 0) { + int loopCount = 1; + while(loopCount > 0) { + instPtr++; + if(!validateInstPtr()) { + status = 2; + return; + } + if(inst[instPtr] == '[') { + loopCount++; + } else if(inst[instPtr] == ']') { + loopCount--; + } + } + } +} + +void endLoop() { + runOpCount++; + if(bfStack[stackPtr] != 0) { + int loopCount = 1; + while(loopCount > 0) { + instPtr--; + if(!validateInstPtr()) { + status = 2; + return; + } + if(inst[instPtr] == ']') { + loopCount++; + } else if(inst[instPtr] == '[') { + loopCount--; + } + } + } +} + +static const NotificationSequence led_on = { + &message_blue_255, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence led_off = { + &message_green_0, + NULL, +}; + +void beginWorker() { + status = 1; + while(inst[instPtr] != 0x00) { + if(runOpCount % 500 == 0) { + text_box_set_text(wrkrApp->text_box, workerGetOutput()); + notification_message(wrkrApp->notifications, &led_on); + } + + if(status == 2) { + status = 0; + break; + } + if(killswitch) { + status = 0; + killswitch = false; + break; + } + switch(inst[instPtr]) { + case '>': + rShift(); + break; + case '<': + lShift(); + break; + + case '+': + inc(); + break; + + case '-': + dec(); + break; + + case '.': + print(); + break; + + case ',': + input(); + break; + + case '[': + loop(); + break; + + case ']': + endLoop(); + break; + + default: + break; + } + instPtr++; + if(!validateInstPtr()) { + status = 0; + break; + } + } + + notification_message(wrkrApp->notifications, &led_off); + text_box_set_text(wrkrApp->text_box, workerGetOutput()); + status = 0; +} \ No newline at end of file diff --git a/applications/plugins/brainfuck/worker.h b/applications/plugins/brainfuck/worker.h new file mode 100644 index 000000000..b12e364c3 --- /dev/null +++ b/applications/plugins/brainfuck/worker.h @@ -0,0 +1,9 @@ +#include "brainfuck_i.h" + +void initWorker(BFApp* application); +char* workerGetOutput(); +int getStackSize(); +int getOpCount(); +int getStatus(); +void beginWorker(); +void killThread(); \ No newline at end of file diff --git a/applications/plugins/geigercounter/application.fam b/applications/plugins/geigercounter/application.fam new file mode 100644 index 000000000..9fddb52b0 --- /dev/null +++ b/applications/plugins/geigercounter/application.fam @@ -0,0 +1,13 @@ +App( + appid="Geiger_Coutner", + name="Geiger Counter", + apptype=FlipperAppType.EXTERNAL, + entry_point="flipper_geiger_app", + cdefines=["APP_GEIGER"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + fap_icon="geiger.png", + fap_category="GPIO", +) diff --git a/applications/plugins/geigercounter/flipper_geiger.c b/applications/plugins/geigercounter/flipper_geiger.c new file mode 100644 index 000000000..709db9a26 --- /dev/null +++ b/applications/plugins/geigercounter/flipper_geiger.c @@ -0,0 +1,227 @@ +// CC0 1.0 Universal (CC0 1.0) +// Public Domain Dedication +// https://github.com/nmrr + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCREEN_SIZE_X 128 +#define SCREEN_SIZE_Y 64 + +// FOR J305 GEIGER TUBE +#define CONVERSION_FACTOR 0.0081 + +typedef enum { + EventTypeInput, + ClockEventTypeTick, + EventGPIO, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} EventApp; + +typedef struct { + uint32_t cps, cpm; + uint32_t line[SCREEN_SIZE_X / 2]; + float coef; + uint8_t data; +} mutexStruct; + +static void draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + + mutexStruct displayStruct; + mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block((ValueMutex*)ctx); + memcpy(&displayStruct, geigerMutex, sizeof(mutexStruct)); + release_mutex((ValueMutex*)ctx, geigerMutex); + + char buffer[32]; + if(displayStruct.data == 0) + snprintf( + buffer, sizeof(buffer), "%ld cps - %ld cpm", displayStruct.cps, displayStruct.cpm); + else if(displayStruct.data == 1) + snprintf( + buffer, + sizeof(buffer), + "%ld cps - %.2f uSv/h", + displayStruct.cps, + ((double)displayStruct.cpm * (double)CONVERSION_FACTOR)); + else + snprintf( + buffer, + sizeof(buffer), + "%ld cps - %.2f mSv/y", + displayStruct.cps, + (((double)displayStruct.cpm * (double)CONVERSION_FACTOR)) * (double)8.76); + + for(int i = 0; i < SCREEN_SIZE_X; i += 2) { + float Y = SCREEN_SIZE_Y - (displayStruct.line[i / 2] * displayStruct.coef); + + canvas_draw_line(canvas, i, Y, i, SCREEN_SIZE_Y); + canvas_draw_line(canvas, i + 1, Y, i + 1, SCREEN_SIZE_Y); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignBottom, buffer); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + EventApp event = {.type = EventTypeInput, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void clock_tick(void* ctx) { + furi_assert(ctx); + + uint32_t randomNumber = furi_hal_random_get(); + randomNumber &= 0xFFF; + if(randomNumber == 0) randomNumber = 1; + + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, randomNumber, 50); + + FuriMessageQueue* queue = ctx; + EventApp event = {.type = ClockEventTypeTick}; + furi_message_queue_put(queue, &event, 0); +} + +static void gpiocallback(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* queue = ctx; + EventApp event = {.type = EventGPIO}; + furi_message_queue_put(queue, &event, 0); +} + +int32_t flipper_geiger_app() { + EventApp event; + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp)); + + furi_hal_gpio_init(&gpio_ext_pa7, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh); + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 5, 50); + + mutexStruct mutexVal; + mutexVal.cps = 0; + mutexVal.cpm = 0; + for(int i = 0; i < SCREEN_SIZE_X / 2; i++) mutexVal.line[i] = 0; + mutexVal.coef = 1; + mutexVal.data = 0; + + uint32_t counter = 0; + + ValueMutex state_mutex; + init_mutex(&state_mutex, &mutexVal, sizeof(mutexVal)); + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, draw_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + furi_hal_gpio_add_int_callback(&gpio_ext_pa7, gpiocallback, event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, 1000); + + // ENABLE 5V pin + furi_hal_power_enable_otg(); + + while(1) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever); + + uint8_t screenRefresh = 0; + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeInput) { + if(event.input.key == InputKeyBack) { + break; + } else if(event.input.key == InputKeyOk && event.input.type == InputTypeShort) { + counter = 0; + mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex); + + geigerMutex->cps = 0; + geigerMutex->cpm = 0; + for(int i = 0; i < SCREEN_SIZE_X / 2; i++) geigerMutex->line[i] = 0; + + screenRefresh = 1; + release_mutex(&state_mutex, geigerMutex); + } else if((event.input.key == InputKeyLeft && + event.input.type == InputTypeShort)) { + mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex); + + if(geigerMutex->data != 0) + geigerMutex->data--; + else + geigerMutex->data = 2; + + screenRefresh = 1; + release_mutex(&state_mutex, geigerMutex); + } else if((event.input.key == InputKeyRight && + event.input.type == InputTypeShort)) { + mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex); + + if(geigerMutex->data != 2) + geigerMutex->data++; + else + geigerMutex->data = 0; + + screenRefresh = 1; + release_mutex(&state_mutex, geigerMutex); + } + } else if(event.type == ClockEventTypeTick) { + mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex); + + for(int i = 0; i < SCREEN_SIZE_X / 2 - 1; i++) + geigerMutex->line[SCREEN_SIZE_X / 2 - 1 - i] = + geigerMutex->line[SCREEN_SIZE_X / 2 - 2 - i]; + + geigerMutex->line[0] = counter; + geigerMutex->cps = counter; + counter = 0; + + geigerMutex->cpm = geigerMutex->line[0]; + uint32_t max = geigerMutex->line[0]; + for(int i = 1; i < SCREEN_SIZE_X / 2; i++) { + if(i < 60) geigerMutex->cpm += geigerMutex->line[i]; + if(geigerMutex->line[i] > max) max = geigerMutex->line[i]; + } + + if(max > 0) + geigerMutex->coef = ((float)(SCREEN_SIZE_Y - 15)) / ((float)max); + else + geigerMutex->coef = 1; + + screenRefresh = 1; + release_mutex(&state_mutex, geigerMutex); + } else if(event.type == EventGPIO) { + counter++; + } + } + + if(screenRefresh == 1) view_port_update(view_port); + } + + furi_hal_power_disable_otg(); + + furi_hal_gpio_disable_int_callback(&gpio_ext_pa7); + furi_hal_gpio_remove_int_callback(&gpio_ext_pa7); + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_timer_free(timer); + furi_record_close(RECORD_GUI); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/geigercounter/geiger.png b/applications/plugins/geigercounter/geiger.png new file mode 100644 index 0000000000000000000000000000000000000000..d41e1915b6821ab19f982f1c99cad821d5a38abe GIT binary patch literal 8048 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 +#include + +#include +#include + +#include +#include + +#include "clock_app.h" + +/* + This is a modified version of the default clock app intended for use overnight + Up / Down control the displays brightness. Down at brightness 0 turns the notification LED on and off. +*/ + +int brightness = 5; +bool led = false; +NotificationApp* notif = 0; + +const NotificationMessage message_red_dim = { + .type = NotificationMessageTypeLedRed, + .data.led.value = 0xFF / 16, +}; + +const NotificationMessage message_red_off = { + .type = NotificationMessageTypeLedRed, + .data.led.value = 0x00, +}; + +static const NotificationSequence led_on = { + &message_red_dim, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence led_off = { + &message_red_off, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence led_reset = { + &message_red_0, + NULL, +}; + +void set_backlight_brightness(float brightness) { + notif->settings.display_brightness = brightness; + notification_message(notif, &sequence_display_backlight_on); +} + +void handle_up() { + if(brightness < 100) { + led = false; + notification_message(notif, &led_off); + brightness += 5; + } + set_backlight_brightness((float)(brightness / 100.f)); +} + +void handle_down() { + if(brightness > 0) { + brightness -= 5; + if(brightness == 0) { //trigger only on the first brightness 5 -> 0 transition + led = true; + notification_message(notif, &led_on); + } + } else if(brightness == 0) { //trigger on every down press afterwards + led = !led; + if(led) { + notification_message(notif, &led_on); + } else { + notification_message(notif, &led_off); + } + } + set_backlight_brightness((float)(brightness / 100.f)); +} + +static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void clock_render_callback(Canvas* const canvas, void* ctx) { + //canvas_clear(canvas); + //canvas_set_color(canvas, ColorBlack); + + //avoids a bug with the brightness being reverted after the backlight-off period + set_backlight_brightness((float)(brightness / 100.f)); + + ClockState* state = ctx; + if(furi_mutex_acquire(state->mutex, 200) != FuriStatusOk) { + //FURI_LOG_D(TAG, "Can't obtain mutex, requeue render"); + PluginEvent event = {.type = EventTypeTick}; + furi_message_queue_put(state->event_queue, &event, 0); + return; + } + + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + char time_string[TIME_LEN]; + char date_string[DATE_LEN]; + char meridian_string[MERIDIAN_LEN]; + char timer_string[20]; + + if(state->time_format == LocaleTimeFormat24h) { + snprintf( + time_string, TIME_LEN, CLOCK_TIME_FORMAT, curr_dt.hour, curr_dt.minute, curr_dt.second); + } else { + bool pm = curr_dt.hour > 12; + bool pm12 = curr_dt.hour >= 12; + snprintf( + time_string, + TIME_LEN, + CLOCK_TIME_FORMAT, + pm ? curr_dt.hour - 12 : curr_dt.hour, + curr_dt.minute, + curr_dt.second); + + snprintf( + meridian_string, + MERIDIAN_LEN, + MERIDIAN_FORMAT, + pm12 ? MERIDIAN_STRING_PM : MERIDIAN_STRING_AM); + } + + if(state->date_format == LocaleDateFormatYMD) { + snprintf( + date_string, DATE_LEN, CLOCK_ISO_DATE_FORMAT, curr_dt.year, curr_dt.month, curr_dt.day); + } else if(state->date_format == LocaleDateFormatMDY) { + snprintf( + date_string, DATE_LEN, CLOCK_RFC_DATE_FORMAT, curr_dt.month, curr_dt.day, curr_dt.year); + } else { + snprintf( + date_string, DATE_LEN, CLOCK_RFC_DATE_FORMAT, curr_dt.day, curr_dt.month, curr_dt.year); + } + + bool timer_running = state->timer_running; + uint32_t timer_start_timestamp = state->timer_start_timestamp; + uint32_t timer_stopped_seconds = state->timer_stopped_seconds; + + furi_mutex_release(state->mutex); + + canvas_set_font(canvas, FontBigNumbers); + + if(timer_start_timestamp != 0) { + int32_t elapsed_secs = timer_running ? (curr_ts - timer_start_timestamp) : + timer_stopped_seconds; + snprintf(timer_string, 20, "%.2ld:%.2ld", elapsed_secs / 60, elapsed_secs % 60); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, time_string); // DRAW TIME + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, timer_string); // DRAW TIMER + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignTop, date_string); // DRAW DATE + elements_button_left(canvas, "Reset"); + } else { + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, time_string); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 65, 17, AlignCenter, AlignCenter, date_string); + + if(state->time_format == LocaleTimeFormat12h) + canvas_draw_str_aligned(canvas, 64, 47, AlignCenter, AlignCenter, meridian_string); + } + if(timer_running) { + elements_button_center(canvas, "Stop"); + } else if(timer_start_timestamp != 0 && !timer_running) { + elements_button_center(canvas, "Start"); + } +} + +static void clock_state_init(ClockState* const state) { + state->time_format = locale_get_time_format(); + + state->date_format = locale_get_date_format(); + + //FURI_LOG_D(TAG, "Time format: %s", state->settings.time_format == H12 ? "12h" : "24h"); + //FURI_LOG_D(TAG, "Date format: %s", state->settings.date_format == Iso ? "ISO 8601" : "RFC 5322"); + //furi_hal_rtc_get_datetime(&state->datetime); +} + +// Runs every 1000ms by default +static void clock_tick(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + PluginEvent event = {.type = EventTypeTick}; + // It's OK to loose this event if system overloaded + furi_message_queue_put(event_queue, &event, 0); +} + +void timer_start_stop(ClockState* plugin_state) { + // START/STOP TIMER + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + if(plugin_state->timer_running) { + // Update stopped seconds + plugin_state->timer_stopped_seconds = curr_ts - plugin_state->timer_start_timestamp; + } else { + if(plugin_state->timer_start_timestamp == 0) { + // Set starting timestamp if this is first time + plugin_state->timer_start_timestamp = curr_ts; + } else { + // Timer was already running, need to slightly readjust so we don't + // count the intervening time + plugin_state->timer_start_timestamp = curr_ts - plugin_state->timer_stopped_seconds; + } + } + plugin_state->timer_running = !plugin_state->timer_running; +} + +void timer_reset_seconds(ClockState* plugin_state) { + if(plugin_state->timer_start_timestamp != 0) { + // Reset seconds + plugin_state->timer_running = false; + plugin_state->timer_start_timestamp = 0; + plugin_state->timer_stopped_seconds = 0; + } +} + +int32_t clock_app(void* p) { + UNUSED(p); + ClockState* plugin_state = malloc(sizeof(ClockState)); + + plugin_state->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + if(plugin_state->event_queue == NULL) { + FURI_LOG_E(TAG, "Cannot create event queue"); + free(plugin_state); + return 255; + } + //FURI_LOG_D(TAG, "Event queue created"); + + plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(plugin_state->mutex == NULL) { + FURI_LOG_E(TAG, "Cannot create mutex"); + furi_message_queue_free(plugin_state->event_queue); + free(plugin_state); + return 255; + } + //FURI_LOG_D(TAG, "Mutex created"); + + clock_state_init(plugin_state); + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, clock_render_callback, plugin_state); + view_port_input_callback_set(view_port, clock_input_callback, plugin_state->event_queue); + + FuriTimer* timer = + furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, plugin_state->event_queue); + + if(timer == NULL) { + FURI_LOG_E(TAG, "Cannot create timer"); + furi_mutex_free(plugin_state->mutex); + furi_message_queue_free(plugin_state->event_queue); + free(plugin_state); + return 255; + } + //FURI_LOG_D(TAG, "Timer created"); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + furi_timer_start(timer, furi_kernel_get_tick_frequency()); + //FURI_LOG_D(TAG, "Timer started"); + + notif = furi_record_open(RECORD_NOTIFICATION); + float tmpBrightness = notif->settings.display_brightness; + + notification_message(notif, &sequence_display_backlight_enforce_on); + notification_message(notif, &led_off); + + // Main loop + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(plugin_state->event_queue, &event, 100); + + if(event_status != FuriStatusOk) continue; + + if(furi_mutex_acquire(plugin_state->mutex, FuriWaitForever) != FuriStatusOk) continue; + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeLong) { + switch(event.input.key) { + case InputKeyLeft: + // Reset seconds + timer_reset_seconds(plugin_state); + break; + case InputKeyOk: + // Toggle timer + timer_start_stop(plugin_state); + break; + case InputKeyBack: + // Exit the plugin + processing = false; + break; + default: + break; + } + } else if(event.input.type == InputTypeShort) { + switch(event.input.key) { + case InputKeyUp: + handle_up(); + break; + case InputKeyDown: + handle_down(); + break; + default: + break; + } + } + } /*else if(event.type == EventTypeTick) { + furi_hal_rtc_get_datetime(&plugin_state->datetime); + }*/ + + view_port_update(view_port); + furi_mutex_release(plugin_state->mutex); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(plugin_state->event_queue); + furi_mutex_free(plugin_state->mutex); + free(plugin_state); + + set_backlight_brightness(tmpBrightness); + notification_message(notif, &sequence_display_backlight_enforce_auto); + notification_message(notif, &led_reset); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/nightstand/clock_app.h b/applications/plugins/nightstand/clock_app.h new file mode 100644 index 000000000..693bdfac0 --- /dev/null +++ b/applications/plugins/nightstand/clock_app.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#define TAG "Clock" + +#define CLOCK_ISO_DATE_FORMAT "%.4d-%.2d-%.2d" +#define CLOCK_RFC_DATE_FORMAT "%.2d-%.2d-%.4d" +#define CLOCK_TIME_FORMAT "%.2d:%.2d:%.2d" + +#define MERIDIAN_FORMAT "%s" +#define MERIDIAN_STRING_AM "AM" +#define MERIDIAN_STRING_PM "PM" + +#define TIME_LEN 12 +#define DATE_LEN 14 +#define MERIDIAN_LEN 3 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + LocaleDateFormat date_format; + LocaleTimeFormat time_format; + FuriHalRtcDateTime datetime; + FuriMutex* mutex; + FuriMessageQueue* event_queue; + uint32_t timer_start_timestamp; + uint32_t timer_stopped_seconds; + bool timer_running; +} ClockState; diff --git a/applications/plugins/pomodoro/application.fam b/applications/plugins/pomodoro/application.fam new file mode 100644 index 000000000..27e73a0ce --- /dev/null +++ b/applications/plugins/pomodoro/application.fam @@ -0,0 +1,12 @@ +App( + appid="Pomodoro2", + name="Pomodoro 2", + apptype=FlipperAppType.EXTERNAL, + entry_point="flipp_pomodoro_app", + requires=["gui", "notification", "dolphin"], + stack_size=1 * 1024, + fap_category="Tools", + fap_icon_assets="images", + fap_icon="flipp_pomodoro_10.png", + fap_icon_assets_symbol="flipp_pomodoro", +) \ No newline at end of file diff --git a/applications/plugins/pomodoro/flipp_pomodoro_10.png b/applications/plugins/pomodoro/flipp_pomodoro_10.png new file mode 100644 index 0000000000000000000000000000000000000000..977d16a584f63307ceb028a48f57713402abc520 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>3HNky4ABT~ zo#4oKK!Jm0_y2m6v$J|4wU)(+%sJ4oYSlc8Nnua6+`q%bGDG3H*bnyJge())*BAQu z?-*^XlzwJ?r0uexhV_H}+Gscene_manager); +}; + +static void flipp_pomodoro_app_tick_event_callback(void* ctx) { + furi_assert(ctx); + FlippPomodoroApp* app = ctx; + + scene_manager_handle_custom_event(app->scene_manager, FlippPomodoroAppCustomEventTimerTick); +}; + +static bool flipp_pomodoro_app_custom_event_callback(void* ctx, uint32_t event) { + furi_assert(ctx); + FlippPomodoroApp* app = ctx; + + switch(event) { + case FlippPomodoroAppCustomEventStageSkip: + flipp_pomodoro__toggle_stage(app->state); + view_dispatcher_send_custom_event( + app->view_dispatcher, FlippPomodoroAppCustomEventStateUpdated); + return CustomEventConsumed; + case FlippPomodoroAppCustomEventStageComplete: + if(flipp_pomodoro__get_stage(app->state) == FlippPomodoroStageFocus) { + // REGISTER a deed on work stage complete to get an acheivement + DOLPHIN_DEED(DolphinDeedPluginGameWin); + }; + + flipp_pomodoro__toggle_stage(app->state); + notification_message( + app->notification_app, + stage_start_notification_sequence_map[flipp_pomodoro__get_stage(app->state)]); + view_dispatcher_send_custom_event( + app->view_dispatcher, FlippPomodoroAppCustomEventStateUpdated); + return CustomEventConsumed; + default: + break; + } + return scene_manager_handle_custom_event(app->scene_manager, event); +}; + +FlippPomodoroApp* flipp_pomodoro_app_alloc() { + FlippPomodoroApp* app = malloc(sizeof(FlippPomodoroApp)); + app->state = flipp_pomodoro__new(); + + app->scene_manager = scene_manager_alloc(&flipp_pomodoro_scene_handlers, app); + app->gui = furi_record_open(RECORD_GUI); + app->notification_app = furi_record_open(RECORD_NOTIFICATION); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, flipp_pomodoro_app_custom_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, flipp_pomodoro_app_tick_event_callback, 1000); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, flipp_pomodoro_app_back_event_callback); + + app->timer_view = flipp_pomodoro_view_timer_alloc(); + + view_dispatcher_add_view( + app->view_dispatcher, + FlippPomodoroAppViewTimer, + flipp_pomodoro_view_timer_get_view(app->timer_view)); + + scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneTimer); + + return app; +}; + +void flipp_pomodoro_app_free(FlippPomodoroApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, FlippPomodoroAppViewTimer); + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + flipp_pomodoro_view_timer_free(app->timer_view); + flipp_pomodoro__destroy(app->state); + free(app); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); +}; + +int32_t flipp_pomodoro_app(void* p) { + UNUSED(p); + FlippPomodoroApp* app = flipp_pomodoro_app_alloc(); + + view_dispatcher_run(app->view_dispatcher); + + flipp_pomodoro_app_free(app); + + return 0; +}; \ No newline at end of file diff --git a/applications/plugins/pomodoro/flipp_pomodoro_app.h b/applications/plugins/pomodoro/flipp_pomodoro_app.h new file mode 100644 index 000000000..54102e1f3 --- /dev/null +++ b/applications/plugins/pomodoro/flipp_pomodoro_app.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "views/flipp_pomodoro_timer_view.h" + +#include "modules/flipp_pomodoro.h" + +typedef enum { + // Reserve first 100 events for button types and indexes, starting from 0 + FlippPomodoroAppCustomEventStageSkip = 100, + FlippPomodoroAppCustomEventStageComplete, // By Expiration + FlippPomodoroAppCustomEventTimerTick, + FlippPomodoroAppCustomEventStateUpdated, +} FlippPomodoroAppCustomEvent; + +typedef struct { + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notification_app; + FlippPomodoroTimerView* timer_view; + FlippPomodoroState* state; +} FlippPomodoroApp; + +typedef enum { + FlippPomodoroAppViewTimer, +} FlippPomodoroAppView; \ No newline at end of file diff --git a/applications/plugins/pomodoro/flipp_pomodoro_app_i.h b/applications/plugins/pomodoro/flipp_pomodoro_app_i.h new file mode 100644 index 000000000..492ef9c2d --- /dev/null +++ b/applications/plugins/pomodoro/flipp_pomodoro_app_i.h @@ -0,0 +1,31 @@ +#pragma once + +#define FURI_DEBUG 1 + +/** + * Index of dependencies for the main app + */ + +// Platform Imports + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// App resource imports + +#include "helpers/time.h" +#include "helpers/notifications.h" +#include "modules/flipp_pomodoro.h" +#include "flipp_pomodoro_app.h" +#include "scenes/flipp_pomodoro_scene.h" +#include "views/flipp_pomodoro_timer_view.h" + +// Auto-compiled icons +#include "flipp_pomodoro_icons.h" diff --git a/applications/plugins/pomodoro/helpers/debug.h b/applications/plugins/pomodoro/helpers/debug.h new file mode 100644 index 000000000..13b8f2998 --- /dev/null +++ b/applications/plugins/pomodoro/helpers/debug.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +#define TAG "FlippPomodoro" \ No newline at end of file diff --git a/applications/plugins/pomodoro/helpers/notifications.c b/applications/plugins/pomodoro/helpers/notifications.c new file mode 100644 index 000000000..388a3f11d --- /dev/null +++ b/applications/plugins/pomodoro/helpers/notifications.c @@ -0,0 +1,49 @@ +#include + +const NotificationSequence work_start_notification = { + &message_display_backlight_on, + + &message_vibro_on, + + &message_note_b5, + &message_delay_250, + + &message_note_d5, + &message_delay_250, + + &message_sound_off, + &message_vibro_off, + + &message_green_255, + &message_delay_1000, + &message_green_0, + &message_delay_250, + &message_green_255, + &message_delay_1000, + + NULL, +}; + +const NotificationSequence rest_start_notification = { + &message_display_backlight_on, + + &message_vibro_on, + + &message_note_d5, + &message_delay_250, + + &message_note_b5, + &message_delay_250, + + &message_sound_off, + &message_vibro_off, + + &message_red_255, + &message_delay_1000, + &message_red_0, + &message_delay_250, + &message_red_255, + &message_delay_1000, + + NULL, +}; \ No newline at end of file diff --git a/applications/plugins/pomodoro/helpers/notifications.h b/applications/plugins/pomodoro/helpers/notifications.h new file mode 100644 index 000000000..c6cd0428f --- /dev/null +++ b/applications/plugins/pomodoro/helpers/notifications.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../modules/flipp_pomodoro.h" +#include + +extern const NotificationSequence work_start_notification; +extern const NotificationSequence rest_start_notification; + +/// @brief Defines a notification sequence that should indicate start of specific pomodoro stage. +const NotificationSequence* stage_start_notification_sequence_map[] = { + [FlippPomodoroStageFocus] = &work_start_notification, + [FlippPomodoroStageRest] = &rest_start_notification, + [FlippPomodoroStageLongBreak] = &rest_start_notification, +}; diff --git a/applications/plugins/pomodoro/helpers/time.c b/applications/plugins/pomodoro/helpers/time.c new file mode 100644 index 000000000..7fb0d13c2 --- /dev/null +++ b/applications/plugins/pomodoro/helpers/time.c @@ -0,0 +1,20 @@ +#include +#include +#include "time.h" + +const int TIME_SECONDS_IN_MINUTE = 60; +const int TIME_MINUTES_IN_HOUR = 60; + +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); +}; + +TimeDifference time_difference_seconds(uint32_t begin, uint32_t end) { + const uint32_t duration_seconds = end - begin; + + uint32_t minutes = (duration_seconds / TIME_MINUTES_IN_HOUR) % TIME_MINUTES_IN_HOUR; + uint32_t seconds = duration_seconds % TIME_SECONDS_IN_MINUTE; + + return ( + TimeDifference){.total_seconds = duration_seconds, .minutes = minutes, .seconds = seconds}; +}; diff --git a/applications/plugins/pomodoro/helpers/time.h b/applications/plugins/pomodoro/helpers/time.h new file mode 100644 index 000000000..7a7d90bf2 --- /dev/null +++ b/applications/plugins/pomodoro/helpers/time.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +extern const int TIME_SECONDS_IN_MINUTE; +extern const int TIME_MINUTES_IN_HOUR; + +/// @brief Container for a time period +typedef struct { + uint8_t seconds; + uint8_t minutes; + uint32_t total_seconds; +} TimeDifference; + +/// @brief Time by the moment of calling +/// @return A timestamp(seconds percision) +uint32_t time_now(); + +/// @brief Calculates difference between two provided timestamps +/// @param begin - start timestamp of the period +/// @param end - end timestamp of the period to measure +/// @return TimeDifference struct +TimeDifference time_difference_seconds(uint32_t begin, uint32_t end); diff --git a/applications/plugins/pomodoro/images/flipp_pomodoro_focus_64/frame_00.png b/applications/plugins/pomodoro/images/flipp_pomodoro_focus_64/frame_00.png new file mode 100644 index 0000000000000000000000000000000000000000..7ead27c4e2d36b8155c088954b50e87cfc2a7e0c GIT binary patch literal 1242 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 +#include +#include "../helpers/time.h" +#include "flipp_pomodoro.h" + +PomodoroStage stages_sequence[] = { + FlippPomodoroStageFocus, + FlippPomodoroStageRest, + + FlippPomodoroStageFocus, + FlippPomodoroStageRest, + + FlippPomodoroStageFocus, + FlippPomodoroStageRest, + + FlippPomodoroStageFocus, + FlippPomodoroStageLongBreak, +}; + +char* current_stage_label[] = { + [FlippPomodoroStageFocus] = "Continue focus for:", + [FlippPomodoroStageRest] = "Keep rest for:", + [FlippPomodoroStageLongBreak] = "Long Break for:", +}; + +char* next_stage_label[] = { + [FlippPomodoroStageFocus] = "Focus", + [FlippPomodoroStageRest] = "Short Break", + [FlippPomodoroStageLongBreak] = "Long Break", +}; + +PomodoroStage flipp_pomodoro__stage_by_index(int index) { + const int one_loop_size = sizeof(stages_sequence); + return stages_sequence[index % one_loop_size]; +} + +void flipp_pomodoro__toggle_stage(FlippPomodoroState* state) { + furi_assert(state); + state->current_stage_index = state->current_stage_index + 1; + state->started_at_timestamp = time_now(); +}; + +PomodoroStage flipp_pomodoro__get_stage(FlippPomodoroState* state) { + furi_assert(state); + return flipp_pomodoro__stage_by_index(state->current_stage_index); +}; + +char* flipp_pomodoro__current_stage_label(FlippPomodoroState* state) { + furi_assert(state); + return current_stage_label[flipp_pomodoro__get_stage(state)]; +}; + +char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state) { + furi_assert(state); + return next_stage_label[flipp_pomodoro__stage_by_index(state->current_stage_index + 1)]; +}; + +void flipp_pomodoro__destroy(FlippPomodoroState* state) { + furi_assert(state); + free(state); +}; + +uint32_t flipp_pomodoro__current_stage_total_duration(FlippPomodoroState* state) { + const int32_t stage_duration_seconds_map[] = { + [FlippPomodoroStageFocus] = 25 * TIME_SECONDS_IN_MINUTE, + [FlippPomodoroStageRest] = 5 * TIME_SECONDS_IN_MINUTE, + [FlippPomodoroStageLongBreak] = 30 * TIME_SECONDS_IN_MINUTE, + }; + + return stage_duration_seconds_map[flipp_pomodoro__get_stage(state)]; +}; + +uint32_t flipp_pomodoro__stage_expires_timestamp(FlippPomodoroState* state) { + return state->started_at_timestamp + flipp_pomodoro__current_stage_total_duration(state); +}; + +TimeDifference flipp_pomodoro__stage_remaining_duration(FlippPomodoroState* state) { + const uint32_t stage_ends_at = flipp_pomodoro__stage_expires_timestamp(state); + return time_difference_seconds(time_now(), stage_ends_at); +}; + +bool flipp_pomodoro__is_stage_expired(FlippPomodoroState* state) { + const uint32_t expired_by = flipp_pomodoro__stage_expires_timestamp(state); + const uint8_t seamless_change_span_seconds = 1; + return (time_now() - seamless_change_span_seconds) >= expired_by; +}; + +FlippPomodoroState* flipp_pomodoro__new() { + FlippPomodoroState* state = malloc(sizeof(FlippPomodoroState)); + const uint32_t now = time_now(); + state->started_at_timestamp = now; + state->current_stage_index = 0; + return state; +}; \ No newline at end of file diff --git a/applications/plugins/pomodoro/modules/flipp_pomodoro.h b/applications/plugins/pomodoro/modules/flipp_pomodoro.h new file mode 100644 index 000000000..251a77469 --- /dev/null +++ b/applications/plugins/pomodoro/modules/flipp_pomodoro.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "../helpers/time.h" + +/// @brief Options of pomodoro stages +typedef enum { + FlippPomodoroStageFocus, + FlippPomodoroStageRest, + FlippPomodoroStageLongBreak, +} PomodoroStage; + +/// @brief State of the pomodoro timer +typedef struct { + PomodoroStage stage; + uint8_t current_stage_index; + uint32_t started_at_timestamp; +} FlippPomodoroState; + +/// @brief Generates initial state +/// @returns A new pre-populated state for pomodoro timer +FlippPomodoroState* flipp_pomodoro__new(); + +/// @brief Extract current stage of pomodoro +/// @param state - pointer to the state of pomorodo +/// @returns Current stage value +PomodoroStage flipp_pomodoro__get_stage(FlippPomodoroState* state); + +/// @brief Destroys state of timer and it's dependencies +void flipp_pomodoro__destroy(FlippPomodoroState* state); + +/// @brief Get remaining stage time. +/// @param state - pointer to the state of pomorodo +/// @returns Time difference to the end of current stage +TimeDifference flipp_pomodoro__stage_remaining_duration(FlippPomodoroState* state); + +/// @brief Label of currently active stage +/// @param state - pointer to the state of pomorodo +/// @returns A string that explains current stage +char* flipp_pomodoro__current_stage_label(FlippPomodoroState* state); + +/// @brief Label of transition to the next stage +/// @param state - pointer to the state of pomorodo. +/// @returns string with the label of the "skipp" button +char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state); + +/// @brief Check if current stage is expired +/// @param state - pointer to the state of pomorodo. +/// @returns expriations status - true means stage is expired +bool flipp_pomodoro__is_stage_expired(FlippPomodoroState* state); + +/// @brief Rotate stage of the timer +/// @param state - pointer to the state of pomorodo. +void flipp_pomodoro__toggle_stage(FlippPomodoroState* state); diff --git a/applications/plugins/pomodoro/scenes/.keep b/applications/plugins/pomodoro/scenes/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/applications/plugins/pomodoro/scenes/config/flipp_pomodoro_scene_config.h b/applications/plugins/pomodoro/scenes/config/flipp_pomodoro_scene_config.h new file mode 100644 index 000000000..f95daeb30 --- /dev/null +++ b/applications/plugins/pomodoro/scenes/config/flipp_pomodoro_scene_config.h @@ -0,0 +1 @@ +ADD_SCENE(flipp_pomodoro, timer, Timer) \ No newline at end of file diff --git a/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.c b/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.c new file mode 100644 index 000000000..5856ac947 --- /dev/null +++ b/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.c @@ -0,0 +1,30 @@ +#include "flipp_pomodoro_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const flipp_pomodoro_scene_on_enter_handlers[])(void*) = { +#include "config/flipp_pomodoro_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const flipp_pomodoro_scene_on_event_handlers[])(void* ctx, SceneManagerEvent event) = { +#include "config/flipp_pomodoro_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const flipp_pomodoro_scene_on_exit_handlers[])(void* ctx) = { +#include "config/flipp_pomodoro_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers flipp_pomodoro_scene_handlers = { + .on_enter_handlers = flipp_pomodoro_scene_on_enter_handlers, + .on_event_handlers = flipp_pomodoro_scene_on_event_handlers, + .on_exit_handlers = flipp_pomodoro_scene_on_exit_handlers, + .scene_num = FlippPomodoroSceneNum, +}; \ No newline at end of file diff --git a/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.h b/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.h new file mode 100644 index 000000000..3b8a9052a --- /dev/null +++ b/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.h @@ -0,0 +1,27 @@ +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) FlippPomodoroScene##id, +typedef enum { +#include "config/flipp_pomodoro_scene_config.h" + FlippPomodoroSceneNum, +} FlippPomodoroScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers flipp_pomodoro_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "config/flipp_pomodoro_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* ctx, SceneManagerEvent event); +#include "config/flipp_pomodoro_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* ctx); +#include "config/flipp_pomodoro_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene_timer.c b/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene_timer.c new file mode 100644 index 000000000..2190dbdb7 --- /dev/null +++ b/applications/plugins/pomodoro/scenes/flipp_pomodoro_scene_timer.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include "../flipp_pomodoro_app.h" +#include "../views/flipp_pomodoro_timer_view.h" + +enum { SceneEventConusmed = true, SceneEventNotConusmed = false }; + +uint8_t ExitSignal = 0; + +void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) { + furi_assert(ctx); + + FlippPomodoroApp* app = ctx; + + flipp_pomodoro_view_timer_set_state( + flipp_pomodoro_view_timer_get_view(app->timer_view), app->state); +}; + +void flipp_pomodoro_scene_timer_on_next_stage(void* ctx) { + furi_assert(ctx); + + FlippPomodoroApp* app = ctx; + + view_dispatcher_send_custom_event(app->view_dispatcher, FlippPomodoroAppCustomEventStageSkip); +}; + +void flipp_pomodoro_scene_timer_on_enter(void* ctx) { + furi_assert(ctx); + + FlippPomodoroApp* app = ctx; + + view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer); + flipp_pomodoro_scene_timer_sync_view_state(app); + flipp_pomodoro_view_timer_set_on_right_cb( + app->timer_view, flipp_pomodoro_scene_timer_on_next_stage, app); +}; + +void flipp_pomodoro_scene_timer_handle_custom_event( + FlippPomodoroApp* app, + FlippPomodoroAppCustomEvent custom_event) { + if(custom_event == FlippPomodoroAppCustomEventTimerTick && + flipp_pomodoro__is_stage_expired(app->state)) { + view_dispatcher_send_custom_event( + app->view_dispatcher, FlippPomodoroAppCustomEventStageComplete); + } + + if(custom_event == FlippPomodoroAppCustomEventStateUpdated) { + flipp_pomodoro_scene_timer_sync_view_state(app); + } +}; + +bool flipp_pomodoro_scene_timer_on_event(void* ctx, SceneManagerEvent event) { + furi_assert(ctx); + FlippPomodoroApp* app = ctx; + + switch(event.type) { + case SceneManagerEventTypeCustom: + flipp_pomodoro_scene_timer_handle_custom_event(app, event.event); + return SceneEventConusmed; + case SceneManagerEventTypeBack: + return ExitSignal; + default: + break; + }; + return SceneEventNotConusmed; +}; + +void flipp_pomodoro_scene_timer_on_exit(void* ctx) { + UNUSED(ctx); +}; \ No newline at end of file diff --git a/applications/plugins/pomodoro/views/.keep b/applications/plugins/pomodoro/views/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.c b/applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.c new file mode 100644 index 000000000..e8e0383b7 --- /dev/null +++ b/applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.c @@ -0,0 +1,195 @@ +#include "flipp_pomodoro_timer_view.h" +#include +#include +#include +#include +#include "../helpers/debug.h" +#include "../flipp_pomodoro_app.h" +#include "../modules/flipp_pomodoro.h" + +// Auto-compiled icons +#include "flipp_pomodoro_icons.h" + +enum { + ViewInputConsumed = true, + ViewInputNotConusmed = false, +}; + +struct FlippPomodoroTimerView { + View* view; + FlippPomodoroTimerViewInputCb right_cb; + void* right_cb_ctx; +}; + +typedef struct { + IconAnimation* icon; + FlippPomodoroState* state; +} FlippPomodoroTimerViewModel; + +static const Icon* stage_background_image[] = { + [FlippPomodoroStageFocus] = &A_flipp_pomodoro_focus_64, + [FlippPomodoroStageRest] = &A_flipp_pomodoro_rest_64, + [FlippPomodoroStageLongBreak] = &A_flipp_pomodoro_rest_64, +}; + +static void + flipp_pomodoro_view_timer_draw_countdown(Canvas* canvas, TimeDifference remaining_time) { + canvas_set_font(canvas, FontBigNumbers); + const uint8_t right_border_margin = 1; + + const uint8_t countdown_box_height = canvas_height(canvas) * 0.4; + const uint8_t countdown_box_width = canvas_width(canvas) * 0.5; + const uint8_t countdown_box_x = + canvas_width(canvas) - countdown_box_width - right_border_margin; + const uint8_t countdown_box_y = 15; + + elements_bold_rounded_frame( + canvas, countdown_box_x, countdown_box_y, countdown_box_width, countdown_box_height); + + FuriString* timer_string = furi_string_alloc(); + furi_string_printf(timer_string, "%02u:%02u", remaining_time.minutes, remaining_time.seconds); + const char* remaining_stage_time_string = furi_string_get_cstr(timer_string); + canvas_draw_str_aligned( + canvas, + countdown_box_x + (countdown_box_width / 2), + countdown_box_y + (countdown_box_height / 2), + AlignCenter, + AlignCenter, + remaining_stage_time_string); + + furi_string_free(timer_string); +}; + +static void draw_str_with_drop_shadow( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* str) { + canvas_set_color(canvas, ColorWhite); + for(int x_off = -2; x_off <= 2; x_off++) { + for(int y_off = -2; y_off <= 2; y_off++) { + canvas_draw_str_aligned(canvas, x + x_off, y + y_off, horizontal, vertical, str); + } + } + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, str); +} + +static void + flipp_pomodoro_view_timer_draw_current_stage_label(Canvas* canvas, FlippPomodoroState* state) { + canvas_set_font(canvas, FontPrimary); + draw_str_with_drop_shadow( + canvas, + canvas_width(canvas), + 0, + AlignRight, + AlignTop, + flipp_pomodoro__current_stage_label(state)); +} + +static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) { + if(!_model) { + return; + }; + + FlippPomodoroTimerViewModel* model = _model; + + canvas_clear(canvas); + if(model->icon) { + canvas_draw_icon_animation(canvas, 0, 0, model->icon); + } + + flipp_pomodoro_view_timer_draw_countdown( + canvas, flipp_pomodoro__stage_remaining_duration(model->state)); + + flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state); + canvas_set_color(canvas, ColorBlack); + + canvas_set_font(canvas, FontSecondary); + elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state)); +}; + +bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) { + furi_assert(ctx); + furi_assert(event); + FlippPomodoroTimerView* timer = ctx; + + const bool should_trigger_right_event_cb = (event->type == InputTypePress) && + (event->key == InputKeyRight) && + (timer->right_cb != NULL); + + if(should_trigger_right_event_cb) { + furi_assert(timer->right_cb); + furi_assert(timer->right_cb_ctx); + timer->right_cb(timer->right_cb_ctx); + return ViewInputConsumed; + }; + + return ViewInputNotConusmed; +}; + +View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) { + furi_assert(timer); + return timer->view; +}; + +void flipp_pomodoro_view_timer_assign_animation(View* view) { + with_view_model( + view, + FlippPomodoroTimerViewModel * model, + { + furi_assert(model->state); + if(model->icon) { + icon_animation_free(model->icon); + } + model->icon = icon_animation_alloc( + stage_background_image[flipp_pomodoro__get_stage(model->state)]); + view_tie_icon_animation(view, model->icon); + icon_animation_start(model->icon); + }, + true); +} + +FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() { + FlippPomodoroTimerView* timer = malloc(sizeof(FlippPomodoroTimerView)); + timer->view = view_alloc(); + + view_allocate_model(timer->view, ViewModelTypeLockFree, sizeof(FlippPomodoroTimerViewModel)); + view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer); + view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback); + view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback); + + return timer; +}; + +void flipp_pomodoro_view_timer_set_on_right_cb( + FlippPomodoroTimerView* timer, + FlippPomodoroTimerViewInputCb right_cb, + void* right_cb_ctx) { + furi_assert(right_cb); + furi_assert(right_cb_ctx); + timer->right_cb = right_cb; + timer->right_cb_ctx = right_cb_ctx; +}; + +void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) { + furi_assert(view); + furi_assert(state); + with_view_model( + view, FlippPomodoroTimerViewModel * model, { model->state = state; }, false); + flipp_pomodoro_view_timer_assign_animation(view); +}; + +void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer) { + furi_assert(timer); + with_view_model( + timer->view, + FlippPomodoroTimerViewModel * model, + { icon_animation_free(model->icon); }, + false); + view_free(timer->view); + + free(timer); +}; \ No newline at end of file diff --git a/applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.h b/applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.h new file mode 100644 index 000000000..929a0eba3 --- /dev/null +++ b/applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../modules/flipp_pomodoro.h" + +typedef struct FlippPomodoroTimerView FlippPomodoroTimerView; + +typedef void (*FlippPomodoroTimerViewInputCb)(void* ctx); + +FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc(); + +View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer); + +void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer); + +void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state); + +void flipp_pomodoro_view_timer_set_on_right_cb( + FlippPomodoroTimerView* timer, + FlippPomodoroTimerViewInputCb right_cb, + void* right_cb_ctx); diff --git a/applications/plugins/scrambler/LICENSE b/applications/plugins/scrambler/LICENSE new file mode 100644 index 000000000..4a6de25cb --- /dev/null +++ b/applications/plugins/scrambler/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 RaZe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/plugins/scrambler/README.md b/applications/plugins/scrambler/README.md new file mode 100644 index 000000000..7e4700bcd --- /dev/null +++ b/applications/plugins/scrambler/README.md @@ -0,0 +1,16 @@ +# Setting up the Rubik's Cube Scrambler + +## Installation +To install the Rubik's Cube Scrambler, simply add the `rubiks_cube_scrambler` folder to your `application_users` folder. + +## Cleaning the code and removing old files +Run `./fbt -c fap_rubiks_cube_scrambler` to clean the code and remove any old binaries or compilation artifacts. + +## Compiling the FAP +To compile the FAP, run `./fbt fap_rubiks_cube_scrambler`. + +## Launching the app +To run the Rubik's Cube Scrambler directly from the Flip.x0, use `./fbt launch_app APPSRC=rubiks_cube_scrambler`. + +# A special thanks to Tanish for their c scrambler example 🙏 +https://github.com/TanishBhongade/RubiksCubeScrambler-C/ diff --git a/applications/plugins/scrambler/application.fam b/applications/plugins/scrambler/application.fam new file mode 100644 index 000000000..8d87a4a62 --- /dev/null +++ b/applications/plugins/scrambler/application.fam @@ -0,0 +1,20 @@ +# COMPILE ISTRUCTIONS: + +# Clean the code and remove old binaries/compilation artefact +# ./fbt -c fap_rubiks_cube_scrambler + +# Compile FAP +# ./fbt fap_rubiks_cube_scrambler + +# Run application directly inside the Flip.x0 +# ./fbt launch_app APPSRC=rubiks_cube_scrambler + +App( + appid="Rubiks_Cube_Scrambler", + name="Rubik's Cube Scrambler", + apptype=FlipperAppType.EXTERNAL, + entry_point="rubiks_cube_scrambler_main", + stack_size=1 * 1024, + fap_category="Misc", + fap_icon="cube.png", +) diff --git a/applications/plugins/scrambler/assets/1.png b/applications/plugins/scrambler/assets/1.png new file mode 100644 index 0000000000000000000000000000000000000000..d2099ea34bc3e6b6bb9718f9c15e6b412f7e94ef GIT binary patch literal 1964 zcmbuAe^AnA9LFDOp6Oj)S?yZg#2qc0%&qIrvJ@<*Y1+(l2}4D*G;3Wc5-1XC&8x*M z($s7qXy#P{YN#_sq+9FE>iCw5ga~f_=m!Xt2q*}SuC~7IcDuIz_XN%nKS&8LdxJ z!8mP1jR2M<0Mvc}wyr~fCD8yBd;gE=RhDK`SD_<>aa|4}yR}J1O486pijc44S_F?Z zw5AOTF=r#UNh_0jtRNu%+T*GLQ-sF1HTMiDdjk!TepY>~gaR?i48O77g8~4;JU~(0 z0^mE)iYZ#kFU$>%r|TtyOlsm#vbrFK9blpiCAW9Aw^yg&%?^T9RmUe8KAtbVo=8$J z>t+Y&+mjEaAIpbth_V2jxS>usbJ#3cm7m4F;@Ce|toym7w|ZajA=YK9Il%l>L*4H% zhTZEb>3V#ZIZGsc88E`H>hlB>i{6}uD6PdR1&}KKt7MWo9R*1aJWMiGw^`Ru*)$9{ zXH_6B#_67s|05{(eVqo^z4-wXO3L8udV1@JU}&zR({PYtAml|6Zj_NL%O;W z%h{;NJ*#hg!O0k;IsAgs3U)nWU0?8O>6db5`3=;98@v_oXZZWT@^1gy9;uIKx|ljD zaa!%BXO<+5YRXoLWlgu-){?5^vTEL;%Z?S+UV)|I9HdiIvq33VE@P}^v*XmlrAE-f4hJkruA z))inc!rb%ka0fSo(67+^lcCpj^AFPm=f#_7uAcBhV5y`gwsPvbZGzcS({L_k^&W-N z#&1%oPHC(u*K+1}XTB}beGz|q;T0xNC!;9?v3{x6=n7*@MS)>2ENl`nhpN@!OKF?G zsi0l!gNQHz$}vS=@C#>v?>bLF?#X{*zklx1`XT=+MZj3zUXwQ|w#1<_8ESLe8V{=D|{G zq5%8-gf>ykJ-ua$hKFLZ?D8iW5y29}T=%dNV#1E<@)H2=K3HP!*&p8x`TyK`HPqai zvJoSsCI-pu3drz9IC<9wcnK&M=Edje6V&rNd#!6AcpK=q03RJo5;$5pl;P^6jFrza z?@?0a)%yZ-gc?`V3+4F!&Q0gQK(no+nWCymI3X3U=25j%*=u3Y$tCW;S#P%#KWn?idpKqd-0`pJKq4K})Yp6QaqNTRKG<3A zsiiP48|f4se8IOhDd}brz9l&;6K8`sO+IA t&>(7LWYlC4adU~LN{0T4ZGxGm4Eb^njH~-D1pu`$c)I$ztaD0e0szja7u)~< literal 0 HcmV?d00001 diff --git a/applications/plugins/scrambler/rubiks_cube_scrambler.c b/applications/plugins/scrambler/rubiks_cube_scrambler.c new file mode 100644 index 000000000..4c845b883 --- /dev/null +++ b/applications/plugins/scrambler/rubiks_cube_scrambler.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include + +#include "scrambler.h" +#include "furi_hal_random.h" + +int scrambleStarted = 0; +char scramble_str[100] = {0}; +char scramble_start[100] = {0}; +char scramble_end[100] = {0}; +int notifications_enabled = 0; + +static void success_vibration() { + furi_hal_vibro_on(false); + furi_hal_vibro_on(true); + furi_delay_ms(50); + furi_hal_vibro_on(false); + return; +} +void split_array(char original[], int size, char first[], char second[]) { + int mid = size / 2; + if(size % 2 != 0) { + mid++; + } + int first_index = 0, second_index = 0; + for(int i = 0; i < size; i++) { + if(i < mid) { + first[first_index++] = original[i]; + } else { + if(i == mid && (original[i] == '2' || original[i] == '\'')) { + continue; + } + second[second_index++] = original[i]; + } + } + first[first_index] = '\0'; + second[second_index] = '\0'; +} + +static void draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 13, "Rubik's Cube Scrambler"); + + if(scrambleStarted) { + genScramble(); + scrambleReplace(); + strcpy(scramble_str, printData()); + if(notifications_enabled) { + success_vibration(); + } + split_array(scramble_str, strlen(scramble_str), scramble_start, scramble_end); + scrambleStarted = 0; + } + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 28, AlignCenter, AlignCenter, scramble_start); + canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, scramble_end); + elements_button_center(canvas, "New"); + + elements_button_left(canvas, notifications_enabled ? "On" : "Off"); +}; + +static void input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t rubiks_cube_scrambler_main(void* p) { + UNUSED(p); + InputEvent event; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + ViewPort* view_port = view_port_alloc(); + + view_port_draw_callback_set(view_port, draw_callback, NULL); + + view_port_input_callback_set(view_port, input_callback, event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + while(true) { + furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk); + + if(event.key == InputKeyOk && event.type == InputTypeShort) { + scrambleStarted = 1; + } + if(event.key == InputKeyLeft && event.type == InputTypeShort) { + if(notifications_enabled) { + notifications_enabled = 0; + } else { + notifications_enabled = 1; + success_vibration(); + } + } + if(event.key == InputKeyBack) { + break; + } + } + + furi_message_queue_free(event_queue); + + gui_remove_view_port(gui, view_port); + + view_port_free(view_port); + furi_record_close(RECORD_GUI); + return 0; +} diff --git a/applications/plugins/scrambler/scrambler.c b/applications/plugins/scrambler/scrambler.c new file mode 100644 index 000000000..ea5291940 --- /dev/null +++ b/applications/plugins/scrambler/scrambler.c @@ -0,0 +1,102 @@ +/* +Authors: Tanish Bhongade and RaZe +*/ + +#include +#include +#include +#include "furi_hal_random.h" +#include +#include +#include "scrambler.h" + +// 6 moves along with direction +char moves[6] = {'R', 'U', 'F', 'B', 'L', 'D'}; +char dir[4] = {' ', '\'', '2'}; +const int SLEN = 20; +#define RESULT_SIZE 100 +// Structure which holds main scramble +struct GetScramble { + char mainScramble[25][3]; +}; +struct GetScramble a; // Its object + +// Function prototypes to avoid bugs +void scrambleReplace(); +void genScramble(); +void valid(); +int getRand(int upr, int lwr); +char* printData(); +void writeToFile(); + +// Main function +/* int main(){ + genScramble ();//Calling genScramble + scrambleReplace();//Calling scrambleReplace + valid();//Calling valid to validate the scramble + printData ();//Printing the final scramble + //writeToFile();//If you want to write to a file, please uncomment this + + return 0; +} */ + +void genScramble() { + // Stage 1 + for(int i = 0; i < SLEN; i++) { + strcpy(a.mainScramble[i], "00"); + } + // This makes array like this 00 00 00....... +} + +void scrambleReplace() { + // Stage 2 + // Actual process begins here + + // Initialize the mainScramble array with all the possible moves + for(int i = 0; i < SLEN; i++) { + a.mainScramble[i][0] = moves[furi_hal_random_get() % 6]; + a.mainScramble[i][1] = dir[furi_hal_random_get() % 3]; + } + + // Perform the Fisher-Yates shuffle + for(int i = 6 - 1; i > 0; i--) { + int j = rand() % (i + 1); + char temp[3]; + strcpy(temp, a.mainScramble[i]); + strcpy(a.mainScramble[i], a.mainScramble[j]); + strcpy(a.mainScramble[j], temp); + } + + // Select the first 10 elements as the scramble, using only the first three elements of the dir array + for(int i = 0; i < SLEN; i++) { + a.mainScramble[i][1] = dir[furi_hal_random_get() % 3]; + } + for(int i = 1; i < SLEN; i++) { + while(a.mainScramble[i][0] == a.mainScramble[i - 2][0] || + a.mainScramble[i][0] == a.mainScramble[i - 1][0]) { + a.mainScramble[i][0] = moves[furi_hal_random_get() % 5]; + } + } +} + +// Let this function be here for now till I find out what is causing the extra space bug in the scrambles +void remove_double_spaces(char* str) { + int i, j; + int len = strlen(str); + for(i = 0, j = 0; i < len; i++, j++) { + if(str[i] == ' ' && str[i + 1] == ' ') { + i++; + } + str[j] = str[i]; + } + str[j] = '\0'; +} +char* printData() { + static char result[RESULT_SIZE]; + int offset = 0; + for(int loop = 0; loop < SLEN; loop++) { + offset += snprintf(result + offset, RESULT_SIZE - offset, "%s ", a.mainScramble[loop]); + } + remove_double_spaces(result); + return result; +} diff --git a/applications/plugins/scrambler/scrambler.h b/applications/plugins/scrambler/scrambler.h new file mode 100644 index 000000000..4b56c565d --- /dev/null +++ b/applications/plugins/scrambler/scrambler.h @@ -0,0 +1,3 @@ +void scrambleReplace(); +void genScramble(); +char* printData();