intervalometer.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. // An intervalometer application written for the Flipper Zero
  2. //
  3. // author: nitepone <sierra>
  4. #include "intervalometer.h"
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7. #include <furi.h>
  8. #include <furi_hal.h>
  9. #include <core/string.h>
  10. #include <gui/gui.h>
  11. #include <gui/elements.h>
  12. #include <infrared_transmit.h>
  13. #include <input/input.h>
  14. #include <notification/notification.h>
  15. #include <notification/notification_messages.h>
  16. // app ui scenes
  17. enum flipvalo_ui_scene {
  18. FVSceneMain,
  19. FVSceneConfig,
  20. };
  21. // run config for intervalometer
  22. struct flipvalo_config {
  23. int init_delay_msec; // initial delay to start capture
  24. int interval_delay_msec; // time between shots
  25. int shot_count; // total shots in run
  26. int burst_count; // number of triggers in a shot
  27. int burst_delay_msec; // time between triggers in a shot
  28. int tickrate; // tick rate in "ticks per second"
  29. // camera control functions.
  30. // a bit overkill atm, but this will allow us to drop in support
  31. // for other cameras, bluetooth, and more adv. functions later.
  32. int (*send_trigger_fn)(void* output_config);
  33. void* output_config;
  34. };
  35. // run time states for intervalometer
  36. enum flipvalo_state {
  37. FVDone = 0, // done, 0 so it is default if state struct is zeroed
  38. FVWaitInitShot, // waiting for first shot
  39. FVWaitContShot, // waiting between "bursts" or "shots"
  40. FVWaitBurst, // waiting between shots in a "burst"
  41. };
  42. // run time data for intervalometer
  43. // (this can be safely cleared between runs of the intervalometer)
  44. struct flipvalo_run_state {
  45. enum flipvalo_state state; // current state of the run
  46. int tick_cur; // current tick count
  47. int tick_next; // tick when next action will occur
  48. int shot_cur; // current shot
  49. int burst_cur; // current trigger in a burst
  50. };
  51. enum flipvalo_config_edit_lines {
  52. FvConfigEditInitDelay,
  53. FvConfigEditMIN = FvConfigEditInitDelay,
  54. FvConfigEditShotDelay,
  55. FvConfigEditShotCount,
  56. FvConfigEditBurstDelay,
  57. FvConfigEditBurstCount,
  58. FvConfigEditMAX = FvConfigEditBurstCount,
  59. };
  60. struct flipvalo_config_edit_view {
  61. // the `config` that is under edit
  62. struct flipvalo_config* config;
  63. // the `cur_index` of the selection
  64. // (e.g. editing the 3rd value of a number)
  65. int cur_index;
  66. // the `cur_line` of the selection
  67. enum flipvalo_config_edit_lines cur_line;
  68. // the current line that is at the top of the scrolled view
  69. enum flipvalo_config_edit_lines scroll_pos;
  70. // are we editing the selection?
  71. // (this is really only needed for number fields)
  72. bool edit_mode;
  73. };
  74. // private data of app
  75. struct flipvalo_priv {
  76. struct flipvalo_config config;
  77. struct flipvalo_config_edit_view config_edit_view;
  78. struct flipvalo_run_state run_state;
  79. enum flipvalo_ui_scene ui_scene;
  80. FuriTimer* timer;
  81. NotificationApp* notifications;
  82. FuriMutex* mutex;
  83. };
  84. enum event_type {
  85. EventTypeTick,
  86. EventTypeKey,
  87. };
  88. struct plugin_event {
  89. enum event_type type;
  90. InputEvent input;
  91. };
  92. // XXX(luna) settings experimental ui kludge
  93. enum flipvalo_config_edit_line_type {
  94. FvConfigEditTypeTimer,
  95. FvConfigEditTypeCount,
  96. };
  97. #define ITEM_H 64 / 3
  98. #define ITEM_W 128
  99. #define VALUE_X 100
  100. #define VALUE_W 100
  101. static void flipvalo_config_edit_draw(Canvas* canvas, struct flipvalo_config_edit_view* view) {
  102. int* line_value;
  103. char* line_label = NULL;
  104. FuriString* temp_str = furi_string_alloc();
  105. enum flipvalo_config_edit_line_type line_type;
  106. for (size_t line = 0; line < 3; line++) {
  107. switch (view->scroll_pos + line) {
  108. case FvConfigEditInitDelay:
  109. line_value = &view->config->init_delay_msec;
  110. line_type = FvConfigEditTypeTimer;
  111. line_label = "Init Timer";
  112. break;
  113. case FvConfigEditShotDelay:
  114. line_value = &view->config->interval_delay_msec;
  115. line_type = FvConfigEditTypeTimer;
  116. line_label = "Seq. Timer";
  117. break;
  118. case FvConfigEditShotCount:
  119. line_value = &view->config->shot_count;
  120. line_type = FvConfigEditTypeCount;
  121. line_label = "Seq. Count";
  122. break;
  123. case FvConfigEditBurstDelay:
  124. line_value = &view->config->burst_delay_msec;
  125. line_type = FvConfigEditTypeTimer;
  126. line_label = "Burst Timer";
  127. break;
  128. case FvConfigEditBurstCount:
  129. line_value = &view->config->burst_count;
  130. line_type = FvConfigEditTypeCount;
  131. line_label = "Burst Count";
  132. break;
  133. default:
  134. continue;
  135. };
  136. canvas_set_color(canvas, ColorBlack);
  137. if ((view->scroll_pos + line) == view->cur_line) {
  138. elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1);
  139. canvas_set_color(canvas, ColorWhite);
  140. }
  141. uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2;
  142. canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label);
  143. switch(line_type) {
  144. case FvConfigEditTypeTimer:
  145. furi_string_printf(
  146. temp_str, "%02d:%02d:%02d:%03d",
  147. *line_value / 3600000,
  148. (*line_value / 60000) % 60,
  149. (*line_value / 1000) % 60,
  150. *line_value % 1000);
  151. canvas_set_font(canvas, FontKeyboard);
  152. canvas_draw_str_aligned(
  153. canvas, 124, text_y, AlignRight, AlignCenter,
  154. furi_string_get_cstr(temp_str));
  155. canvas_set_font(canvas, FontSecondary);
  156. if (view->edit_mode) {
  157. //TODO(luna) review positioning.
  158. //uint8_t icon_x = (128) - ((7 - view->cur_index - 1) * 6);
  159. //canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_3x5);
  160. //canvas_draw_icon(canvas, icon_x, text_y + 5, &I_SmallArrowDown_3x5);
  161. }
  162. break;
  163. case FvConfigEditTypeCount:
  164. furi_string_printf(temp_str, "%d", *line_value);
  165. canvas_draw_str_aligned(
  166. canvas, VALUE_X, text_y, AlignCenter, AlignCenter,
  167. furi_string_get_cstr(temp_str));
  168. // TODO(luna) 0 values are actually more special for shot count and burst count.
  169. // former being infinite, latter being uh.. nothing? not allowed?.. review this logic later.
  170. if(*line_value > 0) {
  171. canvas_draw_str_aligned(
  172. canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
  173. }
  174. canvas_draw_str_aligned(
  175. canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
  176. break;
  177. }
  178. }
  179. furi_string_free(temp_str);
  180. }
  181. static void flipvalo_config_edit_input_move_cursor(struct flipvalo_config_edit_view* view, int dx, int dy) {
  182. enum flipvalo_config_edit_lines new_line = 0;
  183. int* line_value;
  184. enum flipvalo_config_edit_line_type line_type;
  185. switch (view->cur_line) {
  186. case FvConfigEditInitDelay:
  187. line_value = &view->config->init_delay_msec;
  188. line_type = FvConfigEditTypeTimer;
  189. break;
  190. case FvConfigEditShotDelay:
  191. line_value = &view->config->interval_delay_msec;
  192. line_type = FvConfigEditTypeTimer;
  193. break;
  194. case FvConfigEditShotCount:
  195. line_value = &view->config->shot_count;
  196. line_type = FvConfigEditTypeCount;
  197. break;
  198. case FvConfigEditBurstDelay:
  199. line_value = &view->config->burst_delay_msec;
  200. line_type = FvConfigEditTypeTimer;
  201. break;
  202. case FvConfigEditBurstCount:
  203. line_value = &view->config->burst_count;
  204. line_type = FvConfigEditTypeCount;
  205. break;
  206. default:
  207. return;
  208. };
  209. if (!view->edit_mode) {
  210. // Do `dy` behaviors
  211. new_line = view->cur_line + dy;
  212. if (new_line > FvConfigEditMAX) {
  213. // Out of bound cursor. No-op.
  214. return;
  215. }
  216. view->cur_line = new_line;
  217. // Handle moving scroll position.
  218. if (new_line < view->scroll_pos) {
  219. view->scroll_pos = new_line;
  220. }
  221. else if (new_line >= (view->scroll_pos + 3)) {
  222. view->scroll_pos+=dy;
  223. }
  224. // Do `dx` behavior
  225. switch (line_type) {
  226. case FvConfigEditTypeCount:
  227. if (*line_value + dx >= 0) {
  228. *line_value += dx;
  229. }
  230. break;
  231. case FvConfigEditTypeTimer:
  232. // no-op unless edit mode
  233. break;
  234. }
  235. }
  236. else /* edit mode */ {
  237. switch (line_type) {
  238. case FvConfigEditTypeCount:
  239. // If current line does not edit mode.. why are we in edit mode?
  240. // Reaching this would be a bug, so lets go back to normal mode.
  241. view->edit_mode = false;
  242. return;
  243. case FvConfigEditTypeTimer:
  244. // bro the math here is gonna suck.
  245. // I'm doing it WRONG for now x3
  246. // TODO(luna) REAL MATHS
  247. if (*line_value + (dy * -100) >= 0) {
  248. *line_value += (dy * -100);
  249. }
  250. view->cur_index += dx;
  251. if (view->cur_index < 0) {
  252. view->cur_index = 0;
  253. }
  254. if (view->cur_index > 10) {
  255. view->cur_index = 10;
  256. }
  257. break;
  258. }
  259. }
  260. }
  261. static int flipvalo_config_edit_input(InputEvent* event, struct flipvalo_config_edit_view* view) {
  262. // ignore all but short and repeats
  263. if (!(event->type == InputTypeShort || event->type == InputTypeRepeat)) {
  264. return 0;
  265. }
  266. switch (event->key) {
  267. case InputKeyRight:
  268. flipvalo_config_edit_input_move_cursor(view, 1, 0);
  269. break;
  270. case InputKeyLeft:
  271. flipvalo_config_edit_input_move_cursor(view, -1, 0);
  272. break;
  273. case InputKeyUp:
  274. flipvalo_config_edit_input_move_cursor(view, 0, -1);
  275. break;
  276. case InputKeyDown:
  277. flipvalo_config_edit_input_move_cursor(view, 0, 1);
  278. break;
  279. case InputKeyOk:
  280. view->edit_mode = !view->edit_mode;
  281. break;
  282. case InputKeyBack:
  283. if (view->edit_mode) {
  284. view->edit_mode = false;
  285. } else {
  286. // exit config edit view
  287. return 1;
  288. }
  289. default:
  290. break;
  291. }
  292. return 0;
  293. }
  294. // XXX(luna) back to app
  295. static void flipvalo_run_state_init(struct flipvalo_run_state* fv_run_state) {
  296. fv_run_state->burst_cur = 0;
  297. fv_run_state->shot_cur = 0;
  298. fv_run_state->tick_next = 0;
  299. fv_run_state->state = FVDone;
  300. fv_run_state->tick_next = 0;
  301. fv_run_state->tick_cur = 0;
  302. }
  303. static int sony_ir_trigger(void* ctx) {
  304. UNUSED(ctx);
  305. InfraredMessage message = {
  306. .address = 0x1E3A,
  307. .command = 0x2D,
  308. .protocol = InfraredProtocolSIRC20,
  309. };
  310. infrared_send(&message, 1);
  311. return 0;
  312. }
  313. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  314. furi_assert(event_queue);
  315. struct plugin_event event = {.type = EventTypeKey, .input = *input_event };
  316. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  317. }
  318. static inline bool flipvalo_intv_running(struct flipvalo_priv* fv_priv) {
  319. return fv_priv->run_state.state != FVDone;
  320. }
  321. static void flipvalo_intv_tick(struct flipvalo_priv* fv_priv) {
  322. struct flipvalo_config* conf = &fv_priv->config;
  323. struct flipvalo_run_state* run = &fv_priv->run_state;
  324. // check if action required
  325. if (run->tick_cur++ >= run->tick_next) {
  326. // call trigger function
  327. conf->send_trigger_fn(conf->output_config);
  328. run->shot_cur++;
  329. run->burst_cur++;
  330. // end of burst, prepare next shot
  331. if (run->burst_cur >= conf->burst_count) {
  332. run->state = FVWaitContShot;
  333. run->tick_next = run->tick_cur + ((conf->interval_delay_msec * conf->tickrate) / 1000);
  334. }
  335. else /*continue burst */ {
  336. run->state = FVWaitBurst;
  337. run->tick_next = run->tick_cur + ((conf->burst_delay_msec * conf->tickrate) / 1000);
  338. }
  339. }
  340. }
  341. static void flipvalo_intv_stop(struct flipvalo_priv* fv_priv) {
  342. fv_priv->run_state.state = FVDone;
  343. }
  344. static void flipvalo_intv_start(struct flipvalo_priv* fv_priv) {
  345. // clear struct
  346. furi_assert(fv_priv);
  347. flipvalo_run_state_init(&fv_priv->run_state);
  348. fv_priv->run_state.state = FVWaitInitShot;
  349. fv_priv->run_state.tick_next =
  350. ((fv_priv->config.init_delay_msec * fv_priv->config.tickrate) / 1000);
  351. }
  352. static void timer_callback(void* ctx) {
  353. furi_assert(ctx);
  354. struct flipvalo_priv* fv_priv = ctx;
  355. furi_mutex_acquire(fv_priv->mutex, FuriWaitForever);
  356. if (flipvalo_intv_running(fv_priv)) {
  357. flipvalo_intv_tick(fv_priv);
  358. }
  359. furi_mutex_release(fv_priv->mutex);
  360. }
  361. static void render_callback(Canvas* const canvas, void* ctx) {
  362. furi_assert(ctx);
  363. struct flipvalo_priv* fv_priv = ctx;
  364. FuriString* temp_str = furi_string_alloc();
  365. furi_mutex_acquire(fv_priv->mutex, FuriWaitForever);
  366. if (fv_priv->ui_scene == FVSceneMain) {
  367. int countdown_msec = (1000 * (fv_priv->run_state.tick_next
  368. - fv_priv->run_state.tick_cur))
  369. / fv_priv->config.tickrate;
  370. int elapsed_msec = (1000 * fv_priv->run_state.tick_cur)
  371. / fv_priv->config.tickrate;
  372. canvas_draw_frame(canvas, 0, 0, 128, 64);
  373. // draw countdown
  374. canvas_set_font(canvas, FontPrimary);
  375. furi_string_printf(
  376. temp_str, "%02d:%02d:%02d:%03d",
  377. countdown_msec / 3600000,
  378. (countdown_msec / 60000) % 60,
  379. (countdown_msec / 1000) % 60,
  380. countdown_msec % 1000);
  381. canvas_draw_str_aligned(
  382. canvas, 64, 24,
  383. AlignCenter, AlignCenter,
  384. furi_string_get_cstr(temp_str));
  385. // draw top and bottom status bars
  386. canvas_set_font(canvas, FontSecondary);
  387. furi_string_printf(
  388. temp_str, "%02d:%02d:%02d",
  389. elapsed_msec / 3600000,
  390. (elapsed_msec / 60000) % 60,
  391. (elapsed_msec / 1000) % 60);
  392. canvas_draw_str_aligned(
  393. canvas, 4, 8,
  394. AlignLeft, AlignCenter,
  395. furi_string_get_cstr(temp_str));
  396. furi_string_printf(temp_str, "Shot: %d", fv_priv->run_state.shot_cur);
  397. canvas_draw_str_aligned(
  398. canvas, 124, 8,
  399. AlignRight, AlignCenter,
  400. furi_string_get_cstr(temp_str));
  401. elements_button_left(canvas, "Cfg");
  402. elements_button_right(canvas, "Snap");
  403. if (fv_priv->run_state.state == FVDone) {
  404. elements_button_center(canvas, "Start");
  405. } else {
  406. elements_button_center(canvas, "Stop ");
  407. }
  408. }
  409. else if (fv_priv->ui_scene == FVSceneConfig) {
  410. flipvalo_config_edit_draw(canvas, &fv_priv->config_edit_view);
  411. }
  412. furi_string_free(temp_str);
  413. furi_mutex_release(fv_priv->mutex);
  414. }
  415. static void flipvalo_config_init(struct flipvalo_config* fv_conf) {
  416. fv_conf->init_delay_msec = 0;
  417. fv_conf->interval_delay_msec = 5000;
  418. fv_conf->shot_count = 10;
  419. fv_conf->burst_count = 1;
  420. fv_conf->burst_delay_msec = 0;
  421. fv_conf->tickrate = 60;
  422. fv_conf->send_trigger_fn = sony_ir_trigger;
  423. fv_conf->output_config = NULL;
  424. }
  425. static void flipvalo_priv_init(struct flipvalo_priv* fv_priv) {
  426. flipvalo_config_init(&(fv_priv->config));
  427. flipvalo_run_state_init(&(fv_priv->run_state));
  428. fv_priv->timer = NULL;
  429. fv_priv->notifications = NULL;
  430. fv_priv->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  431. }
  432. int32_t flipvalo_app() {
  433. int ret = 0;
  434. ViewPort* view_port = NULL;
  435. Gui* gui = NULL;
  436. FuriStatus event_status = {0};
  437. struct plugin_event event = {0};
  438. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(struct plugin_event));
  439. struct flipvalo_priv* fv_priv = malloc(sizeof(*fv_priv));
  440. flipvalo_priv_init(fv_priv);
  441. if (!fv_priv->mutex) {
  442. FURI_LOG_E("Flipvalo", "Cannot create mutex\r\n");
  443. ret = 1;
  444. goto cleanup;
  445. }
  446. view_port = view_port_alloc();
  447. view_port_draw_callback_set(view_port, render_callback, fv_priv);
  448. view_port_input_callback_set(view_port, input_callback, event_queue);
  449. fv_priv->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, fv_priv);
  450. furi_timer_start(
  451. fv_priv->timer,
  452. (uint32_t) furi_kernel_get_tick_frequency() / fv_priv->config.tickrate);
  453. gui = furi_record_open("gui");
  454. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  455. while (true) {
  456. event_status = furi_message_queue_get(event_queue, &event, 100);
  457. furi_mutex_acquire(fv_priv->mutex, FuriWaitForever);
  458. // catch event_status that is not Ok
  459. if (event_status == FuriStatusErrorTimeout) {
  460. // timeout, ignore
  461. goto next_event;
  462. }
  463. else if (event_status != FuriStatusOk) {
  464. FURI_LOG_E("Flipvalo", "Event Queue Error: %d\r\n", event_status);
  465. goto next_event;
  466. // TODO(luna) evaluate if we should exit here.
  467. //goto cleanup;
  468. }
  469. // handle input
  470. if (
  471. event.type == EventTypeKey
  472. && event.input.type == InputTypeLong
  473. && event.input.key == InputKeyBack
  474. ) {
  475. goto cleanup;
  476. }
  477. switch (fv_priv->ui_scene) {
  478. case FVSceneMain:
  479. if (event.type == EventTypeKey) {
  480. if (event.input.type == InputTypeShort) {
  481. switch (event.input.key) {
  482. case InputKeyUp:
  483. break;
  484. case InputKeyDown:
  485. break;
  486. case InputKeyLeft:
  487. fv_priv->config_edit_view.config = &fv_priv->config;
  488. fv_priv->config_edit_view.cur_index = 0;
  489. fv_priv->config_edit_view.cur_line = 0;
  490. fv_priv->config_edit_view.scroll_pos = 0;
  491. fv_priv->config_edit_view.edit_mode = false;
  492. fv_priv->ui_scene = FVSceneConfig;
  493. break;
  494. case InputKeyRight:
  495. fv_priv->config.send_trigger_fn(fv_priv->config.output_config);
  496. break;
  497. case InputKeyOk:
  498. if (flipvalo_intv_running(fv_priv)) {
  499. flipvalo_intv_stop(fv_priv);
  500. } else {
  501. flipvalo_intv_start(fv_priv);
  502. }
  503. break;
  504. case InputKeyMAX:
  505. break;
  506. case InputKeyBack:
  507. break;
  508. }
  509. }
  510. }
  511. break;
  512. case FVSceneConfig:
  513. ret = flipvalo_config_edit_input(&event.input, &fv_priv->config_edit_view);
  514. if (ret) {
  515. fv_priv->ui_scene = FVSceneMain;
  516. }
  517. break;
  518. }
  519. next_event:
  520. furi_mutex_release(fv_priv->mutex);
  521. }
  522. cleanup:
  523. if (view_port) {
  524. view_port_enabled_set(view_port, false);
  525. if (gui) {
  526. gui_remove_view_port(gui, view_port);
  527. furi_record_close("gui");
  528. }
  529. view_port_free(view_port);
  530. }
  531. if (event_queue) {
  532. furi_message_queue_free(event_queue);
  533. }
  534. if (fv_priv) {
  535. furi_mutex_free(fv_priv->mutex);
  536. furi_timer_free(fv_priv->timer);
  537. }
  538. free(fv_priv);
  539. return ret;
  540. }