Update apps pt1

This commit is contained in:
Willy-JL
2023-03-09 01:15:14 +00:00
parent 9968b2ff0f
commit e4998bf330
91 changed files with 1845 additions and 1073 deletions

View File

@@ -19,6 +19,7 @@ Using it is really simple:
* Up button for Left mouse click;
* Down button for Right mouse click;
* Center button for Middle mouse click;
* Left and Right buttons for scrolling;
* Use calibration menu option if you notice significant drift (place your Flipper onto a level surface, make sure it doesn't move, run this option, wait 2 seconds, done).
See early prototype [in action](https://www.youtube.com/watch?v=DdxAmmsYfMA).
@@ -41,10 +42,9 @@ Reality:
![What it looks like](https://github.com/ginkage/FlippAirMouse/blob/main/schematic/flipper.jpg)
## Software
The code is based on the original Bosch [driver](https://github.com/BoschSensortec/BMI160_driver/) and an orientation tracking implementation from the [Cardboard](https://github.com/googlevr/cardboard/tree/master/sdk/sensors) project
The code is based on the original Bosch [driver](https://github.com/BoschSensortec/BMI160_driver/) and an orientation tracking implementation from the Google [Cardboard](https://github.com/googlevr/cardboard/tree/master/sdk/sensors) project
If you're familiar with Flipper applications, start in the [firmware](https://github.com/flipperdevices/flipperzero-firmware) checkout folder and do the following:
```
@@ -54,3 +54,7 @@ cd ../..
./fbt fap_air_mouse
```
If you're not familiar with those, just grab a `fap` file from Releases.
## License
TL;DR: Use the code however you want, give credit where it's due, no warranty of any kind is provided.

View File

@@ -1,5 +1,5 @@
App(
appid="BMI160_Air_Mouse",
appid="Air_Mouse",
name="[BMI160] Air Mouse",
apptype=FlipperAppType.EXTERNAL,
entry_point="air_mouse_app",

View File

@@ -44,8 +44,7 @@ struct BtMouse {
#define BT_MOUSE_FLAG_KILL_THREAD (1UL << 1)
#define BT_MOUSE_FLAG_ALL (BT_MOUSE_FLAG_INPUT_EVENT | BT_MOUSE_FLAG_KILL_THREAD)
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
#define MOUSE_SCROLL 2
static void bt_mouse_notify_event(BtMouse* bt_mouse) {
FuriThreadId thread_id = furi_thread_get_id(bt_mouse->thread);
@@ -100,6 +99,14 @@ static void bt_mouse_process(BtMouse* bt_mouse, InputEvent* event) {
} else if(event->type == InputTypeRelease) {
bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_WHEEL, false);
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
bt_mouse->wheel = MOUSE_SCROLL;
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
bt_mouse->wheel = -MOUSE_SCROLL;
}
}
},
true);

View File

@@ -21,6 +21,8 @@ static void usb_mouse_draw_callback(Canvas* canvas, void* context) {
canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
}
#define MOUSE_SCROLL 2
static void usb_mouse_process(UsbMouse* usb_mouse, InputEvent* event) {
with_view_model(
usb_mouse->view,
@@ -45,6 +47,14 @@ static void usb_mouse_process(UsbMouse* usb_mouse, InputEvent* event) {
} else if(event->type == InputTypeRelease) {
furi_hal_hid_mouse_release(HID_MOUSE_BTN_WHEEL);
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
furi_hal_hid_mouse_scroll(MOUSE_SCROLL);
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
furi_hal_hid_mouse_scroll(-MOUSE_SCROLL);
}
}
},
true);

View File

@@ -9,6 +9,6 @@ Hit any button other than back repeatedly. Calculates based on the average of th
## Compiling
```
./fbt fap_bpm_tapper
./fbt firmware_bpm_tapper
```

View File

@@ -3,7 +3,6 @@
#include <dialogs/dialogs.h>
#include <gui/gui.h>
#include <input/input.h>
#include <m-string.h>
#include <stdlib.h>
#include "BPM_Tapper_icons.h"
@@ -91,6 +90,7 @@ static float queue_avg(queue* q) {
//}
//
typedef struct {
FuriMutex* mutex;
int taps;
double bpm;
uint32_t last_stamp;
@@ -127,42 +127,43 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
}
static void render_callback(Canvas* const canvas, void* ctx) {
string_t tempStr;
furi_assert(ctx);
const BPMTapper* bpm_state = ctx;
furi_mutex_acquire(bpm_state->mutex, FuriWaitForever);
const BPMTapper* bpm_state = acquire_mutex((ValueMutex*)ctx, 25);
if(bpm_state == NULL) {
return;
}
FuriString* tempStr;
// border
//canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_set_font(canvas, FontPrimary);
string_init(tempStr);
tempStr = furi_string_alloc();
string_printf(tempStr, "Taps: %d", bpm_state->taps);
canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignBottom, string_get_cstr(tempStr));
string_reset(tempStr);
furi_string_printf(tempStr, "Taps: %d", bpm_state->taps);
canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr));
furi_string_reset(tempStr);
string_printf(tempStr, "Queue: %d", bpm_state->tap_queue->size);
canvas_draw_str_aligned(canvas, 70, 10, AlignLeft, AlignBottom, string_get_cstr(tempStr));
string_reset(tempStr);
furi_string_printf(tempStr, "Queue: %d", bpm_state->tap_queue->size);
canvas_draw_str_aligned(canvas, 70, 10, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr));
furi_string_reset(tempStr);
string_printf(tempStr, "Interval: %dms", bpm_state->interval);
canvas_draw_str_aligned(canvas, 5, 20, AlignLeft, AlignBottom, string_get_cstr(tempStr));
string_reset(tempStr);
furi_string_printf(tempStr, "Interval: %ldms", bpm_state->interval);
canvas_draw_str_aligned(canvas, 5, 20, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr));
furi_string_reset(tempStr);
string_printf(tempStr, "x2 %.2f /2 %.2f", bpm_state->bpm * 2, bpm_state->bpm / 2);
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignCenter, string_get_cstr(tempStr));
string_reset(tempStr);
furi_string_printf(tempStr, "x2 %.2f /2 %.2f", bpm_state->bpm * 2, bpm_state->bpm / 2);
canvas_draw_str_aligned(
canvas, 64, 60, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
furi_string_reset(tempStr);
string_printf(tempStr, "%.2f", bpm_state->bpm);
furi_string_printf(tempStr, "%.2f", bpm_state->bpm);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, string_get_cstr(tempStr));
string_reset(tempStr);
canvas_draw_str_aligned(
canvas, 64, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
furi_string_reset(tempStr);
string_clear(tempStr);
furi_string_free(tempStr);
release_mutex((ValueMutex*)ctx, bpm_state);
furi_mutex_release(bpm_state->mutex);
}
static void bpm_state_init(BPMTapper* const plugin_state) {
@@ -185,8 +186,8 @@ int32_t bpm_tapper_app(void* p) {
// setup
bpm_state_init(bpm_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, bpm_state, sizeof(bpm_state))) {
bpm_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!bpm_state->mutex) {
FURI_LOG_E("BPM-Tapper", "cannot create mutex\r\n");
free(bpm_state);
return 255;
@@ -197,7 +198,7 @@ int32_t bpm_tapper_app(void* p) {
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_draw_callback_set(view_port, render_callback, bpm_state);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
@@ -207,7 +208,7 @@ int32_t bpm_tapper_app(void* p) {
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
BPMTapper* bpm_state = (BPMTapper*)acquire_mutex_block(&state_mutex);
furi_mutex_acquire(bpm_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
@@ -240,19 +241,16 @@ int32_t bpm_tapper_app(void* p) {
}
}
}
} else {
FURI_LOG_D("BPM-Tapper", "FuriMessageQueue: event timeout");
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, bpm_state);
furi_mutex_release(bpm_state->mutex);
}
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_mutex_free(bpm_state->mutex);
queue* q = bpm_state->tap_queue;
free(q);
free(bpm_state);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -11,5 +11,4 @@ App(
fap_icon="bfico.png",
fap_category="Misc",
fap_icon_assets="icons",
fap_icon_assets_symbol="brainfuck",
)

View File

@@ -29,7 +29,7 @@ typedef unsigned char byte;
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <brainfuck_icons.h>
#include <Brainfuck_icons.h>
#include <storage/storage.h>
#include <stream/stream.h>

View File

@@ -35,9 +35,9 @@ int selectedButton = 0;
int saveNotifyCountdown = 0;
int execCountdown = 0;
char dspLine0[25] = {};
char dspLine1[25] = {};
char dspLine2[25] = {};
char dspLine0[25] = {0x00};
char dspLine1[25] = {0x00};
char dspLine2[25] = {0x00};
static bMapping buttonMappings[12] = {
{8, 8, 7, 1}, //0
@@ -361,6 +361,10 @@ static void bf_dev_enter_callback(void* context) {
//read into the buffer
appDev->dataSize = stream_size(stream);
if(appDev->dataSize > 2000) {
return; //BF file is too large
}
stream_read(stream, (uint8_t*)appDev->dataBuffer, appDev->dataSize);
buffered_file_stream_close(stream);

View File

@@ -1,4 +1,6 @@
#include "worker.h"
#include <furi_hal_resources.h>
#include <furi.h>
bool killswitch = false;
@@ -207,27 +209,40 @@ static const NotificationSequence led_on = {
};
static const NotificationSequence led_off = {
&message_green_0,
&message_blue_0,
NULL,
};
void input_kill(void* _ctx) {
UNUSED(_ctx);
killswitch = true;
}
void beginWorker() {
status = 1;
//redefined from furi_hal_resources.c
const GpioPin gpio_button_back = {.port = GPIOC, .pin = LL_GPIO_PIN_13};
while(inst[instPtr] != 0x00) {
if(runOpCount % 500 == 0) {
text_box_set_text(wrkrApp->text_box, workerGetOutput());
notification_message(wrkrApp->notifications, &led_on);
}
//status 2 indicates failure
if(status == 2) {
status = 0;
break;
}
if(killswitch) {
//read back button directly to avoid weirdness in furi
if(killswitch || !furi_hal_gpio_read(&gpio_button_back)) {
status = 0;
killswitch = false;
break;
}
switch(inst[instPtr]) {
case '>':
rShift();

View File

@@ -1,7 +1,7 @@
App(
appid="Caesar_Cipher",
name="Caesar Cipher",
apptype=FlipperAppType.EXTERNAL,
apptype=FlipperAppType.PLUGIN,
entry_point="caesar_cipher_app",
cdefines=["APP_CAESAR_CIPHER"],
requires=[

View File

@@ -21,6 +21,7 @@ typedef struct {
} PluginEvent;
typedef struct {
FuriMutex* mutex;
ViewDispatcher* view_dispatcher;
TextInput* text_input;
TextBox* text_box;
@@ -58,7 +59,10 @@ static void build_output(char* input, char* output) {
}
static void text_input_callback(void* ctx) {
CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25);
furi_assert(ctx);
CaesarState* caesar_state = ctx;
furi_mutex_acquire(caesar_state->mutex, FuriWaitForever);
FURI_LOG_D("caesar_cipher", "Input text: %s", caesar_state->input);
// this is where we build the output.
string_to_uppercase(caesar_state->input);
@@ -67,13 +71,14 @@ static void text_input_callback(void* ctx) {
text_box_set_text(caesar_state->text_box, caesar_state->output);
view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 1);
release_mutex((ValueMutex*)ctx, caesar_state);
furi_mutex_release(caesar_state->mutex);
}
static bool back_event_callback(void* ctx) {
const CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25);
const CaesarState* caesar_state = ctx;
furi_mutex_acquire(caesar_state->mutex, FuriWaitForever);
view_dispatcher_stop(caesar_state->view_dispatcher);
release_mutex((ValueMutex*)ctx, caesar_state);
furi_mutex_release(caesar_state->mutex);
return true;
}
@@ -99,8 +104,8 @@ int32_t caesar_cipher_app() {
FURI_LOG_D("caesar_cipher", "Running caesar_cipher_state_init");
caesar_cipher_state_init(caesar_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, caesar_state, sizeof(CaesarState))) {
caesar_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!caesar_state->mutex) {
FURI_LOG_E("caesar_cipher", "cannot create mutex\r\n");
free(caesar_state);
return 255;
@@ -110,7 +115,7 @@ int32_t caesar_cipher_app() {
text_input_set_result_callback(
caesar_state->text_input,
text_input_callback,
&state_mutex,
caesar_state,
caesar_state->input,
TEXT_BUFFER_SIZE,
//clear default text
@@ -135,12 +140,12 @@ int32_t caesar_cipher_app() {
FURI_LOG_D("ceasar_cipher", "starting view dispatcher");
view_dispatcher_set_navigation_event_callback(
caesar_state->view_dispatcher, back_event_callback);
view_dispatcher_set_event_callback_context(caesar_state->view_dispatcher, &state_mutex);
view_dispatcher_set_event_callback_context(caesar_state->view_dispatcher, caesar_state);
view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 0);
view_dispatcher_run(caesar_state->view_dispatcher);
furi_record_close("gui");
delete_mutex(&state_mutex);
furi_mutex_free(caesar_state->mutex);
caesar_cipher_state_free(caesar_state);
return 0;

View File

@@ -1,7 +1,7 @@
App(
appid="Counter",
appid="counter",
name="Counter",
apptype=FlipperAppType.EXTERNAL,
apptype=FlipperAppType.PLUGIN,
entry_point="counterapp",
requires=[
"gui",

View File

@@ -2,7 +2,7 @@
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <Counter_icons.h>
#include <counter_icons.h>
#define MAX_COUNT 99
#define BOXTIME 2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 MiB

View File

@@ -10,4 +10,4 @@ App(
fap_icon="i2ctools.png",
fap_category="GPIO",
fap_icon_assets="images",
)
)

View File

@@ -21,6 +21,7 @@ typedef struct {
typedef struct {
bool revive;
int evo;
FuriMutex* mutex;
} State;
unsigned char new[TOTAL_PIXELS] = {};
@@ -92,7 +93,10 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
}
static void render_callback(Canvas* canvas, void* ctx) {
State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25);
//furi_assert(ctx);
State* state = ctx;
furi_mutex_acquire(state->mutex, FuriWaitForever);
canvas_clear(canvas);
for(int i = 0; i < TOTAL_PIXELS; ++i) {
@@ -100,7 +104,7 @@ static void render_callback(Canvas* canvas, void* ctx) {
int y = (int)(i / SCREEN_WIDTH);
if(fields[current][i] == 1) canvas_draw_dot(canvas, x, y);
}
release_mutex((ValueMutex*)ctx, state);
furi_mutex_release(state->mutex);
}
int32_t game_of_life_app(void* p) {
@@ -112,8 +116,8 @@ int32_t game_of_life_app(void* p) {
State* _state = malloc(sizeof(State));
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, _state, sizeof(State))) {
_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!_state->mutex) {
printf("cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(_state);
@@ -121,7 +125,7 @@ int32_t game_of_life_app(void* p) {
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_draw_callback_set(view_port, render_callback, _state);
view_port_input_callback_set(view_port, input_callback, event_queue);
Gui* gui = furi_record_open(RECORD_GUI);
@@ -129,23 +133,23 @@ int32_t game_of_life_app(void* p) {
AppEvent event;
for(bool processing = true; processing;) {
State* state = (State*)acquire_mutex_block(&state_mutex);
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25);
furi_mutex_acquire(_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk && event.type == EventTypeKey &&
event.input.type == InputTypePress) {
if(event.input.key == InputKeyBack) {
// furiac_exit(NULL);
processing = false;
release_mutex(&state_mutex, state);
furi_mutex_release(_state->mutex);
break;
}
}
update_field(state);
update_field(_state);
view_port_update(view_port);
release_mutex(&state_mutex, state);
furi_mutex_release(_state->mutex);
}
view_port_enabled_set(view_port, false);
@@ -153,7 +157,7 @@ int32_t game_of_life_app(void* p) {
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_mutex_free(_state->mutex);
free(_state);
return 0;

View File

@@ -11,4 +11,4 @@ App(
order=20,
fap_icon="dist_sensor10px.png",
fap_category="GPIO",
)
)

View File

@@ -15,6 +15,7 @@ typedef struct {
} PluginEvent;
typedef struct {
FuriMutex* mutex;
float xZoom;
float yZoom;
float xOffset;
@@ -52,10 +53,9 @@ bool mandelbrot_pixel(int x, int y, float xZoom, float yZoom, float xOffset, flo
}
static void render_callback(Canvas* const canvas, void* ctx) {
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
furi_assert(ctx);
const PluginState* plugin_state = ctx;
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
// border around the edge of the screen
canvas_draw_frame(canvas, 0, 0, 128, 64);
@@ -75,7 +75,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
}
}
release_mutex((ValueMutex*)ctx, plugin_state);
furi_mutex_release(plugin_state->mutex);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
@@ -100,8 +100,8 @@ int32_t mandelbrot_app(void* p) {
PluginState* plugin_state = malloc(sizeof(PluginState));
mandelbrot_state_init(plugin_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!plugin_state->mutex) {
FURI_LOG_E("mandelbrot", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(plugin_state);
@@ -110,7 +110,7 @@ int32_t mandelbrot_app(void* p) {
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_draw_callback_set(view_port, render_callback, plugin_state);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
@@ -120,7 +120,7 @@ int32_t mandelbrot_app(void* p) {
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
@@ -154,12 +154,9 @@ int32_t mandelbrot_app(void* p) {
}
}
}
} else {
FURI_LOG_D("mandelbrot", "osMessageQueue: event timeout");
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
furi_mutex_release(plugin_state->mutex);
}
view_port_enabled_set(view_port, false);
@@ -167,6 +164,8 @@ int32_t mandelbrot_app(void* p) {
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_mutex_free(plugin_state->mutex);
free(plugin_state);
return 0;
}

View File

@@ -11,9 +11,9 @@ App(
provides=["music_beeper_start"],
stack_size=2 * 1024,
order=45,
fap_icon="icons/music_10px.png",
fap_category="Music",
fap_icon="music_10px.png",
fap_icon_assets="icons",
fap_category="Music",
)
App(

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

View File

@@ -101,8 +101,11 @@ MusicBeeperWorker* music_beeper_worker_alloc() {
NoteBlockArray_init(instance->notes);
instance->thread = furi_thread_alloc_ex(
"MusicBeeperWorker", 1024, music_beeper_worker_thread_callback, instance);
instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "MusicBeeperWorker");
furi_thread_set_stack_size(instance->thread, 1024);
furi_thread_set_context(instance->thread, instance);
furi_thread_set_callback(instance->thread, music_beeper_worker_thread_callback);
instance->volume = 1.0f;

View File

@@ -1,7 +1,7 @@
App(
appid="Zero_Tracker",
appid="zero_tracker",
name="Zero Tracker",
apptype=FlipperAppType.EXTERNAL,
apptype=FlipperAppType.PLUGIN,
entry_point="zero_tracker_app",
requires=[
"gui",

View File

@@ -40,7 +40,7 @@ void tracker_speaker_stop() {
}
void tracker_speaker_init() {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
furi_hal_speaker_start(200.0f, 0.01f);
tracker_speaker_stop();
}

View File

@@ -59,6 +59,21 @@ OK - вход в режим просмотра адресов и включен
<img src="https://raw.githubusercontent.com/vad7/nrf24scan/master/Screenshot-5.png">
<br>
<br>
Схема подключения модуля nRF24l01:<br>
Big board:<br>
<img src="https://raw.githubusercontent.com/vad7/nrf24scan/master/scheme.png">
Gerber for full board (https://raw.githubusercontent.com/vad7/nrf24scan/master/Gerber_PCB_Flipper%20Zero%20nRF24%20board_v1_0.zip)<br>
Easyeda source (https://oshwlab.com/vad7/flipper-zero-nrf24-board).
<br><br>
Mini board:<br>
<img src="https://raw.githubusercontent.com/vad7/nRF24-Batch/main/Pics/NRF24_mini_3D.png">
<br>
<img src="https://raw.githubusercontent.com/vad7/nrf24scan/master/scheme2.png">
<br>
Gerber for mini board with 5V - 3.3V converter LM1117-3.3 (https://raw.githubusercontent.com/vad7/nrf24scan/master/Gerber_PCB_Flipper%20Zero%20nRF24%20board%20mini_v1_0.zip)<br>
Easyeda source (https://oshwlab.com/vad7/flipper-zero-nrf24-board_copy).
<br>
<br>
_________________________________________________________________________________
<br>
<br>

View File

@@ -14,7 +14,7 @@
#include <u8g2.h>
#define TAG "nrf24scan"
#define VERSION "2.1"
#define VERSION "2.2"
#define MAX_CHANNEL 125
#define MAX_ADDR 6
@@ -71,6 +71,7 @@ uint8_t NRF_Payload = 32; // Payload len in bytes or Minimum payload in sniff mo
uint8_t NRF_Payload_sniff_min = 0;
uint8_t NRF_AA_OFF = 0; // Disable Auto Acknowledgement
bool NRF_ERROR = 0;
bool NRF_BOARD_POWER_5V = false;
struct ADDRS {
uint8_t addr_P0[5]; // MSB first
@@ -485,7 +486,13 @@ void check_add_addr(uint8_t* pkt) {
static void prepare_nrf24(bool fsend_packet) {
nrf24_write_reg(nrf24_HANDLE, REG_STATUS, 0x70); // clear interrupts
nrf24_write_reg(nrf24_HANDLE, REG_RF_SETUP, NRF_rate);
nrf24_write_reg(
nrf24_HANDLE,
REG_RF_SETUP,
(NRF_rate == 0 ? 0b00100000 :
NRF_rate == 1 ? 0 :
0b00001000) |
0b111); // +TX high power
uint8_t erx_addr = (1 << 0); // Enable RX_P0
struct ADDRS* adr = what_to_do == 1 ? &addrs_sniff : &addrs;
if(!fsend_packet) {
@@ -517,14 +524,13 @@ static void prepare_nrf24(bool fsend_packet) {
}
if(what_to_do == 1) { // SNIFF
payload = 32;
nrf24_write_reg(nrf24_HANDLE, REG_CONFIG, 0x70); // Mask all interrupts, NO CRC
nrf24_write_reg(nrf24_HANDLE, REG_CONFIG, 0x70); // Mask all interrupts
nrf24_write_reg(nrf24_HANDLE, REG_SETUP_RETR, 0); // Automatic Retransmission
nrf24_write_reg(nrf24_HANDLE, REG_EN_AA, 0); // Auto acknowledgement
nrf24_write_reg(
nrf24_HANDLE,
REG_FEATURE,
0); // Enables the W_TX_PAYLOAD_NOACK command, Disable Payload with ACK, set Dynamic Payload
nrf24_write_reg(nrf24_HANDLE, REG_RF_CH, NRF_channel);
} else if(setup_from_log) { // Scan
nrf24_write_reg(
nrf24_HANDLE,
@@ -947,8 +953,10 @@ bool nrf24_send_packet() {
}
static void render_callback(Canvas* const canvas, void* ctx) {
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) return;
furi_assert(ctx);
const PluginState* plugin_state = ctx;
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
//canvas_draw_frame(canvas, 0, 0, 128, 64); // border around the edge of the screen
if(what_doing == 0) {
canvas_set_font(canvas, FontSecondary); // 8x10 font, 6 lines
@@ -1317,7 +1325,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_str(canvas, 0, 64, screen_buf);
}
}
release_mutex((ValueMutex*)ctx, plugin_state);
furi_mutex_release(plugin_state->mutex);
}
int32_t nrf24scan_app(void* p) {
@@ -1325,8 +1333,8 @@ int32_t nrf24scan_app(void* p) {
APP = malloc(sizeof(Nrf24Scan));
APP->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
APP->plugin_state = malloc(sizeof(PluginState));
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, APP->plugin_state, sizeof(PluginState))) {
APP->plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!APP->plugin_state->mutex) {
furi_message_queue_free(APP->event_queue);
FURI_LOG_E(TAG, "cannot create mutex");
free(APP->plugin_state);
@@ -1346,11 +1354,16 @@ int32_t nrf24scan_app(void* p) {
memset((uint8_t*)&addrs, 0, sizeof(addrs));
memset((uint8_t*)&addrs_sniff, 0, sizeof(addrs_sniff));
if(!furi_hal_power_is_otg_enabled()) {
furi_hal_power_enable_otg();
NRF_BOARD_POWER_5V = true;
furi_delay_ms(100);
}
nrf24_init();
// Set system callbacks
APP->view_port = view_port_alloc();
view_port_draw_callback_set(APP->view_port, render_callback, &state_mutex);
view_port_draw_callback_set(APP->view_port, render_callback, APP->plugin_state);
view_port_input_callback_set(APP->view_port, input_callback, APP->event_queue);
// Open GUI and register view_port
@@ -1391,7 +1404,7 @@ int32_t nrf24scan_app(void* p) {
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(APP->event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
furi_mutex_acquire(APP->plugin_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
@@ -1618,13 +1631,14 @@ int32_t nrf24scan_app(void* p) {
}
view_port_update(APP->view_port);
release_mutex(&state_mutex, plugin_state);
furi_mutex_release(APP->plugin_state->mutex);
}
nrf24_set_idle(nrf24_HANDLE);
if(log_arr_idx && (log_to_file == 1 || log_to_file == 2)) {
write_to_log_file(APP->storage, false);
}
nrf24_deinit();
if(NRF_BOARD_POWER_5V) furi_hal_power_disable_otg();
view_port_enabled_set(APP->view_port, false);
gui_remove_view_port(APP->gui, APP->view_port);
@@ -1632,6 +1646,7 @@ int32_t nrf24scan_app(void* p) {
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_STORAGE);
view_port_free(APP->view_port);
furi_mutex_free(APP->plugin_state->mutex);
furi_message_queue_free(APP->event_queue);
free(APP->plugin_state);
if(APP->log_arr) free(APP->log_arr);

View File

@@ -19,8 +19,7 @@ typedef struct {
} PluginEvent;
typedef struct {
int x;
int y;
FuriMutex* mutex;
} PluginState;
struct FOUND {

View File

@@ -88,27 +88,27 @@ int32_t ocarina_app(void* p) {
if(event.type == InputTypePress) {
switch(event.key) {
case InputKeyUp:
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
furi_hal_speaker_start(NOTE_UP, volume);
}
break;
case InputKeyDown:
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
furi_hal_speaker_start(NOTE_DOWN, volume);
}
break;
case InputKeyLeft:
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
furi_hal_speaker_start(NOTE_LEFT, volume);
}
break;
case InputKeyRight:
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
furi_hal_speaker_start(NOTE_RIGHT, volume);
}
break;
case InputKeyOk:
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
furi_hal_speaker_start(NOTE_OK, volume);
}
break;

View File

@@ -12,14 +12,17 @@ typedef struct selected_position {
} selected_position;
typedef struct {
FuriMutex* mutex;
selected_position selected;
bool board[32][16];
bool isDrawing;
} PaintData;
void paint_draw_callback(Canvas* canvas, void* ctx) {
const PaintData* paint_state = acquire_mutex((ValueMutex*)ctx, 25);
UNUSED(ctx);
furi_assert(ctx);
const PaintData* paint_state = ctx;
furi_mutex_acquire(paint_state->mutex, FuriWaitForever);
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
//draw the canvas(32x16) on screen(144x64) using 4x4 tiles
@@ -39,7 +42,7 @@ void paint_draw_callback(Canvas* canvas, void* ctx) {
canvas, paint_state->selected.x * 4 + 1, paint_state->selected.y * 4 + 1, 2, 2);
//release the mutex
release_mutex((ValueMutex*)ctx, paint_state);
furi_mutex_release(paint_state->mutex);
}
void paint_input_callback(InputEvent* input_event, void* ctx) {
@@ -53,8 +56,9 @@ int32_t paint_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
PaintData* paint_state = malloc(sizeof(PaintData));
ValueMutex paint_state_mutex;
if(!init_mutex(&paint_state_mutex, paint_state, sizeof(PaintData))) {
paint_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!paint_state->mutex) {
FURI_LOG_E("paint", "cannot create mutex\r\n");
free(paint_state);
return -1;
@@ -62,7 +66,7 @@ int32_t paint_app(void* p) {
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, paint_draw_callback, &paint_state_mutex);
view_port_draw_callback_set(view_port, paint_draw_callback, paint_state);
view_port_input_callback_set(view_port, paint_input_callback, event_queue);
// Register view port in GUI
@@ -140,9 +144,10 @@ int32_t paint_app(void* p) {
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
free(paint_state);
furi_mutex_free(paint_state->mutex);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_GUI);
free(paint_state);
return 0;
}

View File

@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@@ -1,12 +1,15 @@
App(
appid="Pomodoro",
name="Pomodoro",
appid="Pomodoro_Timer",
name="Pomodoro Timer",
apptype=FlipperAppType.EXTERNAL,
entry_point="flipp_pomodoro_app",
requires=["gui", "notification", "dolphin"],
entry_point="pomodoro_app",
stack_size=1 * 1024,
cdefines=["APP_POMODORO"],
requires=[
"gui",
],
order=10,
fap_icon="pomodoro_timer.png",
fap_category="Tools",
fap_icon_assets="images",
fap_icon="flipp_pomodoro_10.png",
fap_icon_assets_symbol="flipp_pomodoro",
fap_icon_assets="icons",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

View File

@@ -1,96 +0,0 @@
#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:
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

@@ -1,32 +0,0 @@
#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

@@ -1,31 +0,0 @@
#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

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

View File

@@ -1,49 +0,0 @@
#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

@@ -1,14 +0,0 @@
#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

@@ -1,20 +0,0 @@
#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

@@ -1,24 +0,0 @@
#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.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,94 +0,0 @@
#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

@@ -1,54 +0,0 @@
#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,164 @@
#include "pomodoro.h"
#include <notification/notification_messages.h>
#define TAG "PomodoroApp"
enum PomodoroDebugSubmenuIndex {
PomodoroSubmenuIndex10,
PomodoroSubmenuIndex25,
PomodoroSubmenuIndex50,
};
void pomodoro_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
Pomodoro* app = context;
if(index == PomodoroSubmenuIndex10) {
app->view_id = PomodoroView10;
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView10);
}
if(index == PomodoroSubmenuIndex25) {
app->view_id = PomodoroView25;
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView25);
}
if(index == PomodoroSubmenuIndex50) {
app->view_id = PomodoroView50;
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView50);
}
}
void pomodoro_dialog_callback(DialogExResult result, void* context) {
furi_assert(context);
Pomodoro* app = context;
if(result == DialogExResultLeft) {
view_dispatcher_stop(app->view_dispatcher);
} else if(result == DialogExResultRight) {
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
} else if(result == DialogExResultCenter) {
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroViewSubmenu);
}
}
uint32_t pomodoro_exit_confirm_view(void* context) {
UNUSED(context);
return PomodoroViewExitConfirm;
}
uint32_t pomodoro_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
}
Pomodoro* pomodoro_app_alloc() {
Pomodoro* app = malloc(sizeof(Pomodoro));
// Gui
app->gui = furi_record_open(RECORD_GUI);
// Notifications
app->notifications = furi_record_open(RECORD_NOTIFICATION);
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Submenu view
app->submenu = submenu_alloc();
submenu_add_item(
app->submenu,
"Classic: 25 work 5 rest",
PomodoroSubmenuIndex25,
pomodoro_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Long: 50 work 10 rest",
PomodoroSubmenuIndex50,
pomodoro_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Sprint: 10 work 2 rest",
PomodoroSubmenuIndex10,
pomodoro_submenu_callback,
app);
view_set_previous_callback(submenu_get_view(app->submenu), pomodoro_exit);
view_dispatcher_add_view(
app->view_dispatcher, PomodoroViewSubmenu, submenu_get_view(app->submenu));
// Dialog view
app->dialog = dialog_ex_alloc();
dialog_ex_set_result_callback(app->dialog, pomodoro_dialog_callback);
dialog_ex_set_context(app->dialog, app);
dialog_ex_set_left_button_text(app->dialog, "Exit");
dialog_ex_set_right_button_text(app->dialog, "Stay");
dialog_ex_set_center_button_text(app->dialog, "Menu");
dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
view_dispatcher_add_view(
app->view_dispatcher, PomodoroViewExitConfirm, dialog_ex_get_view(app->dialog));
// 25 minutes view
app->pomodoro_25 = pomodoro_25_alloc();
view_set_previous_callback(pomodoro_25_get_view(app->pomodoro_25), pomodoro_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, PomodoroView25, pomodoro_25_get_view(app->pomodoro_25));
// 50 minutes view
app->pomodoro_50 = pomodoro_50_alloc();
view_set_previous_callback(pomodoro_50_get_view(app->pomodoro_50), pomodoro_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, PomodoroView50, pomodoro_50_get_view(app->pomodoro_50));
// 10 minutes view
app->pomodoro_10 = pomodoro_10_alloc();
view_set_previous_callback(pomodoro_10_get_view(app->pomodoro_10), pomodoro_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, PomodoroView10, pomodoro_10_get_view(app->pomodoro_10));
// TODO switch to menu after Media is done
app->view_id = PomodoroViewSubmenu;
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
return app;
}
void pomodoro_app_free(Pomodoro* app) {
furi_assert(app);
// Reset notification
notification_internal_message(app->notifications, &sequence_reset_blue);
// Free views
view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewSubmenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewExitConfirm);
dialog_ex_free(app->dialog);
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView25);
pomodoro_25_free(app->pomodoro_25);
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView50);
pomodoro_50_free(app->pomodoro_50);
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView10);
pomodoro_10_free(app->pomodoro_10);
view_dispatcher_free(app->view_dispatcher);
// Close records
furi_record_close(RECORD_GUI);
app->gui = NULL;
furi_record_close(RECORD_NOTIFICATION);
app->notifications = NULL;
// Free rest
free(app);
}
int32_t pomodoro_app(void* p) {
UNUSED(p);
// Switch profile to Hid
Pomodoro* app = pomodoro_app_alloc();
view_dispatcher_run(app->view_dispatcher);
pomodoro_app_free(app);
return 0;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <notification/notification.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include "pomodoro_timer.h"
#include "views/pomodoro_10.h"
#include "views/pomodoro_25.h"
#include "views/pomodoro_50.h"
typedef struct {
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
DialogEx* dialog;
PomodoroTimer* pomodoro_10;
PomodoroTimer* pomodoro_25;
PomodoroTimer* pomodoro_50;
uint32_t view_id;
} Pomodoro;
typedef enum {
PomodoroViewSubmenu,
PomodoroView10,
PomodoroView25,
PomodoroView50,
PomodoroViewExitConfirm,
} PomodoroView;

View File

@@ -0,0 +1,242 @@
#include "pomodoro_timer.h"
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
#include <notification/notification_messages.h>
#include <Pomodoro_Timer_icons.h>
const NotificationSequence sequence_finish = {
&message_display_backlight_on,
&message_green_255,
&message_vibro_on,
&message_note_c5,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_e5,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_g5,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_b5,
&message_delay_250,
&message_vibro_off,
&message_vibro_on,
&message_note_c6,
&message_delay_250,
&message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_rest = {
&message_display_backlight_on,
&message_red_255,
&message_vibro_on,
&message_note_c6,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_b5,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_g5,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_e5,
&message_delay_100,
&message_vibro_off,
&message_vibro_on,
&message_note_c5,
&message_delay_250,
&message_vibro_off,
&message_sound_off,
NULL,
};
void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event) {
with_view_model(
pomodoro_timer->view,
PomodoroTimerModel * model,
{
if(event->type == InputTypePress) {
if(event->key == InputKeyOk) {
model->ok_pressed = true;
} else if(event->key == InputKeyLeft) {
model->reset_pressed = true;
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
} else if(event->type == InputTypeRelease) {
if(event->key == InputKeyOk) {
model->ok_pressed = false;
// START/STOP TIMER
FuriHalRtcDateTime curr_dt;
furi_hal_rtc_get_datetime(&curr_dt);
uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
// STARTED -> PAUSED
if(model->timer_running) {
// Update stopped seconds
model->timer_stopped_seconds =
current_timestamp - model->timer_start_timestamp;
} else if(!model->time_passed) {
// INITIAL -> STARTED
model->timer_start_timestamp = current_timestamp;
model->rest_running = false;
} else {
// PAUSED -> STARTED
model->timer_start_timestamp =
current_timestamp - model->timer_stopped_seconds;
}
model->timer_running = !model->timer_running;
} else if(event->key == InputKeyLeft) {
if(!model->timer_running) {
furi_record_close(RECORD_NOTIFICATION);
model->timer_stopped_seconds = 0;
model->timer_start_timestamp = 0;
model->time_passed = 0;
model->timer_running = false;
}
model->reset_pressed = false;
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
}
},
true);
}
void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest) {
furi_assert(context);
PomodoroTimerModel* model = context;
FuriHalRtcDateTime curr_dt;
furi_hal_rtc_get_datetime(&curr_dt);
uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
// Header
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Pomodoro");
canvas_draw_icon(canvas, 68, 1, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 1, AlignRight, AlignTop, "Hold to exit");
// Start/Pause/Continue
int txt_main_y = 34;
canvas_draw_icon(canvas, 63, 23, &I_Space_65x18); // button
if(model->ok_pressed) {
elements_slightly_rounded_box(canvas, 66, 25, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
if(model->timer_running) {
model->time_passed = current_timestamp - model->timer_start_timestamp;
elements_multiline_text_aligned(canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Pause");
canvas_draw_box(canvas, 71, 27, 2, 8);
canvas_draw_box(canvas, 75, 27, 2, 8);
} else {
if(model->time_passed) {
elements_multiline_text_aligned(
canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Continue");
} else {
elements_multiline_text_aligned(
canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Start");
}
canvas_draw_icon(canvas, 70, 26, &I_Ok_btn_9x9); // OK icon
}
canvas_set_color(canvas, ColorBlack);
// Reset
if(!model->timer_running && model->time_passed) {
canvas_draw_icon(canvas, 63, 46, &I_Space_65x18);
if(model->reset_pressed) {
elements_slightly_rounded_box(canvas, 66, 48, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 72, 50, &I_ButtonLeft_4x7);
elements_multiline_text_aligned(canvas, 83, 57, AlignLeft, AlignBottom, "Reset");
canvas_set_color(canvas, ColorBlack);
}
char buffer[64];
// Time to work
int total_time_left = (max_seconds - (uint32_t)model->time_passed);
int minutes_left = total_time_left / 60;
int seconds_left = total_time_left % 60;
canvas_set_font(canvas, FontBigNumbers);
// Play sound
if(total_time_left == 0 && !model->sound_playing) {
model->sound_playing = true;
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_finish);
}
if(total_time_left < 0) {
model->timer_running = false;
model->time_passed = 0;
model->sound_playing = false;
model->rest_running = true;
model->rest_start_timestamp = current_timestamp;
seconds_left = 0;
model->counter += 1;
}
if(!model->rest_running) {
snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes_left, seconds_left);
canvas_draw_str(canvas, 0, 39, buffer);
}
if(model->timer_running) {
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 0, 50, AlignLeft, AlignTop, "Time to work");
}
// Time to rest
if(model->rest_running && !model->timer_running) {
canvas_set_font(canvas, FontBigNumbers);
int rest_passed = current_timestamp - model->rest_start_timestamp;
int rest_total_time_left = (max_seconds_rest - rest_passed);
int rest_minutes_left = rest_total_time_left / 60;
int rest_seconds_left = rest_total_time_left % 60;
// Play sound
if(rest_total_time_left == 0 && !model->sound_playing) {
model->sound_playing = true;
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_rest);
}
if(rest_total_time_left < 0) {
rest_seconds_left = 0;
model->rest_running = false;
model->sound_playing = false;
}
snprintf(buffer, sizeof(buffer), "%02d:%02d", rest_minutes_left, rest_seconds_left);
canvas_draw_str(canvas, 0, 60, buffer);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 0, 27, AlignLeft, AlignTop, "Have a rest");
}
// Clocks
canvas_set_font(canvas, FontSecondary);
snprintf(
buffer,
sizeof(buffer),
"%02ld:%02ld:%02ld",
((uint32_t)current_timestamp % (60 * 60 * 24)) / (60 * 60),
((uint32_t)current_timestamp % (60 * 60)) / 60,
(uint32_t)current_timestamp % 60);
canvas_draw_str(canvas, 0, 20, buffer);
// Tomato counter
if(model->counter > 5) {
model->counter = 1;
}
for(int i = 0; i < model->counter; ++i) {
canvas_draw_disc(canvas, 122 - i * 10, 15, 4);
}
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include <gui/view.h>
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
typedef struct PomodoroTimer PomodoroTimer;
struct PomodoroTimer {
View* view;
};
typedef struct PomodoroTimerModel PomodoroTimerModel;
struct PomodoroTimerModel {
bool ok_pressed;
bool reset_pressed;
bool back_pressed;
bool connected;
bool timer_running;
bool rest_running;
bool sound_playing;
uint32_t timer_start_timestamp;
uint32_t timer_stopped_seconds;
uint32_t time_passed;
uint32_t rest_start_timestamp;
int counter;
};
void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event);
void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest);

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

View File

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

View File

@@ -1,30 +0,0 @@
#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

@@ -1,27 +0,0 @@
#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

@@ -1,71 +0,0 @@
#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

@@ -1,195 +0,0 @@
#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

@@ -1,21 +0,0 @@
#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,46 @@
#include "../pomodoro_timer.h"
#include "pomodoro_10.h"
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
#include <notification/notification_messages.h>
static void pomodoro_10_draw_callback(Canvas* canvas, void* context) {
int max_seconds = 60 * 10;
int max_seconds_rest = 60 * 2;
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
}
static bool pomodoro_10_input_callback(InputEvent* event, void* context) {
furi_assert(context);
PomodoroTimer* pomodoro_10 = context;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
return false;
} else {
pomodoro_timer_process(pomodoro_10, event);
return true;
}
}
PomodoroTimer* pomodoro_10_alloc() {
PomodoroTimer* pomodoro_10 = malloc(sizeof(PomodoroTimer));
pomodoro_10->view = view_alloc();
view_set_context(pomodoro_10->view, pomodoro_10);
view_allocate_model(pomodoro_10->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
view_set_draw_callback(pomodoro_10->view, pomodoro_10_draw_callback);
view_set_input_callback(pomodoro_10->view, pomodoro_10_input_callback);
return pomodoro_10;
}
void pomodoro_10_free(PomodoroTimer* pomodoro_10) {
furi_assert(pomodoro_10);
view_free(pomodoro_10->view);
free(pomodoro_10);
}
View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10) {
furi_assert(pomodoro_10);
return pomodoro_10->view;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include <gui/view.h>
#include "../pomodoro_timer.h"
PomodoroTimer* pomodoro_10_alloc();
void pomodoro_10_free(PomodoroTimer* pomodoro_10);
View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10);

View File

@@ -0,0 +1,46 @@
#include "../pomodoro_timer.h"
#include "pomodoro_25.h"
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
#include <notification/notification_messages.h>
static void pomodoro_25_draw_callback(Canvas* canvas, void* context) {
int max_seconds = 60 * 25;
int max_seconds_rest = 60 * 5;
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
}
static bool pomodoro_25_input_callback(InputEvent* event, void* context) {
furi_assert(context);
PomodoroTimer* pomodoro_25 = context;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
return false;
} else {
pomodoro_timer_process(pomodoro_25, event);
return true;
}
}
PomodoroTimer* pomodoro_25_alloc() {
PomodoroTimer* pomodoro_25 = malloc(sizeof(PomodoroTimer));
pomodoro_25->view = view_alloc();
view_set_context(pomodoro_25->view, pomodoro_25);
view_allocate_model(pomodoro_25->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
view_set_draw_callback(pomodoro_25->view, pomodoro_25_draw_callback);
view_set_input_callback(pomodoro_25->view, pomodoro_25_input_callback);
return pomodoro_25;
}
void pomodoro_25_free(PomodoroTimer* pomodoro_25) {
furi_assert(pomodoro_25);
view_free(pomodoro_25->view);
free(pomodoro_25);
}
View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25) {
furi_assert(pomodoro_25);
return pomodoro_25->view;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include <gui/view.h>
#include "../pomodoro_timer.h"
PomodoroTimer* pomodoro_25_alloc();
void pomodoro_25_free(PomodoroTimer* pomodoro_25);
View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25);

View File

@@ -0,0 +1,46 @@
#include "../pomodoro_timer.h"
#include "pomodoro_50.h"
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
#include <notification/notification_messages.h>
static void pomodoro_50_draw_callback(Canvas* canvas, void* context) {
int max_seconds = 60 * 50;
int max_seconds_rest = 60 * 10;
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
}
static bool pomodoro_50_input_callback(InputEvent* event, void* context) {
furi_assert(context);
PomodoroTimer* pomodoro_50 = context;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
return false;
} else {
pomodoro_timer_process(pomodoro_50, event);
return true;
}
}
PomodoroTimer* pomodoro_50_alloc() {
PomodoroTimer* pomodoro_50 = malloc(sizeof(PomodoroTimer));
pomodoro_50->view = view_alloc();
view_set_context(pomodoro_50->view, pomodoro_50);
view_allocate_model(pomodoro_50->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
view_set_draw_callback(pomodoro_50->view, pomodoro_50_draw_callback);
view_set_input_callback(pomodoro_50->view, pomodoro_50_input_callback);
return pomodoro_50;
}
void pomodoro_50_free(PomodoroTimer* pomodoro_50) {
furi_assert(pomodoro_50);
view_free(pomodoro_50->view);
free(pomodoro_50);
}
View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50) {
furi_assert(pomodoro_50);
return pomodoro_50->view;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include <gui/view.h>
#include "../pomodoro_timer.h"
PomodoroTimer* pomodoro_50_alloc();
void pomodoro_50_free(PomodoroTimer* pomodoro_50);
View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50);

View File

@@ -1,5 +1,5 @@
App(
appid="RC2014_Coleco",
appid="coleco",
name="[RC2014] ColecoVision",
apptype=FlipperAppType.EXTERNAL,
entry_point="coleco_app",

View File

@@ -2,7 +2,7 @@
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <gui/gui.h>
#include "RC2014_Coleco_icons.h"
#include "coleco_icons.h"
#define CODE_0 0x0A
#define CODE_1 0x0D
@@ -40,16 +40,16 @@ typedef struct {
} PluginEvent;
typedef struct {
FuriMutex* mutex;
bool dpad;
int row;
int column;
} Coleco;
static void render_callback(Canvas* const canvas, void* context) {
Coleco* coleco = acquire_mutex((ValueMutex*)context, 25);
if(coleco == NULL) {
return;
}
furi_assert(context);
Coleco* coleco = context;
furi_mutex_acquire(coleco->mutex, FuriWaitForever);
if(coleco->dpad) {
canvas_draw_icon(canvas, 4, 16, &I_ColecoJoystick_sel_33x33);
@@ -128,7 +128,7 @@ static void render_callback(Canvas* const canvas, void* context) {
(coleco->row == 4 && coleco->column == 2) ? &I_ColecoPound_hvr_17x17 :
&I_ColecoPound_17x17);
release_mutex((ValueMutex*)context, coleco);
furi_mutex_release(coleco->mutex);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
@@ -191,8 +191,8 @@ int32_t coleco_app(void* p) {
Coleco* coleco = coleco_alloc();
ValueMutex coleco_mutex;
if(!init_mutex(&coleco_mutex, coleco, sizeof(Coleco))) {
coleco->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!coleco->mutex) {
FURI_LOG_E("Coleco", "cannot create mutex\r\n");
coleco_free(coleco);
return 255;
@@ -200,7 +200,7 @@ int32_t coleco_app(void* p) {
// set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &coleco_mutex);
view_port_draw_callback_set(view_port, render_callback, coleco);
view_port_input_callback_set(view_port, input_callback, event_queue);
// open GUI and register view_port
@@ -214,7 +214,7 @@ int32_t coleco_app(void* p) {
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
Coleco* coleco = (Coleco*)acquire_mutex_block(&coleco_mutex);
furi_mutex_acquire(coleco->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
@@ -346,11 +346,9 @@ int32_t coleco_app(void* p) {
view_port_update(view_port);
}
} else {
FURI_LOG_D("Coleco", "FuriMessageQueue: event timeout");
}
release_mutex(&coleco_mutex, coleco);
furi_mutex_release(coleco->mutex);
}
furi_hal_power_disable_otg();
@@ -360,7 +358,7 @@ int32_t coleco_app(void* p) {
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&coleco_mutex);
furi_mutex_free(coleco->mutex);
coleco_free(coleco);
return 0;
}

View File

@@ -1,7 +1,7 @@
App(
appid="SAM",
name="SAM AYBABTU",
apptype=FlipperAppType.EXTERNAL,
apptype=FlipperAppType.PLUGIN,
entry_point="sam_app",
requires=[
"gui",
@@ -9,7 +9,7 @@ App(
],
stack_size=4 * 1024,
order=20,
fap_icon="icons/music_10px.png",
fap_icon="music_10px.png",
fap_category="Music",
fap_icon_assets="icons",
)
@@ -24,7 +24,7 @@ App(
],
stack_size=4 * 1024,
order=20,
fap_icon="icons/music_10px.png",
fap_icon="music_10px.png",
fap_category="Music",
fap_icon_assets="icons",
)
@@ -39,7 +39,7 @@ App(
],
stack_size=4 * 1024,
order=20,
fap_icon="icons/music_10px.png",
fap_icon="music_10px.png",
fap_category="Music",
fap_icon_assets="icons",
)
@@ -54,7 +54,7 @@ App(
],
stack_size=4 * 1024,
order=20,
fap_icon="icons/music_10px.png",
fap_icon="music_10px.png",
fap_category="Music",
fap_icon_assets="icons",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

View File

@@ -6,7 +6,7 @@ STM32SAM voice;
extern "C" int32_t sam_app(void* p) {
UNUSED(p);
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
voice.begin();
voice.say(
"All your base are belong to us. You have no chance to survive make your time. ha. ha. ha. GOOD BYE. ");
@@ -17,7 +17,7 @@ extern "C" int32_t sam_app(void* p) {
extern "C" int32_t sam_app_yes(void* p) {
UNUSED(p);
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
voice.begin();
voice.say("Yes");
furi_hal_speaker_release();
@@ -27,7 +27,7 @@ extern "C" int32_t sam_app_yes(void* p) {
extern "C" int32_t sam_app_no(void* p) {
UNUSED(p);
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
voice.begin();
voice.say("No");
furi_hal_speaker_release();
@@ -37,10 +37,10 @@ extern "C" int32_t sam_app_no(void* p) {
extern "C" int32_t sam_app_wtf(void* p) {
UNUSED(p);
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
voice.begin();
voice.say("What The Fuck");
furi_hal_speaker_release();
}
return 0;
}
}

View File

@@ -8,27 +8,32 @@ This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://gith
How to play
-----------
Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`.
Use a search engine to find the Tamagotchi ROM. There is a file named `a`.
Use a search engine to find the Tamagotchi ROM. There is a file named `tama.b`.
Rename this to `rom.bin`.
*Controls in portrait mode are the same as landscape mode, but turned 90 degrees.*
- Left button is A.
- OK is B.
- Down or OK is B.
- Right button is C.
- Holding the Up button functions the same as press both A and C, which mutes the volume.
- Up button takes you to the emulator menu.
- Hold the Back button to save and exit.
![Alt Text](Screenshot1.png)
![Alt Text](Screenshot2.png)
Building
--------
Move this folder into flippers applications/plugins/tama_p1.
Move this folder into flippers `applications/plugins/tama_p1`.
Launching the app, directly from console to flipper:
`./fbt launch_app APPSRC=applications\plugins\tama_p1`
```
./fbt launch_app APPSRC=applications/plugins/tama_p1
```
Run the following to compile icons:
```
scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled
scripts/assets.py icons applications/plugins/tama_p1/icons applications/plugins/tama_p1/compiled
```
Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in
@@ -38,22 +43,34 @@ Debugging
---------
Using the serial script from [FlipperScripts](https://github.com/DroomOne/FlipperScripts/blob/main/serial_logger.py)
it is easy to add direct logging after running the application:
```
`python .\serial_logger.py`
`./fbt launch_app APPSRC=applications\plugins\tama_p1; python .\serial_logger.py`
`./fbt launch_app APPSRC=applications/plugins/tama_p1; python .\serial_logger.py`
```
Alternatively, follow the directions here: https://flipper.atmanos.com/docs/debugging/viewing/
Implemented
-----------
- Basic emulation
- Input
- Sound
- Saving/Loading emulator state (stored in `/ext/tama_p1/save.bin`)
- Mute button combo shortcut (Up = A+C)
- Menu options:
- Switch between portrait and landscape
- A+C shortcut (mute/change in-game time)
- Double / quadruple speed
To-do
![Alt Text](Screenshot3.png)
To-Do
-----
- more than one save slot
- In-game reset
- Test mode?
- Volume adjustment
- Fix bugs:
- When not on 1x speed, after mashing buttons in quick succession, buttons stop responding for a few seconds. But the rom still runs.
- Stuff to do when bored:
- optimization and bug fixing (see above)
- add to this list
- portrait menu
- Add "loading bar" when saving
- "Advanced" settings
- saving and loading, multiple save states, with the date and time of of each save.
- Autosave and changing autosave frequency
- Save settings to /tama_p1/settings.txt
![Alt Text](Screenshot4.png)

View File

@@ -5,7 +5,7 @@ App(
entry_point="tama_p1_app",
cdefines=["APP_TAMA_P1"],
requires=["gui", "storage"],
stack_size=1 * 1024,
stack_size=2 * 1024,
order=215,
fap_icon="tamaIcon.png",
fap_category="Games",

View File

@@ -10,6 +10,21 @@
TamaApp* g_ctx;
FuriMutex* g_state_mutex;
uint8_t layout_mode = 0; // 3: portrait => 4: portrait <=
// 0: landscape (small) 1: landscape (big) 2: landscape (full)
bool in_menu = false;
uint8_t speed = 1;
const uint8_t speed_options[] = {1, 2, 4};
const uint8_t min_speed = 1;
const uint8_t max_speed = 4;
const uint8_t speed_options_size = 3;
// = sizeof(speed_options) / sizeof(speed_options[0]);
uint8_t menu_cursor = 3; // 0: layout mode; 1: speed; 2: A+C;
const uint8_t menu_items = 4; // 3: Close menu, Save & Exit
uint8_t sub_menu_buttons = 0;
uint8_t sub_menu_last = 0;
static const Icon* icons_list[] = {
&I_icon_0,
@@ -22,6 +37,384 @@ static const Icon* icons_list[] = {
&I_icon_7,
};
static InputKey m = InputKeyUp;
static InputKey a = InputKeyLeft;
static InputKey b = InputKeyDown;
static InputKey c = InputKeyRight;
static void speed_up() {
switch(speed) {
case max_speed:
speed = speed_options[0];
break;
default:
for(uint8_t i = 0; i < speed_options_size - 1; i++) {
if(speed == speed_options[i]) {
speed = speed_options[i + 1];
break;
}
}
break;
}
tamalib_set_speed(speed);
}
static void speed_down() {
switch(speed) {
case min_speed:
speed = speed_options[speed_options_size - 1];
break;
default:
for(uint8_t i = speed_options_size - 1; i > 0; i--) {
if(speed == speed_options[i]) {
speed = speed_options[i - 1];
break;
}
}
break;
}
tamalib_set_speed(speed);
}
// static void draw_landscape(Canvas* const canvas, void* cb_ctx)
static void draw_landscape(Canvas* const canvas, uint8_t scale) {
// FURI_LOG_D(TAG, "Drawing frame");
// Calculate positioning
uint16_t canv_width = canvas_width(canvas);
uint16_t canv_height = canvas_height(canvas);
uint16_t lcd_matrix_scaled_width = 32 * scale;
uint16_t lcd_matrix_scaled_height = 16 * scale;
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2; // 0
uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
// uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN;
// uint16_t lcd_icon_lower_top = lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN;
uint16_t lcd_icon_upper_left = lcd_matrix_left;
uint16_t lcd_icon_lower_left = lcd_matrix_left;
uint16_t lcd_icon_spacing_horiz =
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
uint16_t y = lcd_matrix_top;
for(uint8_t row = 0; row < 16; ++row) {
uint16_t x = lcd_matrix_left;
uint32_t row_pixels = g_ctx->framebuffer[row];
for(uint8_t col = 0; col < 32; ++col) {
if(row_pixels & 1) {
canvas_draw_box(canvas, x, y, scale, scale);
}
x += scale;
row_pixels >>= 1;
}
y += scale;
}
// Start drawing icons
uint8_t lcd_icons = g_ctx->icons;
// Draw top icons
y = 0;
uint16_t x_ic = lcd_icon_upper_left;
for(uint8_t i = 0; i < 4; ++i) {
if(lcd_icons & 1) {
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
}
// x_ic += TAMA_LCD_ICON_SIZE + 4;
// if(scale == 3) {
// y += 16;
// } else {
x_ic += lcd_icon_spacing_horiz;
// }
lcd_icons >>= 1;
}
// Draw bottom icons
y = 64 - TAMA_LCD_ICON_SIZE;
// if(scale == 3) {
// y = 0;
// x_ic = 128 - TAMA_LCD_ICON_SIZE;
// x_ic = 0;
// } else {
// y = 64 - TAMA_LCD_ICON_SIZE;
x_ic = lcd_icon_lower_left;
// }
for(uint8_t i = 4; i < 8; ++i) {
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
if(lcd_icons & 1) {
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
}
// if(scale == 3) {
// y += 16;
// } else {
x_ic += lcd_icon_spacing_horiz;
// }
lcd_icons >>= 1;
}
}
// static void draw_portrait_right(Canvas* const canvas, void* cb_ctx)
static void draw_portrait_right(Canvas* const canvas, uint8_t scale) {
// FURI_LOG_D(TAG, "Drawing frame");
// Calculate positioning
// uint16_t canv_width = canvas_width(canvas);
uint16_t canv_height = canvas_height(canvas);
uint16_t lcd_matrix_scaled_width = 32 * scale;
uint16_t lcd_matrix_scaled_height = 16 * scale;
// uint16_t lcd_matrix_top = 0;
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
// uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
uint16_t lcd_matrix_left = 64 - TAMA_LCD_ICON_SIZE;
uint16_t lcd_icon_upper_left = lcd_matrix_left;
uint16_t lcd_icon_lower_left = lcd_matrix_left;
uint16_t lcd_icon_spacing_horiz =
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
uint16_t y = lcd_matrix_top; // 64
for(uint8_t row = 0; row < 16; ++row) {
uint16_t x = 128; // lcd_matrix_left
uint32_t row_pixels = g_ctx->framebuffer[row];
for(uint8_t col = 0; col < 32; ++col) {
if(row_pixels & 1) {
canvas_draw_box(canvas, y + 32, x - 66, scale, scale);
}
x -= scale;
row_pixels >>= 1;
}
y += scale;
}
// Start drawing icons
uint8_t lcd_icons = g_ctx->icons;
// Draw top icons
// y = lcd_icon_upper_top;
y = 30;
// y = 64 - TAMA_LCD_ICON_SIZE;
uint16_t x_ic = lcd_icon_upper_left;
// uint16_t x_ic = 64 - TAMA_LCD_ICON_SIZE;
for(uint8_t i = 0; i < 4; ++i) {
if(lcd_icons & 1) {
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
}
x_ic -= lcd_icon_spacing_horiz; // TAMA_LCD_ICON_SIZE + 4;
lcd_icons >>= 1;
}
// Draw bottom icons
y = 84; // lcd_icon_lower_top
x_ic = lcd_icon_lower_left; // 64 - TAMA_LCD_ICON_SIZE
for(uint8_t i = 4; i < 8; ++i) {
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
if(lcd_icons & 1) {
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
}
x_ic -= lcd_icon_spacing_horiz;
lcd_icons >>= 1;
}
}
static void draw_portrait_left(Canvas* const canvas, uint8_t scale) {
// FURI_LOG_D(TAG, "Drawing frame");
// Calculate positioning
// uint16_t canv_width = canvas_width(canvas);
// uint16_t canv_height = canvas_height(canvas);
uint16_t lcd_matrix_scaled_width = 32 * scale;
// uint16_t lcd_matrix_scaled_height = 16 * scale;
// uint16_t lcd_matrix_top = 0;
// uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
// uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
uint16_t lcd_matrix_left = 0;
uint16_t lcd_icon_upper_left = lcd_matrix_left;
uint16_t lcd_icon_lower_left = lcd_matrix_left;
uint16_t lcd_icon_spacing_horiz =
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
// uint16_t y = 64 + lcd_matrix_top;
uint16_t y = 64;
for(uint8_t row = 0; row < 16; ++row) {
uint16_t x = 0; // lcd_matrix_left
uint32_t row_pixels = g_ctx->framebuffer[row];
for(uint8_t col = 0; col < 32; ++col) {
if(row_pixels & 1) {
canvas_draw_box(canvas, y, x, scale, scale);
}
x += scale;
row_pixels >>= 1;
}
y -= scale;
}
// Start drawing icons
uint8_t lcd_icons = g_ctx->icons;
// Draw top icons
// y = lcd_icon_upper_top;
y = 70;
// y = 64 - TAMA_LCD_ICON_SIZE;
uint16_t x_ic = lcd_icon_upper_left;
// uint16_t x_ic = 64 - TAMA_LCD_ICON_SIZE;
for(uint8_t i = 0; i < 4; ++i) {
if(lcd_icons & 1) {
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
}
x_ic += lcd_icon_spacing_horiz; // TAMA_LCD_ICON_SIZE + 4;
lcd_icons >>= 1;
}
// Draw bottom icons
y = 16; // lcd_icon_lower_top
x_ic = lcd_icon_lower_left; // 64 - TAMA_LCD_ICON_SIZE
for(uint8_t i = 4; i < 8; ++i) {
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
if(lcd_icons & 1) {
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
}
x_ic += lcd_icon_spacing_horiz;
lcd_icons >>= 1;
}
}
// static void draw_mini(Canvas* const canvas, uint16_t inX, uint16_t inY)
static void draw_mini(Canvas* const canvas) {
// Calculate positioning
// uint16_t y = inY;
const uint16_t y = 34;
const uint16_t x = 84;
uint16_t y1 = y;
for(uint8_t row = 0; row < 16; ++row) {
// uint16_t x = inX;
uint16_t x1 = x;
uint32_t row_pixels = g_ctx->framebuffer[row];
for(uint8_t col = 0; col < 32; ++col) {
if(row_pixels & 1) {
canvas_draw_dot(canvas, x1, y1);
}
x1 += 1;
row_pixels >>= 1;
}
y1 += 1;
}
// Start drawing icons
uint8_t lcd_icons = g_ctx->icons;
// Draw top icons
uint16_t y2 = y - 2;
uint16_t x_ic = x;
for(uint8_t i = 0; i < 4; ++i) {
if(lcd_icons & 1) {
// canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
canvas_draw_line(canvas, x_ic, y2, x_ic + 6, y2);
}
x_ic += 8;
lcd_icons >>= 1;
}
// Draw bottom icons
y2 = y + 17;
x_ic = x;
for(uint8_t i = 4; i < 8; ++i) {
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
if(lcd_icons & 1) {
// canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
canvas_draw_line(canvas, x_ic, y2, x_ic + 6, y2);
}
x_ic += 8;
lcd_icons >>= 1;
}
}
static void draw_menu(Canvas* const canvas) {
canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "Menu");
canvas_draw_line(canvas, 0, 10, 128, 10);
draw_mini(canvas);
// draw_mini(canvas, 34, 84);
switch(menu_cursor) {
case 0:
canvas_draw_triangle(canvas, 4, 16, 6, 6, CanvasDirectionLeftToRight);
break;
case 1:
canvas_draw_triangle(canvas, 4, 26, 6, 6, CanvasDirectionLeftToRight);
break;
case 2:
switch(sub_menu_buttons) {
case 0:
canvas_draw_triangle(canvas, 4, 36, 6, 6, CanvasDirectionLeftToRight);
break;
case 1:
canvas_draw_triangle(canvas, 47, 44, 6, 4, CanvasDirectionBottomToTop);
break;
case 2:
canvas_draw_triangle(canvas, 57, 44, 6, 4, CanvasDirectionBottomToTop);
break;
case 3:
canvas_draw_triangle(canvas, 67, 44, 6, 4, CanvasDirectionBottomToTop);
break;
default:
break;
}
break;
case menu_items - 1:
switch(sub_menu_last) {
case 0:
canvas_draw_triangle(canvas, 4, 56, 6, 6, CanvasDirectionLeftToRight);
break;
case 1:
canvas_draw_triangle(canvas, 36, 56, 6, 6, CanvasDirectionLeftToRight);
break;
case 2:
canvas_draw_triangle(canvas, 67, 56, 6, 6, CanvasDirectionLeftToRight);
break;
default:
break;
}
break;
}
switch(layout_mode) {
case 0:
canvas_draw_str(canvas, 12, 20, "Layout: Landscape (small)");
break;
case 1:
canvas_draw_str(canvas, 12, 20, "Layout: Landscape (big)");
break;
case 2:
canvas_draw_str(canvas, 12, 20, "Layout: Landscape (full)");
break;
case 3:
canvas_draw_str(canvas, 12, 20, "Layout: Portrait =>");
break;
case 4:
canvas_draw_str(canvas, 12, 20, "Layout: Portrait <=");
break;
default:
canvas_draw_str(canvas, 12, 20, "Layout: ???");
break;
}
switch(speed) { // match with speed_options
// case 0: // freeze menu too
// canvas_draw_str(canvas, 12, 30, "Speed: 0x");
// break;
case 1:
canvas_draw_str(canvas, 12, 30, "Speed: 1x");
break;
case 2:
canvas_draw_str(canvas, 12, 30, "Speed: 2x");
break;
case 4:
canvas_draw_str(canvas, 12, 30, "Speed: 4x (max)");
break;
default:
canvas_draw_str(canvas, 12, 30, "Speed ??x");
break;
}
canvas_draw_str(canvas, 12, 40, "A+C");
canvas_draw_str(canvas, 45, 40, "A");
canvas_draw_str(canvas, 55, 40, "B");
canvas_draw_str(canvas, 65, 40, "C");
canvas_draw_str(canvas, 12, 60, "Close");
canvas_draw_str(canvas, 44, 60, "Save");
canvas_draw_str(canvas, 75, 60, "Save & Exit");
}
static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
furi_assert(cb_ctx);
@@ -35,68 +428,32 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 30, 30, "Halted");
} else {
// FURI_LOG_D(TAG, "Drawing frame");
// Calculate positioning
uint16_t canv_width = canvas_width(canvas);
uint16_t canv_height = canvas_height(canvas);
uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR;
uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR;
// uint16_t lcd_matrix_top = 0;
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN;
uint16_t lcd_icon_upper_left = lcd_matrix_left;
uint16_t lcd_icon_lower_top =
lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN;
uint16_t lcd_icon_lower_left = lcd_matrix_left;
uint16_t lcd_icon_spacing_horiz =
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
uint16_t y = lcd_matrix_top;
for(uint8_t row = 0; row < 16; ++row) {
uint16_t x = lcd_matrix_left;
uint32_t row_pixels = g_ctx->framebuffer[row];
for(uint8_t col = 0; col < 32; ++col) {
if(row_pixels & 1) {
canvas_draw_box(
canvas, x, y, TAMA_SCREEN_SCALE_FACTOR, TAMA_SCREEN_SCALE_FACTOR);
}
x += TAMA_SCREEN_SCALE_FACTOR;
row_pixels >>= 1;
if(in_menu) {
// switch(layout_mode)
// draw_menu_landscape(canvas);
draw_menu(canvas);
} else {
switch(layout_mode) {
case 0:
draw_landscape(canvas, TAMA_SCREEN_SCALE_FACTOR); // 2
break;
case 1:
draw_landscape(canvas, 3);
break;
case 2:
draw_landscape(canvas, 4);
break;
case 3:
draw_portrait_right(canvas, TAMA_SCREEN_SCALE_FACTOR);
break;
case 4:
draw_portrait_left(canvas, TAMA_SCREEN_SCALE_FACTOR);
break;
default:
break;
}
y += TAMA_SCREEN_SCALE_FACTOR;
}
// Start drawing icons
uint8_t lcd_icons = g_ctx->icons;
// Draw top icons
y = lcd_icon_upper_top;
// y = 64 - TAMA_LCD_ICON_SIZE;
uint16_t x_ic = lcd_icon_upper_left;
for(uint8_t i = 0; i < 4; ++i) {
if(lcd_icons & 1) {
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
}
// x_ic += TAMA_LCD_ICON_SIZE + 4;
x_ic += lcd_icon_spacing_horiz;
lcd_icons >>= 1;
}
// Draw bottom icons
y = lcd_icon_lower_top;
x_ic = lcd_icon_lower_left;
for(uint8_t i = 4; i < 8; ++i) {
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
if(lcd_icons & 1) {
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
}
x_ic += lcd_icon_spacing_horiz;
lcd_icons >>= 1;
}
}
furi_mutex_release(mutex);
}
@@ -345,9 +702,9 @@ static int32_t tama_p1_worker(void* context) {
if(furi_thread_flags_get()) {
running = false;
} else {
// FURI_LOG_D(TAG, "Stepping");
// FURI_LOG_D(TAG, "Stepping"); // enabling this cause blank screen somehow
// for (int i = 0; i < 100; ++i)
tamalib_step();
tamalib_step(); // tamalib_mainloop();
}
}
LL_TIM_DisableCounter(TIM2);
@@ -406,15 +763,15 @@ static void tama_p1_init(TamaApp* const ctx) {
// Init TamaLIB
tamalib_register_hal(&ctx->hal);
tamalib_init((u12_t*)ctx->rom, NULL, 64000);
tamalib_set_speed(1);
tamalib_set_speed(speed);
// TODO: implement fast forwarding
ctx->fast_forward_done = true;
// ctx->fast_forward_done = true;
// Start stepping thread
ctx->thread = furi_thread_alloc();
furi_thread_set_name(ctx->thread, "TamaLIB");
furi_thread_set_stack_size(ctx->thread, 1024);
furi_thread_set_stack_size(ctx->thread, 2 * 1024);
furi_thread_set_callback(ctx->thread, tama_p1_worker);
furi_thread_set_context(ctx->thread, g_state_mutex);
furi_thread_start(ctx->thread);
@@ -449,6 +806,9 @@ int32_t tama_p1_app(void* p) {
furi_timer_alloc(tama_p1_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
// in_menu = false;
// menu_cursor = 2;
for(bool running = true; running;) {
TamaEvent event;
FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
@@ -466,51 +826,353 @@ int32_t tama_p1_app(void* p) {
event.input.sequence,
event.input.key,
event.input.type);
InputType input_type = event.input.type;
if(input_type == InputTypePress || input_type == InputTypeRelease) {
btn_state_t tama_btn_state = 0;
if(input_type == InputTypePress)
tama_btn_state = BTN_STATE_PRESSED;
else if(input_type == InputTypeRelease)
tama_btn_state = BTN_STATE_RELEASED;
// InputType input_type = event.input.type; // idk why this is a variable
btn_state_t tama_btn_state = 0; // BTN_STATE_RELEASED is 0
if(event.input.key == InputKeyLeft) {
tamalib_set_button(BTN_LEFT, tama_btn_state);
if(in_menu) {
// if(menu_cursor == 2 &&
// (event.input.key == InputKeyUp || event.input.key == InputKeyDown)) {
// tama_btn_state = BTN_STATE_RELEASED;
// }
if(event.input.key == InputKeyBack) {
tama_btn_state = BTN_STATE_RELEASED;
in_menu = false;
} else if(event.input.key == InputKeyUp && event.input.type == InputTypePress) {
tama_btn_state = BTN_STATE_RELEASED;
if(menu_cursor > 0) {
menu_cursor -= 1;
} else {
menu_cursor = menu_items - 1;
}
if(menu_cursor >= menu_items - 2 && sub_menu_last > 0) {
sub_menu_buttons = 1;
sub_menu_last = 1;
} else {
sub_menu_buttons = 0;
sub_menu_last = 0;
}
} else if(event.input.key == InputKeyDown && event.input.type == InputTypePress) {
tama_btn_state = BTN_STATE_RELEASED;
if(menu_cursor < menu_items - 1) {
menu_cursor += 1;
} else {
menu_cursor = 0;
}
if(menu_cursor >= menu_items - 2 && sub_menu_buttons > 0) {
sub_menu_buttons = 1;
sub_menu_last = 1;
} else {
sub_menu_buttons = 0;
sub_menu_last = 0;
}
} else if(event.input.key == InputKeyLeft && event.input.type == InputTypePress) {
switch(menu_cursor) {
case 0:
switch(layout_mode) {
case 0:
layout_mode = 4;
m = InputKeyRight;
a = InputKeyUp;
b = InputKeyLeft;
c = InputKeyDown;
break;
case 1:
layout_mode -= 1; // 0
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 2:
layout_mode -= 1; // 1
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 3:
layout_mode -= 1; // 2
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 4:
layout_mode -= 1; // 3
m = InputKeyLeft;
a = InputKeyDown;
b = InputKeyRight;
c = InputKeyUp;
break;
}
break;
case 1:
speed_down();
break;
case 2:
tama_btn_state = BTN_STATE_RELEASED;
switch(sub_menu_buttons) {
case 0:
sub_menu_buttons = 3;
break;
case 1:
case 2:
case 3:
sub_menu_buttons -= 1;
break;
default:
break;
}
break;
case menu_items - 1:
switch(sub_menu_last) {
case 0:
sub_menu_last = 2;
break;
case 1:
case 2:
sub_menu_last -= 1;
break;
default:
break;
}
break;
default:
break;
}
} else if(event.input.key == InputKeyRight && event.input.type == InputTypePress) {
switch(menu_cursor) {
case 0:
switch(layout_mode) {
case 0:
layout_mode += 1; // 1
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 1:
layout_mode += 1; // 2
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 2:
layout_mode += 1; // 3
m = InputKeyLeft;
a = InputKeyDown;
b = InputKeyRight;
c = InputKeyUp;
break;
case 3:
layout_mode += 1; // 4
m = InputKeyRight;
a = InputKeyUp;
b = InputKeyLeft;
c = InputKeyDown;
break;
case 4:
layout_mode = 0;
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
}
break;
case 1:
speed_up();
break;
case 2:
tama_btn_state = BTN_STATE_RELEASED;
switch(sub_menu_buttons) {
case 0:
case 1:
case 2:
sub_menu_buttons += 1;
break;
case 3:
sub_menu_buttons = 0;
break;
default:
break;
}
break;
case menu_items - 1:
switch(sub_menu_last) {
case 0:
case 1:
sub_menu_last += 1;
break;
case 2:
sub_menu_last = 0;
break;
default:
break;
}
break;
default:
break;
}
} else if(event.input.key == InputKeyOk) {
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
} else if(event.input.key == InputKeyRight) {
tamalib_set_button(BTN_RIGHT, tama_btn_state);
} else if(event.input.key == InputKeyDown && event.input.type == InputTypeShort) {
// TODO: pause or fast-forward tamagotchi
tama_p1_save_state();
} else if(event.input.key == InputKeyUp) { // mute tamagotchi
tamalib_set_button(BTN_LEFT, tama_btn_state);
tamalib_set_button(BTN_RIGHT, tama_btn_state);
} else if(event.input.key == InputKeyBack && event.input.type == InputTypeShort) {
switch(menu_cursor) {
case 0:
if(event.input.type == InputTypePress) {
switch(layout_mode) {
case 0:
layout_mode += 1; // 1
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 1:
layout_mode += 1; // 2
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
case 2:
layout_mode += 1; // 3
m = InputKeyLeft;
a = InputKeyDown;
b = InputKeyRight;
c = InputKeyUp;
break;
case 3:
layout_mode += 1; // 4
m = InputKeyRight;
a = InputKeyUp;
b = InputKeyLeft;
c = InputKeyDown;
break;
case 4:
layout_mode = 0;
m = InputKeyUp;
a = InputKeyLeft;
b = InputKeyDown;
c = InputKeyRight;
break;
}
}
break;
case 1:
if(event.input.type == InputTypePress) {
speed_up();
}
break;
case 2:
if(event.input.type == InputTypePress)
tama_btn_state = BTN_STATE_PRESSED;
else if(event.input.type == InputTypeRelease)
tama_btn_state = BTN_STATE_RELEASED;
switch(sub_menu_buttons) {
case 0: // A+C
tamalib_set_button(BTN_LEFT, tama_btn_state);
tamalib_set_button(BTN_RIGHT, tama_btn_state);
break;
case 1: // A
tamalib_set_button(BTN_LEFT, tama_btn_state);
break;
case 2: // B
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
break;
case 3: // C
tamalib_set_button(BTN_RIGHT, tama_btn_state);
break;
default:
break;
}
break;
case menu_items - 1:
default:
if(event.input.type == InputTypePress) {
switch(sub_menu_last) {
case 0: // close menu
in_menu = false;
break;
case 1: // Save
if(speed != 1) {
uint8_t temp = speed;
speed = 1;
tamalib_set_speed(speed);
furi_timer_stop(timer);
tama_p1_save_state();
furi_timer_start(
timer, furi_kernel_get_tick_frequency() / 30);
speed = temp;
tamalib_set_speed(speed);
} else {
furi_timer_stop(timer);
tama_p1_save_state();
furi_timer_start(
timer, furi_kernel_get_tick_frequency() / 30);
}
break;
case 2: // Save & Exit
if(speed != 1) {
speed = 1;
tamalib_set_speed(speed);
}
furi_timer_stop(timer);
tama_p1_save_state();
running = false;
break;
default:
break;
}
}
break;
}
}
} else { // out of menu // TODO: clean up code -.-
if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) {
if(speed != 1) {
speed = 1;
tamalib_set_speed(speed);
}
furi_timer_stop(timer);
tama_p1_save_state();
running = false;
} else if(
event.input.type == InputTypePress ||
event.input.type == InputTypeRelease) {
if(event.input.key != InputKeyBack && event.input.key != m) {
if(event.input.type == InputTypePress)
tama_btn_state = BTN_STATE_PRESSED;
else if(event.input.type == InputTypeRelease)
tama_btn_state = BTN_STATE_RELEASED;
} else {
tama_btn_state = BTN_STATE_RELEASED;
}
if(event.input.key == m && event.input.type == InputTypePress) {
in_menu = true;
} else if(event.input.key == a) {
tamalib_set_button(BTN_LEFT, tama_btn_state);
} else if(event.input.key == b) {
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
} else if(event.input.key == c) {
tamalib_set_button(BTN_RIGHT, tama_btn_state);
} else if(event.input.key == InputKeyOk) {
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
}
}
}
if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) {
furi_timer_stop(timer);
running = false;
tama_p1_save_state();
}
}
furi_mutex_release(g_state_mutex);
} else {
// Timeout
// FURI_LOG_D(TAG, "Timed out");
}
// else {
// // Timeout
// FURI_LOG_D(TAG, "Timed out");
// }
}
if(ctx->rom != NULL) {
furi_thread_flags_set(furi_thread_get_id(ctx->thread), 1);
furi_thread_join(ctx->thread);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
@@ -520,6 +1182,5 @@ int32_t tama_p1_app(void* p) {
furi_mutex_free(g_state_mutex);
tama_p1_deinit(ctx);
free(ctx);
return 0;
}

View File

@@ -105,6 +105,7 @@ typedef struct {
uint8_t sent;
PlayerState* p1;
PlayerState* p2;
FuriMutex* mutex;
} TanksState;
typedef enum {
@@ -388,10 +389,9 @@ void tanks_game_deserialize_and_render(unsigned char* data, Canvas* const canvas
}
static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
const TanksState* tanks_state = acquire_mutex((ValueMutex*)ctx, 25);
if(tanks_state == NULL) {
return;
}
furi_assert(ctx);
const TanksState* tanks_state = ctx;
furi_mutex_acquire(tanks_state->mutex, FuriWaitForever);
// Before the function is called, the state is set with the canvas_reset(canvas)
if(tanks_state->state == GameStateMenu) {
@@ -415,7 +415,7 @@ static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_frame(canvas, 0, 0, 128, 64);
release_mutex((ValueMutex*)ctx, tanks_state);
furi_mutex_release(tanks_state->mutex);
return;
}
@@ -432,7 +432,7 @@ static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
tanks_game_render_constant_cells(canvas);
release_mutex((ValueMutex*)ctx, tanks_state);
furi_mutex_release(tanks_state->mutex);
return;
}
@@ -615,7 +615,7 @@ static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
tanks_game_deserialize_and_render(data, canvas);
// TEST enf
release_mutex((ValueMutex*)ctx, tanks_state);
furi_mutex_release(tanks_state->mutex);
}
static void tanks_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
@@ -1188,8 +1188,8 @@ int32_t tanks_game_app(void* p) {
tanks_state->state = GameStateMenu;
tanks_state->menu_state = MenuStateSingleMode;
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, tanks_state, sizeof(TanksState))) {
tanks_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!tanks_state->mutex) {
FURI_LOG_E("Tanks", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(tanks_state);
@@ -1197,7 +1197,7 @@ int32_t tanks_game_app(void* p) {
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, tanks_game_render_callback, &state_mutex);
view_port_draw_callback_set(view_port, tanks_game_render_callback, tanks_state);
view_port_input_callback_set(view_port, tanks_game_input_callback, event_queue);
FuriTimer* timer =
@@ -1221,7 +1221,7 @@ int32_t tanks_game_app(void* p) {
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
TanksState* tanks_state = (TanksState*)acquire_mutex_block(&state_mutex);
furi_mutex_acquire(tanks_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
@@ -1422,7 +1422,7 @@ int32_t tanks_game_app(void* p) {
}
view_port_update(view_port);
release_mutex(&state_mutex, tanks_state);
furi_mutex_release(tanks_state->mutex);
furi_delay_ms(1);
}
@@ -1440,7 +1440,7 @@ int32_t tanks_game_app(void* p) {
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
furi_mutex_free(tanks_state->mutex);
if(tanks_state->p1 != NULL) {
free(tanks_state->p1);

View File

@@ -5,6 +5,12 @@
english version [below](#eng)
Blog: [theageoflove.ru](https://theageoflove.ru)
TG: [t.me/scuko_bled](https://t.me/scuko_bled)
![zeitraffer for flipper zero](https://theageoflove.ru/uploads/2022/11/photo_2022-11-10_15-54-25.jpg)
Видео работы: https://youtu.be/VPSpRLJXYAc

View File

@@ -1,6 +1,6 @@
App(
appid="GPIO_Intervalometer",
name="[GPIO] Intervalometer",
appid="GPIO_Timelapse",
name="[GPIO] Timelapse",
apptype=FlipperAppType.EXTERNAL,
entry_point="zeitraffer_app",
cdefines=["APP_ZEITRAFFER"],

View File

@@ -5,10 +5,10 @@
#include <notification/notification_messages.h>
#include <flipper_format/flipper_format.h>
#include "gpio_item.h"
#include "GPIO_Intervalometer_icons.h"
#include "GPIO_Timelapse_icons.h"
#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/intravelometer"
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/intravelometer.conf"
#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/timelapse"
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/timelapse.conf"
// Часть кода покрадена из https://github.com/zmactep/flipperzero-hello-world
@@ -390,7 +390,7 @@ int32_t zeitraffer_app(void* p) {
}
if(!flipper_format_write_comment_cstr(
save,
"Zeitraffer app settings: of frames, interval time, backlight type, Delay")) {
"Zeitraffer app settings: n of frames, interval time, backlight type, Delay")) {
notification_message(notifications, &sequence_error);
break;
}

View File

@@ -1,8 +1,5 @@
# Changelog
## 0.5
- Fix compatibility with Flipper Zero firmware 0.74.2
## 0.4
- Show active/inactive state in primary font (bold)

View File

@@ -1 +1 @@
#define VERSION "0.5"
#define VERSION "0.4"

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 BlueChip
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
applications/plugins/wii_ec_anal/info.sh Normal file → Executable file
View File

View File

@@ -94,10 +94,9 @@ static void cbDraw(Canvas* const canvas, void* ctx) {
furi_assert(canvas);
furi_assert(ctx);
state_t* state = NULL;
// Try to acquire the mutex for the plugin state variables, timeout = 25mS
if(!(state = (state_t*)acquire_mutex((ValueMutex*)ctx, 25))) return;
state_t* state = ctx;
furi_mutex_acquire(state->mutex, FuriWaitForever);
switch(state->scene) {
//---------------------------------------------------------------------
@@ -182,7 +181,7 @@ static void cbDraw(Canvas* const canvas, void* ctx) {
}
// Release the mutex
release_mutex((ValueMutex*)ctx, state);
furi_mutex_release(state->mutex);
LEAVE;
return;
@@ -308,7 +307,6 @@ int32_t wii_ec_anal(void) {
Gui* gui = NULL;
ViewPort* vpp = NULL;
state_t* state = NULL;
ValueMutex mutex = {0};
FuriMessageQueue* queue = NULL;
const uint32_t queueSz = 20; // maximum messages in queue
uint32_t tmo = (3.5f * 1000); // timeout splash screen after N seconds
@@ -346,7 +344,8 @@ int32_t wii_ec_anal(void) {
goto bail;
}
// 5. Create a mutex for (reading/writing) the plugin state variables
if(!init_mutex(&mutex, state, sizeof(state))) {
state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!state->mutex) {
ERROR(wii_errs[(error = ERR_NO_MUTEX)]);
goto bail;
}
@@ -360,7 +359,7 @@ int32_t wii_ec_anal(void) {
// 7a. Register a callback for input events
view_port_input_callback_set(vpp, cbInput, queue);
// 7b. Register a callback for draw events
view_port_draw_callback_set(vpp, cbDraw, &mutex);
view_port_draw_callback_set(vpp, cbDraw, state);
// ===== Start GUI Interface =====
// 8. Attach the viewport to the GUI
@@ -435,10 +434,7 @@ int32_t wii_ec_anal(void) {
// Read successful
// *** Try to lock the plugin state variables ***
if(!(state = (state_t*)acquire_mutex_block(&mutex))) {
ERROR(wii_errs[(error = ERR_MUTEX_BLOCK)]);
goto bail;
}
furi_mutex_acquire(state->mutex, FuriWaitForever);
// *** Handle events ***
switch(msg.id) {
@@ -476,10 +472,7 @@ int32_t wii_ec_anal(void) {
if(redraw) view_port_update(vpp);
// *** Try to release the plugin state variables ***
if(!release_mutex(&mutex, state)) {
ERROR(wii_errs[(error = ERR_MUTEX_RELEASE)]);
goto bail;
}
furi_mutex_release(state->mutex);
} while(state->run);
// ===== Game Over =====
@@ -514,10 +507,7 @@ bail:
}
// 5. Free the mutex
if(mutex.mutex) {
delete_mutex(&mutex);
mutex.mutex = NULL;
}
furi_mutex_free(state->mutex);
// 4. Free up state pointer(s)
// none

View File

@@ -56,6 +56,7 @@ typedef struct eventMsg {
// Access to this memory is controlled by mutex
//
typedef struct state {
FuriMutex* mutex;
bool run; // true : plugin is running
bool timerEn; // controller scanning enabled