Просмотр исходного кода

Initial Implementation PoC

TODO:
- Implement Sony IR Trigger
- Implement config menu
- Add icon
- Make pretty
nitepone 2 лет назад
Сommit
3525cacf39
4 измененных файлов с 338 добавлено и 0 удалено
  1. 1 0
      README.md
  2. 9 0
      application.fam
  3. 322 0
      intervalometer.c
  4. 6 0
      intervalometer.h

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# An Intervalometer App For Flipper Zero

+ 9 - 0
application.fam

@@ -0,0 +1,9 @@
+App(
+    appid="intervalometer",
+    name="Intervalometer",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="flipvalo_app",
+    requires=["gui"],
+    stack_size=2 * 1024,
+    order=20,
+)

+ 322 - 0
intervalometer.c

@@ -0,0 +1,322 @@
+// An intervalometer application written for the Flipper Zero
+//
+// author: nitepone <sierra>
+
+#include "intervalometer.h"
+
+#include "gui/scene_manager.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 <input/input.h>
+
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+
+// app ui scenes
+enum flipvalo_ui_scene {
+    FVSceneMain,
+    FVSceneConfig,
+};
+
+// 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"
+
+    // camera control functions.
+    // a bit overkill atm, but this will allow us to drop in support
+    // for other cameras, bluetooth, and more adv. functions later.
+    int (*send_trigger_fn)(void* output_config);
+    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
+};
+
+// private data of app
+struct flipvalo_priv {
+    struct flipvalo_config config;
+    struct flipvalo_run_state run_state;
+    enum flipvalo_ui_scene ui_scene;
+    FuriTimer* timer;
+    NotificationApp* notifications;
+    FuriMutex* mutex;
+};
+
+enum event_type {
+    EventTypeTick,
+    EventTypeKey,
+};
+
+struct plugin_event {
+    enum event_type type;
+    InputEvent input;
+};
+
+static void flipvalo_run_state_init(struct flipvalo_run_state* fv_run_state) {
+    fv_run_state->burst_cur = 0;
+    fv_run_state->shot_cur = 0;
+    fv_run_state->tick_next = 0;
+    fv_run_state->state = FVDone;
+    fv_run_state->tick_next = 0;
+}
+
+static int sony_ir_trigger(void* ctx) {
+    UNUSED(ctx);
+    return 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
+        conf->send_trigger_fn(conf->output_config);
+        run->shot_cur++;
+        run->burst_cur++;
+        // end of burst, prepare next shot
+        if (run->burst_cur >= conf->burst_count) {
+            run->state = FVWaitContShot;
+            run->tick_next = run->tick_cur + ((conf->interval_delay_msec * conf->tickrate) / 1000);
+        }
+        else /*continue burst */ {
+            run->state = FVWaitBurst;
+            run->tick_next = run->tick_cur + ((conf->burst_delay_msec * conf->tickrate) / 1000);
+        }
+    }
+}
+
+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);
+
+    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 ");
+        }
+    }
+
+    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 = 0;
+    fv_conf->interval_delay_msec = 5000;
+    fv_conf->shot_count = 10;
+    fv_conf->burst_count = 1;
+    fv_conf->burst_delay_msec = 0;
+    fv_conf->tickrate = 60;
+    fv_conf->send_trigger_fn = sony_ir_trigger;
+    fv_conf->output_config = NULL;
+}
+
+static void flipvalo_priv_init(struct flipvalo_priv* fv_priv) {
+    flipvalo_config_init(&(fv_priv->config));
+    flipvalo_run_state_init(&(fv_priv->run_state));
+    fv_priv->timer = NULL;
+    fv_priv->notifications = NULL;
+    fv_priv->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+}
+
+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 (event.type == EventTypeKey) {
+            if (event.input.type == InputTypeShort) {
+                switch (event.input.key) {
+                    case InputKeyUp:
+                        break;
+                    case InputKeyDown:
+                        break;
+                    case InputKeyLeft:
+                        break;
+                    case InputKeyRight:
+                        fv_priv->config.send_trigger_fn(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:
+                        // Exit the app
+                        goto cleanup;
+                }
+            }
+        }
+
+next_event:
+        furi_mutex_release(fv_priv->mutex);
+    }
+
+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;
+}
+

+ 6 - 0
intervalometer.h

@@ -0,0 +1,6 @@
+#ifndef INTERVALOMETER_H
+#define INTERVALOMETER_H
+
+
+
+#endif /* !INTERVALOMETER_H */