|
|
@@ -0,0 +1,736 @@
|
|
|
+// An intervalometer application written for the Flipper Zero
|
|
|
+//
|
|
|
+// author: nitepone <sierra>
|
|
|
+
|
|
|
+#include "intervalometer.h"
|
|
|
+#include <stdlib.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <furi.h>
|
|
|
+#include <furi_hal.h>
|
|
|
+#include <core/string.h>
|
|
|
+#include <gui/gui.h>
|
|
|
+#include <gui/elements.h>
|
|
|
+#include <gui/icon.h>
|
|
|
+#include <infrared_transmit.h>
|
|
|
+
|
|
|
+#include <input/input.h>
|
|
|
+
|
|
|
+#include <notification/notification.h>
|
|
|
+#include <notification/notification_messages.h>
|
|
|
+
|
|
|
+#include <ir_intervalometer_icons.h>
|
|
|
+
|
|
|
+// app ui scenes
|
|
|
+enum flipvalo_ui_scene {
|
|
|
+ FVSceneMain,
|
|
|
+ FVSceneConfig,
|
|
|
+};
|
|
|
+
|
|
|
+// defines a flipvalo camera trigger
|
|
|
+struct flipvalo_trigger {
|
|
|
+ const char* display_name;
|
|
|
+ int (*send)(void* output_config);
|
|
|
+};
|
|
|
+
|
|
|
+enum flipvalo_trigger_variants {
|
|
|
+ FvTrigMin = 0,
|
|
|
+ FvTrigSony = 0,
|
|
|
+ FvTrigCanon = 1,
|
|
|
+ FvTrigNikon = 2,
|
|
|
+ FvTrigMax = 2,
|
|
|
+};
|
|
|
+
|
|
|
+// run config for intervalometer
|
|
|
+struct flipvalo_config {
|
|
|
+ int init_delay_msec; // initial delay to start capture
|
|
|
+ int interval_delay_msec; // time between shots
|
|
|
+ int shot_count; // total shots in run
|
|
|
+ int burst_count; // number of triggers in a shot
|
|
|
+ int burst_delay_msec; // time between triggers in a shot
|
|
|
+ int tickrate; // tick rate in "ticks per second"
|
|
|
+ enum flipvalo_trigger_variants trigger; // current trigger
|
|
|
+
|
|
|
+ void* output_config;
|
|
|
+};
|
|
|
+
|
|
|
+// run time states for intervalometer
|
|
|
+enum flipvalo_state {
|
|
|
+ FVDone = 0, // done, 0 so it is default if state struct is zeroed
|
|
|
+ FVWaitInitShot, // waiting for first shot
|
|
|
+ FVWaitContShot, // waiting between "bursts" or "shots"
|
|
|
+ FVWaitBurst, // waiting between shots in a "burst"
|
|
|
+};
|
|
|
+
|
|
|
+// run time data for intervalometer
|
|
|
+// (this can be safely cleared between runs of the intervalometer)
|
|
|
+struct flipvalo_run_state {
|
|
|
+ enum flipvalo_state state; // current state of the run
|
|
|
+ int tick_cur; // current tick count
|
|
|
+ int tick_next; // tick when next action will occur
|
|
|
+ int shot_cur; // current shot
|
|
|
+ int burst_cur; // current trigger in a burst
|
|
|
+};
|
|
|
+
|
|
|
+enum flipvalo_config_edit_lines {
|
|
|
+ FvConfigEditInitDelay,
|
|
|
+ FvConfigEditMIN = FvConfigEditInitDelay,
|
|
|
+ FvConfigEditShotCount,
|
|
|
+ FvConfigEditShotDelay,
|
|
|
+ FvConfigEditBurstCount,
|
|
|
+ FvConfigEditBurstDelay,
|
|
|
+ FvConfigEditTrigger,
|
|
|
+ FvConfigEditMAX = FvConfigEditTrigger,
|
|
|
+};
|
|
|
+
|
|
|
+struct flipvalo_config_edit_view {
|
|
|
+ // the `config` that is under edit
|
|
|
+ struct flipvalo_config* config;
|
|
|
+ // the `cur_index` of the selection
|
|
|
+ // (e.g. editing the 3rd value of a number)
|
|
|
+ int cur_index;
|
|
|
+ // the `cur_line` of the selection
|
|
|
+ enum flipvalo_config_edit_lines cur_line;
|
|
|
+ // the current line that is at the top of the scrolled view
|
|
|
+ enum flipvalo_config_edit_lines scroll_pos;
|
|
|
+ // are we editing the selection?
|
|
|
+ // (this is really only needed for number fields)
|
|
|
+ bool edit_mode;
|
|
|
+};
|
|
|
+
|
|
|
+// private data of app
|
|
|
+struct flipvalo_priv {
|
|
|
+ struct flipvalo_config config;
|
|
|
+ struct flipvalo_config_edit_view config_edit_view;
|
|
|
+ struct flipvalo_run_state run_state;
|
|
|
+ enum flipvalo_ui_scene ui_scene;
|
|
|
+ int gui_shutter_blink;
|
|
|
+ FuriTimer* timer;
|
|
|
+ NotificationApp* notifications;
|
|
|
+ FuriMutex* mutex;
|
|
|
+};
|
|
|
+
|
|
|
+enum event_type {
|
|
|
+ EventTypeTick,
|
|
|
+ EventTypeKey,
|
|
|
+};
|
|
|
+
|
|
|
+struct plugin_event {
|
|
|
+ enum event_type type;
|
|
|
+ InputEvent input;
|
|
|
+};
|
|
|
+
|
|
|
+enum flipvalo_config_edit_line_type {
|
|
|
+ FvConfigEditTypeTimer,
|
|
|
+ FvConfigEditTypeCount,
|
|
|
+ FvConfigEditTypeEnum,
|
|
|
+};
|
|
|
+
|
|
|
+static void flipvalo_config_edit_view_init(struct flipvalo_config_edit_view* view) {
|
|
|
+ view->config = NULL;
|
|
|
+ view->cur_index = 0;
|
|
|
+ view->cur_line = 0;
|
|
|
+ view->scroll_pos = 0;
|
|
|
+ view->edit_mode = false;
|
|
|
+}
|
|
|
+
|
|
|
+static int sony_ir_trigger_send(void* ctx) {
|
|
|
+ UNUSED(ctx);
|
|
|
+ InfraredMessage message = {
|
|
|
+ .address = 0x1E3A,
|
|
|
+ .command = 0x2D,
|
|
|
+ .protocol = InfraredProtocolSIRC20,
|
|
|
+ };
|
|
|
+ infrared_send(&message, 1);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+uint32_t canon_ir_timings[] = {594, 7182, 593};
|
|
|
+static int canon_ir_trigger_send(void* ctx) {
|
|
|
+ UNUSED(ctx);
|
|
|
+ infrared_send_raw_ext(canon_ir_timings, 3, true, 38000, 0.33);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+uint32_t nikon_ir_timings[] =
|
|
|
+ {1945, 28253, 404, 1513, 410, 3611, 460, 70144, 1974, 28213, 455, 1493, 461, 3591, 409};
|
|
|
+static int nikon_ir_trigger_send(void* ctx) {
|
|
|
+ UNUSED(ctx);
|
|
|
+ infrared_send_raw_ext(nikon_ir_timings, 15, true, 38000, 0.33);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+struct flipvalo_trigger sony_ir_trigger = {.send = sony_ir_trigger_send, .display_name = "Sony IR"};
|
|
|
+
|
|
|
+struct flipvalo_trigger canon_ir_trigger = {
|
|
|
+ .send = canon_ir_trigger_send,
|
|
|
+ .display_name = "Canon IR"};
|
|
|
+
|
|
|
+struct flipvalo_trigger nikon_ir_trigger = {
|
|
|
+ .send = nikon_ir_trigger_send,
|
|
|
+ .display_name = "Nikon IR"};
|
|
|
+
|
|
|
+static struct flipvalo_trigger* flipvalo_get_trigger(enum flipvalo_trigger_variants variant) {
|
|
|
+ switch(variant) {
|
|
|
+ case FvTrigSony:
|
|
|
+ return &sony_ir_trigger;
|
|
|
+ case FvTrigCanon:
|
|
|
+ return &canon_ir_trigger;
|
|
|
+ case FvTrigNikon:
|
|
|
+ return &nikon_ir_trigger;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+#define ITEM_H 64 / 3
|
|
|
+#define ITEM_W 128
|
|
|
+#define VALUE_X 100
|
|
|
+#define VALUE_W 45
|
|
|
+static void flipvalo_config_edit_draw(Canvas* canvas, struct flipvalo_config_edit_view* view) {
|
|
|
+ int* line_value;
|
|
|
+ char* line_label = NULL;
|
|
|
+ const char* line_disp_str = "";
|
|
|
+ FuriString* temp_str = furi_string_alloc();
|
|
|
+ enum flipvalo_config_edit_line_type line_type;
|
|
|
+ enum flipvalo_config_edit_lines selected_line;
|
|
|
+
|
|
|
+ for(size_t line = 0; line < 3; line++) {
|
|
|
+ selected_line = view->scroll_pos + line;
|
|
|
+ switch(selected_line) {
|
|
|
+ case FvConfigEditInitDelay:
|
|
|
+ line_value = &view->config->init_delay_msec;
|
|
|
+ line_type = FvConfigEditTypeTimer;
|
|
|
+ line_label = "Init Time";
|
|
|
+ break;
|
|
|
+ case FvConfigEditShotDelay:
|
|
|
+ line_value = &view->config->interval_delay_msec;
|
|
|
+ line_type = FvConfigEditTypeTimer;
|
|
|
+ line_label = "Seq Time";
|
|
|
+ break;
|
|
|
+ case FvConfigEditShotCount:
|
|
|
+ line_value = &view->config->shot_count;
|
|
|
+ line_type = FvConfigEditTypeCount;
|
|
|
+ line_label = "Seq Count";
|
|
|
+ break;
|
|
|
+ case FvConfigEditBurstDelay:
|
|
|
+ line_value = &view->config->burst_delay_msec;
|
|
|
+ line_type = FvConfigEditTypeTimer;
|
|
|
+ line_label = "Brst Time";
|
|
|
+ break;
|
|
|
+ case FvConfigEditBurstCount:
|
|
|
+ line_value = &view->config->burst_count;
|
|
|
+ line_type = FvConfigEditTypeCount;
|
|
|
+ line_label = "Brst Count";
|
|
|
+ break;
|
|
|
+ case FvConfigEditTrigger:
|
|
|
+ line_value = NULL;
|
|
|
+ line_type = FvConfigEditTypeEnum;
|
|
|
+ line_label = "Trig Type";
|
|
|
+ line_disp_str = flipvalo_get_trigger(view->config->trigger)->display_name;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ continue;
|
|
|
+ };
|
|
|
+
|
|
|
+ canvas_set_color(canvas, ColorBlack);
|
|
|
+ if((selected_line) == view->cur_line) {
|
|
|
+ elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1);
|
|
|
+ canvas_set_color(canvas, ColorWhite);
|
|
|
+ }
|
|
|
+
|
|
|
+ uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2;
|
|
|
+
|
|
|
+ canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label);
|
|
|
+
|
|
|
+ switch(line_type) {
|
|
|
+ case FvConfigEditTypeTimer:
|
|
|
+ furi_string_printf(
|
|
|
+ temp_str,
|
|
|
+ "%02d:%02d:%02d:%03d",
|
|
|
+ *line_value / 3600000,
|
|
|
+ (*line_value / 60000) % 60,
|
|
|
+ (*line_value / 1000) % 60,
|
|
|
+ *line_value % 1000);
|
|
|
+ canvas_set_font(canvas, FontKeyboard);
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, 124, text_y, AlignRight, AlignCenter, furi_string_get_cstr(temp_str));
|
|
|
+ canvas_set_font(canvas, FontSecondary);
|
|
|
+ if(view->edit_mode && view->cur_line == selected_line) {
|
|
|
+ switch(view->cur_index) {
|
|
|
+ case 0:
|
|
|
+ canvas_draw_icon(canvas, 117, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 117, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ canvas_draw_icon(canvas, 112, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 112, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ canvas_draw_icon(canvas, 107, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 107, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ canvas_draw_icon(canvas, 93, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 93, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ canvas_draw_icon(canvas, 89, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 89, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ canvas_draw_icon(canvas, 75, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 75, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ canvas_draw_icon(canvas, 71, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 71, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ canvas_draw_icon(canvas, 57, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 57, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ canvas_draw_icon(canvas, 53, text_y - 9, &I_ArrowUp_3x5);
|
|
|
+ canvas_draw_icon(canvas, 53, text_y + 5, &I_ArrowDown_3x5);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case FvConfigEditTypeCount:
|
|
|
+ furi_string_printf(temp_str, "%d", *line_value);
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, VALUE_X, text_y, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str));
|
|
|
+ // TODO(luna) 0 values are actually more special for shot count and burst count.
|
|
|
+ // former being infinite, latter being uh.. nothing? not allowed?.. review this logic later.
|
|
|
+ if(*line_value > 0) {
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
|
|
|
+ }
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
|
|
|
+ break;
|
|
|
+ case FvConfigEditTypeEnum:
|
|
|
+ furi_string_printf(temp_str, "%s", line_disp_str);
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, VALUE_X, text_y, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str));
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ furi_string_free(temp_str);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+ flipvalo_config_edit_input_move_cursor(struct flipvalo_config_edit_view* view, int dx, int dy) {
|
|
|
+ enum flipvalo_config_edit_lines new_line = 0;
|
|
|
+
|
|
|
+ int* line_value = NULL;
|
|
|
+ enum flipvalo_config_edit_line_type line_type;
|
|
|
+ // only used for enum type
|
|
|
+ int max_value;
|
|
|
+ int min_value;
|
|
|
+
|
|
|
+ switch(view->cur_line) {
|
|
|
+ case FvConfigEditInitDelay:
|
|
|
+ line_value = &view->config->init_delay_msec;
|
|
|
+ line_type = FvConfigEditTypeTimer;
|
|
|
+ break;
|
|
|
+ case FvConfigEditShotDelay:
|
|
|
+ line_value = &view->config->interval_delay_msec;
|
|
|
+ line_type = FvConfigEditTypeTimer;
|
|
|
+ break;
|
|
|
+ case FvConfigEditShotCount:
|
|
|
+ line_value = &view->config->shot_count;
|
|
|
+ line_type = FvConfigEditTypeCount;
|
|
|
+ break;
|
|
|
+ case FvConfigEditBurstDelay:
|
|
|
+ line_value = &view->config->burst_delay_msec;
|
|
|
+ line_type = FvConfigEditTypeTimer;
|
|
|
+ break;
|
|
|
+ case FvConfigEditBurstCount:
|
|
|
+ line_value = &view->config->burst_count;
|
|
|
+ line_type = FvConfigEditTypeCount;
|
|
|
+ break;
|
|
|
+ case FvConfigEditTrigger:
|
|
|
+ line_value = (int*)(&view->config->trigger);
|
|
|
+ line_type = FvConfigEditTypeEnum;
|
|
|
+ min_value = FvTrigMin;
|
|
|
+ max_value = FvTrigMax;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return;
|
|
|
+ };
|
|
|
+
|
|
|
+ if(!view->edit_mode) {
|
|
|
+ // Do `dy` behaviors
|
|
|
+ new_line = view->cur_line + dy;
|
|
|
+ if(new_line > FvConfigEditMAX) {
|
|
|
+ // Out of bound cursor. No-op.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ view->cur_line = new_line;
|
|
|
+
|
|
|
+ // Handle moving scroll position.
|
|
|
+ if(new_line < view->scroll_pos) {
|
|
|
+ view->scroll_pos = new_line;
|
|
|
+ } else if(new_line >= (view->scroll_pos + 3)) {
|
|
|
+ view->scroll_pos += dy;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Do `dx` behavior
|
|
|
+ switch(line_type) {
|
|
|
+ case FvConfigEditTypeTimer:
|
|
|
+ // no-op unless edit mode
|
|
|
+ break;
|
|
|
+ case FvConfigEditTypeCount:
|
|
|
+ min_value = 0;
|
|
|
+ max_value = INT_MAX;
|
|
|
+ // fall through.
|
|
|
+ case FvConfigEditTypeEnum:
|
|
|
+ if((*line_value + dx) >= min_value && (*line_value + dx) <= max_value) {
|
|
|
+ *line_value += dx;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else /* edit mode */ {
|
|
|
+ switch(line_type) {
|
|
|
+ case FvConfigEditTypeCount:
|
|
|
+ case FvConfigEditTypeEnum:
|
|
|
+ // If current line does not edit mode.. why are we in edit mode?
|
|
|
+ // Reaching this would be a bug, so lets go back to normal mode.
|
|
|
+ view->edit_mode = false;
|
|
|
+ return;
|
|
|
+ case FvConfigEditTypeTimer:
|
|
|
+ switch(view->cur_index) {
|
|
|
+ case 0:
|
|
|
+ if(*line_value + (dy * -10) >= 0) {
|
|
|
+ *line_value += (dy * -10);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ if(*line_value + (dy * -1000) >= 0) {
|
|
|
+ *line_value += (dy * -1000);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ if(*line_value + (dy * -60000) >= 0) {
|
|
|
+ *line_value += (dy * -60000);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ if(*line_value + (dy * -3600000) >= 0) {
|
|
|
+ *line_value += (dy * -3600000);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ view->cur_index -= dx;
|
|
|
+ if(view->cur_index < 0) {
|
|
|
+ view->cur_index = 0;
|
|
|
+ }
|
|
|
+ if(view->cur_index > 3) {
|
|
|
+ view->cur_index = 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int flipvalo_config_edit_input(InputEvent* event, struct flipvalo_config_edit_view* view) {
|
|
|
+ // ignore all but short and repeats
|
|
|
+ if(!(event->type == InputTypeShort || event->type == InputTypeRepeat)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ switch(event->key) {
|
|
|
+ case InputKeyRight:
|
|
|
+ flipvalo_config_edit_input_move_cursor(view, 1, 0);
|
|
|
+ break;
|
|
|
+ case InputKeyLeft:
|
|
|
+ flipvalo_config_edit_input_move_cursor(view, -1, 0);
|
|
|
+ break;
|
|
|
+ case InputKeyUp:
|
|
|
+ flipvalo_config_edit_input_move_cursor(view, 0, -1);
|
|
|
+ break;
|
|
|
+ case InputKeyDown:
|
|
|
+ flipvalo_config_edit_input_move_cursor(view, 0, 1);
|
|
|
+ break;
|
|
|
+ case InputKeyOk:
|
|
|
+ //TODO(luna) Check if line supports edit mode before doing this.
|
|
|
+ view->edit_mode = !view->edit_mode;
|
|
|
+ break;
|
|
|
+ case InputKeyBack:
|
|
|
+ if(view->edit_mode) {
|
|
|
+ view->edit_mode = false;
|
|
|
+ } else {
|
|
|
+ // exit config edit view
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+// XXX(luna) back to app
|
|
|
+
|
|
|
+static void flipvalo_run_state_init(struct flipvalo_run_state* fv_run_state) {
|
|
|
+ fv_run_state->burst_cur = 1;
|
|
|
+ fv_run_state->shot_cur = 1;
|
|
|
+ fv_run_state->tick_next = 0;
|
|
|
+ fv_run_state->state = FVDone;
|
|
|
+ fv_run_state->tick_next = 0;
|
|
|
+ fv_run_state->tick_cur = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
|
+ furi_assert(event_queue);
|
|
|
+ struct plugin_event event = {.type = EventTypeKey, .input = *input_event};
|
|
|
+ furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
|
+}
|
|
|
+
|
|
|
+static inline bool flipvalo_intv_running(struct flipvalo_priv* fv_priv) {
|
|
|
+ return fv_priv->run_state.state != FVDone;
|
|
|
+}
|
|
|
+
|
|
|
+static void flipvalo_intv_tick(struct flipvalo_priv* fv_priv) {
|
|
|
+ struct flipvalo_config* conf = &fv_priv->config;
|
|
|
+ struct flipvalo_run_state* run = &fv_priv->run_state;
|
|
|
+ // check if action required
|
|
|
+ if(run->tick_cur++ >= run->tick_next) {
|
|
|
+ // call trigger function
|
|
|
+ flipvalo_get_trigger(conf->trigger)->send(conf->output_config);
|
|
|
+ fv_priv->gui_shutter_blink = 3;
|
|
|
+ // end of burst, prepare next shot
|
|
|
+ if(run->burst_cur >= conf->burst_count) {
|
|
|
+ run->burst_cur = 1;
|
|
|
+ run->shot_cur++;
|
|
|
+ run->state = FVWaitContShot;
|
|
|
+ run->tick_next = run->tick_cur + ((conf->interval_delay_msec * conf->tickrate) / 1000);
|
|
|
+ } else /*continue burst */ {
|
|
|
+ run->burst_cur++;
|
|
|
+ run->state = FVWaitBurst;
|
|
|
+ run->tick_next = run->tick_cur + ((conf->burst_delay_msec * conf->tickrate) / 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(run->shot_cur > conf->shot_count) {
|
|
|
+ run->state = FVDone;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void flipvalo_intv_stop(struct flipvalo_priv* fv_priv) {
|
|
|
+ fv_priv->run_state.state = FVDone;
|
|
|
+}
|
|
|
+
|
|
|
+static void flipvalo_intv_start(struct flipvalo_priv* fv_priv) {
|
|
|
+ // clear struct
|
|
|
+ furi_assert(fv_priv);
|
|
|
+ flipvalo_run_state_init(&fv_priv->run_state);
|
|
|
+ fv_priv->run_state.state = FVWaitInitShot;
|
|
|
+ fv_priv->run_state.tick_next =
|
|
|
+ ((fv_priv->config.init_delay_msec * fv_priv->config.tickrate) / 1000);
|
|
|
+}
|
|
|
+
|
|
|
+static void timer_callback(void* ctx) {
|
|
|
+ furi_assert(ctx);
|
|
|
+ struct flipvalo_priv* fv_priv = ctx;
|
|
|
+ furi_mutex_acquire(fv_priv->mutex, FuriWaitForever);
|
|
|
+ if(flipvalo_intv_running(fv_priv)) {
|
|
|
+ flipvalo_intv_tick(fv_priv);
|
|
|
+ }
|
|
|
+ furi_mutex_release(fv_priv->mutex);
|
|
|
+}
|
|
|
+
|
|
|
+static void render_callback(Canvas* const canvas, void* ctx) {
|
|
|
+ furi_assert(ctx);
|
|
|
+ struct flipvalo_priv* fv_priv = ctx;
|
|
|
+ FuriString* temp_str = furi_string_alloc();
|
|
|
+ furi_mutex_acquire(fv_priv->mutex, FuriWaitForever);
|
|
|
+
|
|
|
+ // invert screen if blinking
|
|
|
+ if(fv_priv->gui_shutter_blink > 0) {
|
|
|
+ fv_priv->gui_shutter_blink--;
|
|
|
+ canvas_draw_box(canvas, 0, 0, 127, 63);
|
|
|
+ canvas_set_color(canvas, ColorWhite);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(fv_priv->ui_scene == FVSceneMain) {
|
|
|
+ int countdown_msec =
|
|
|
+ (1000 * (fv_priv->run_state.tick_next - fv_priv->run_state.tick_cur)) /
|
|
|
+ fv_priv->config.tickrate;
|
|
|
+ int elapsed_msec = (1000 * fv_priv->run_state.tick_cur) / fv_priv->config.tickrate;
|
|
|
+
|
|
|
+ canvas_draw_frame(canvas, 0, 0, 128, 64);
|
|
|
+
|
|
|
+ // draw countdown
|
|
|
+ canvas_set_font(canvas, FontPrimary);
|
|
|
+ furi_string_printf(
|
|
|
+ temp_str,
|
|
|
+ "%02d:%02d:%02d:%03d",
|
|
|
+ countdown_msec / 3600000,
|
|
|
+ (countdown_msec / 60000) % 60,
|
|
|
+ (countdown_msec / 1000) % 60,
|
|
|
+ countdown_msec % 1000);
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, 64, 24, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str));
|
|
|
+
|
|
|
+ // draw top and bottom status bars
|
|
|
+ canvas_set_font(canvas, FontSecondary);
|
|
|
+ furi_string_printf(
|
|
|
+ temp_str,
|
|
|
+ "%02d:%02d:%02d",
|
|
|
+ elapsed_msec / 3600000,
|
|
|
+ (elapsed_msec / 60000) % 60,
|
|
|
+ (elapsed_msec / 1000) % 60);
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, 4, 8, AlignLeft, AlignCenter, furi_string_get_cstr(temp_str));
|
|
|
+ furi_string_printf(temp_str, "Shot: %d", fv_priv->run_state.shot_cur);
|
|
|
+ canvas_draw_str_aligned(
|
|
|
+ canvas, 124, 8, AlignRight, AlignCenter, furi_string_get_cstr(temp_str));
|
|
|
+ elements_button_left(canvas, "Cfg");
|
|
|
+ elements_button_right(canvas, "Snap");
|
|
|
+ if(fv_priv->run_state.state == FVDone) {
|
|
|
+ elements_button_center(canvas, "Start");
|
|
|
+ } else {
|
|
|
+ elements_button_center(canvas, "Stop ");
|
|
|
+ }
|
|
|
+ } else if(fv_priv->ui_scene == FVSceneConfig) {
|
|
|
+ flipvalo_config_edit_draw(canvas, &fv_priv->config_edit_view);
|
|
|
+ }
|
|
|
+
|
|
|
+ furi_string_free(temp_str);
|
|
|
+ furi_mutex_release(fv_priv->mutex);
|
|
|
+}
|
|
|
+
|
|
|
+static void flipvalo_config_init(struct flipvalo_config* fv_conf) {
|
|
|
+ fv_conf->init_delay_msec = 2000;
|
|
|
+ fv_conf->interval_delay_msec = 0;
|
|
|
+ fv_conf->shot_count = 1;
|
|
|
+ fv_conf->burst_count = 1;
|
|
|
+ fv_conf->burst_delay_msec = 0;
|
|
|
+ fv_conf->tickrate = 125;
|
|
|
+ fv_conf->trigger = FvTrigSony;
|
|
|
+ fv_conf->output_config = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static void flipvalo_priv_init(struct flipvalo_priv* fv_priv) {
|
|
|
+ flipvalo_config_init(&fv_priv->config);
|
|
|
+ flipvalo_config_edit_view_init(&fv_priv->config_edit_view);
|
|
|
+ flipvalo_run_state_init(&fv_priv->run_state);
|
|
|
+ fv_priv->gui_shutter_blink = 0;
|
|
|
+ fv_priv->timer = NULL;
|
|
|
+ fv_priv->notifications = NULL;
|
|
|
+ fv_priv->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
|
+ fv_priv->gui_shutter_blink = 0;
|
|
|
+}
|
|
|
+
|
|
|
+int32_t flipvalo_app() {
|
|
|
+ int ret = 0;
|
|
|
+ ViewPort* view_port = NULL;
|
|
|
+ Gui* gui = NULL;
|
|
|
+ FuriStatus event_status = {0};
|
|
|
+ struct plugin_event event = {0};
|
|
|
+
|
|
|
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(struct plugin_event));
|
|
|
+ struct flipvalo_priv* fv_priv = malloc(sizeof(*fv_priv));
|
|
|
+
|
|
|
+ flipvalo_priv_init(fv_priv);
|
|
|
+
|
|
|
+ if(!fv_priv->mutex) {
|
|
|
+ FURI_LOG_E("Flipvalo", "Cannot create mutex\r\n");
|
|
|
+ ret = 1;
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+
|
|
|
+ view_port = view_port_alloc();
|
|
|
+ view_port_draw_callback_set(view_port, render_callback, fv_priv);
|
|
|
+ view_port_input_callback_set(view_port, input_callback, event_queue);
|
|
|
+
|
|
|
+ fv_priv->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, fv_priv);
|
|
|
+ furi_timer_start(
|
|
|
+ fv_priv->timer, (uint32_t)furi_kernel_get_tick_frequency() / fv_priv->config.tickrate);
|
|
|
+ gui = furi_record_open("gui");
|
|
|
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
+
|
|
|
+ while(true) {
|
|
|
+ event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
|
+
|
|
|
+ furi_mutex_acquire(fv_priv->mutex, FuriWaitForever);
|
|
|
+
|
|
|
+ // catch event_status that is not Ok
|
|
|
+ if(event_status == FuriStatusErrorTimeout) {
|
|
|
+ // timeout, ignore
|
|
|
+ goto next_event;
|
|
|
+ } else if(event_status != FuriStatusOk) {
|
|
|
+ FURI_LOG_E("Flipvalo", "Event Queue Error: %d\r\n", event_status);
|
|
|
+ goto next_event;
|
|
|
+ // TODO(luna) evaluate if we should exit here.
|
|
|
+ //goto cleanup;
|
|
|
+ }
|
|
|
+
|
|
|
+ // handle input
|
|
|
+ if(/* long press back */
|
|
|
+ event.type == EventTypeKey && event.input.type == InputTypeLong &&
|
|
|
+ event.input.key == InputKeyBack) {
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+ switch(fv_priv->ui_scene) {
|
|
|
+ case FVSceneMain:
|
|
|
+ // TODO(luna) Maybe give this a function.. look howl clean FVSceneConfig is...
|
|
|
+ if(event.type == EventTypeKey) {
|
|
|
+ if(event.input.type == InputTypeShort || event.input.type == InputTypeLong) {
|
|
|
+ switch(event.input.key) {
|
|
|
+ case InputKeyUp:
|
|
|
+ break;
|
|
|
+ case InputKeyDown:
|
|
|
+ break;
|
|
|
+ case InputKeyLeft:
|
|
|
+ flipvalo_intv_stop(fv_priv);
|
|
|
+ fv_priv->config_edit_view.config = &fv_priv->config;
|
|
|
+ fv_priv->ui_scene = FVSceneConfig;
|
|
|
+ break;
|
|
|
+ case InputKeyRight:
|
|
|
+ fv_priv->gui_shutter_blink = 3;
|
|
|
+ flipvalo_get_trigger(fv_priv->config.trigger)
|
|
|
+ ->send(fv_priv->config.output_config);
|
|
|
+ break;
|
|
|
+ case InputKeyOk:
|
|
|
+ if(flipvalo_intv_running(fv_priv)) {
|
|
|
+ flipvalo_intv_stop(fv_priv);
|
|
|
+ } else {
|
|
|
+ flipvalo_intv_start(fv_priv);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case InputKeyMAX:
|
|
|
+ break;
|
|
|
+ case InputKeyBack:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case FVSceneConfig:
|
|
|
+ ret = flipvalo_config_edit_input(&event.input, &fv_priv->config_edit_view);
|
|
|
+ if(ret) {
|
|
|
+ fv_priv->ui_scene = FVSceneMain;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ next_event:
|
|
|
+ furi_mutex_release(fv_priv->mutex);
|
|
|
+
|
|
|
+ view_port_update(view_port);
|
|
|
+ }
|
|
|
+
|
|
|
+cleanup:
|
|
|
+ if(view_port) {
|
|
|
+ view_port_enabled_set(view_port, false);
|
|
|
+ if(gui) {
|
|
|
+ gui_remove_view_port(gui, view_port);
|
|
|
+ furi_record_close("gui");
|
|
|
+ }
|
|
|
+ view_port_free(view_port);
|
|
|
+ }
|
|
|
+ if(event_queue) {
|
|
|
+ furi_message_queue_free(event_queue);
|
|
|
+ }
|
|
|
+ if(fv_priv) {
|
|
|
+ furi_mutex_free(fv_priv->mutex);
|
|
|
+ furi_timer_free(fv_priv->timer);
|
|
|
+ }
|
|
|
+ free(fv_priv);
|
|
|
+ return ret;
|
|
|
+}
|