Added multiple apps

Added Geiger Counter, Nightstand, Scrambler, Pomodoro
This commit is contained in:
VerstreuteSeele
2023-02-08 04:32:23 +01:00
parent 15ec5b4e0d
commit 25ce710b6e
69 changed files with 2936 additions and 0 deletions

View File

@@ -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",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,149 @@
#include "brainfuck_i.h"
/*
Due to the lack of documentation on the flipper i copied the picopass app,
ripped its insides out and used its hollow corpse to build this app inside of.
i dont know how this stuff works and after 6 hours of trying to learn it, i dont care
*/
bool brainfuck_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
BFApp* brainfuck = context;
return scene_manager_handle_custom_event(brainfuck->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;
}

View File

@@ -0,0 +1,3 @@
#pragma once
typedef struct BFApp BFApp;

View File

@@ -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 <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/text_input.h>
#include <gui/modules/widget.h>
#include <gui/modules/text_box.h>
#include <dialogs/dialogs.h>
#include <input/input.h>
#include "scenes/brainfuck_scene.h"
#include "views/bf_dev_env.h"
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <brainfuck_icons.h>
#include <storage/storage.h>
#include <stream/stream.h>
#include <stream/buffered_file_stream.h>
#include <toolbox/stream/file_stream.h>
#include <notification/notification_messages.h>
#include <notification/notification_app.h>
#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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,30 @@
#include "brainfuck_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const brainfuck_on_enter_handlers[])(void*) = {
#include "brainfuck_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 brainfuck_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "brainfuck_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 brainfuck_on_exit_handlers[])(void* context) = {
#include "brainfuck_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers brainfuck_scene_handlers = {
.on_enter_handlers = brainfuck_on_enter_handlers,
.on_event_handlers = brainfuck_on_event_handlers,
.on_exit_handlers = brainfuck_on_exit_handlers,
.scene_num = brainfuckSceneNum,
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// 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

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -0,0 +1,419 @@
#include "bf_dev_env.h"
#include <gui/elements.h>
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;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "../brainfuck_i.h"
#include <gui/view.h>
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);

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
#include "brainfuck_i.h"
void initWorker(BFApp* application);
char* workerGetOutput();
int getStackSize();
int getOpCount();
int getStatus();
void beginWorker();
void killThread();

View File

@@ -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",
)

View File

@@ -0,0 +1,227 @@
// CC0 1.0 Universal (CC0 1.0)
// Public Domain Dedication
// https://github.com/nmrr
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification_messages.h>
#include <furi_hal_random.h>
#include <furi_hal_pwm.h>
#include <furi_hal_power.h>
#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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,13 @@
App(
appid="NightstandClock",
name="Nightstand Clock",
apptype=FlipperAppType.EXTERNAL,
entry_point="clock_app",
requires=["gui"],
icon="A_Clock_14",
stack_size=2 * 1024,
fap_icon="ClockIcon.png",
fap_category="Misc",
order=81,
)

View File

@@ -0,0 +1,338 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <notification/notification_messages.h>
#include <notification/notification_app.h>
#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;
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <input/input.h>
#include <locale/locale.h>
#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;

View File

@@ -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",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

View File

@@ -0,0 +1,101 @@
#include "flipp_pomodoro_app_i.h"
enum {
CustomEventConsumed = true,
CustomEventNotConsumed = false,
};
static bool flipp_pomodoro_app_back_event_callback(void* ctx) {
furi_assert(ctx);
FlippPomodoroApp* app = ctx;
return scene_manager_handle_back_event(app->scene_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;
};

View File

@@ -0,0 +1,32 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#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;

View File

@@ -0,0 +1,31 @@
#pragma once
#define FURI_DEBUG 1
/**
* Index of dependencies for the main app
*/
// Platform Imports
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_stack.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/elements.h>
#include <dolphin/dolphin.h>
#include <input/input.h>
// 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"

View File

@@ -0,0 +1,5 @@
#pragma once
#include <furi.h>
#define TAG "FlippPomodoro"

View File

@@ -0,0 +1,49 @@
#include <notification/notification_messages.h>
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,
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "../modules/flipp_pomodoro.h"
#include <notification/notification_messages.h>
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,
};

View File

@@ -0,0 +1,20 @@
#include <furi.h>
#include <furi_hal.h>
#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};
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,94 @@
#include <furi.h>
#include <furi_hal.h>
#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;
};

View File

@@ -0,0 +1,54 @@
#pragma once
#include <furi_hal.h>
#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);

View File

@@ -0,0 +1 @@
ADD_SCENE(flipp_pomodoro, timer, Timer)

View File

@@ -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,
};

View File

@@ -0,0 +1,27 @@
#include <gui/scene_manager.h>
// 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

View File

@@ -0,0 +1,71 @@
#include <furi.h>
#include <gui/scene_manager.h>
#include <gui/view_dispatcher.h>
#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);
};

View File

@@ -0,0 +1,195 @@
#include "flipp_pomodoro_timer_view.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <gui/view.h>
#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);
};

View File

@@ -0,0 +1,21 @@
#pragma once
#include <gui/view.h>
#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);

View File

@@ -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.

View File

@@ -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/

View File

@@ -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",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

View File

@@ -0,0 +1,115 @@
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <gui/elements.h>
#include <furi_hal.h>
#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;
}

View File

@@ -0,0 +1,102 @@
/*
Authors: Tanish Bhongade and RaZe
*/
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include "furi_hal_random.h"
#include <input/input.h>
#include <gui/elements.h>
#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;
}

View File

@@ -0,0 +1,3 @@
void scrambleReplace();
void genScramble();
char* printData();