zeitraffer.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. #include <stdio.h>
  2. #include <furi.h>
  3. #include <gui/gui.h>
  4. #include <input/input.h>
  5. #include <notification/notification_messages.h>
  6. #include <flipper_format/flipper_format.h>
  7. #include "gpio_item.h"
  8. #include "zeitraffer_icons.h"
  9. #define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/zeitraffer"
  10. #define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/zeitraffer.conf"
  11. // Часть кода покрадена из https://github.com/zmactep/flipperzero-hello-world
  12. int32_t Time = 10; // Таймер
  13. int32_t Count = 10; // Количество кадров
  14. int32_t WorkTime = 0; // Счётчик таймера
  15. int32_t WorkCount = 0; // Счётчик кадров
  16. bool InfiniteShot = false; // Бесконечная съёмка
  17. bool Bulb = false; // Режим BULB
  18. int32_t Backlight = 0; // Подсветка: вкл/выкл/авто
  19. int32_t Delay = 3; // Задержка на отскочить
  20. bool Work = false;
  21. const NotificationSequence sequence_click = {
  22. &
  23. message_note_c7,
  24. &
  25. message_delay_50,
  26. &
  27. message_sound_off,
  28. NULL,
  29. };
  30. typedef enum {
  31. EventTypeTick,
  32. EventTypeInput,
  33. }
  34. EventType;
  35. typedef struct {
  36. EventType type;
  37. InputEvent input;
  38. }
  39. ZeitrafferEvent;
  40. static void draw_callback(Canvas * canvas, void * ctx) {
  41. UNUSED(ctx);
  42. char temp_str[36];
  43. canvas_clear(canvas);
  44. canvas_set_font(canvas, FontPrimary);
  45. switch (Count) {
  46. case -1:
  47. snprintf(temp_str, sizeof(temp_str), "Set: BULB %li sec", Time);
  48. break;
  49. case 0:
  50. snprintf(temp_str, sizeof(temp_str), "Set: infinite, %li sec", Time);
  51. break;
  52. default:
  53. snprintf(temp_str, sizeof(temp_str), "Set: %li frames, %li sec", Count, Time);
  54. }
  55. canvas_draw_str(canvas, 3, 15, temp_str);
  56. snprintf(temp_str, sizeof(temp_str), "Left: %li frames, %li sec", WorkCount, WorkTime);
  57. canvas_draw_str(canvas, 3, 35, temp_str);
  58. switch (Backlight) {
  59. case 1:
  60. canvas_draw_str(canvas, 13, 55, "ON");
  61. break;
  62. case 2:
  63. canvas_draw_str(canvas, 13, 55, "OFF");
  64. break;
  65. default:
  66. canvas_draw_str(canvas, 13, 55, "AUTO");
  67. }
  68. if (Work) {
  69. canvas_draw_icon(canvas, 85, 41, & I_ButtonUpHollow_7x4);
  70. canvas_draw_icon(canvas, 85, 57, & I_ButtonDownHollow_7x4);
  71. canvas_draw_icon(canvas, 59, 48, & I_ButtonLeftHollow_4x7);
  72. canvas_draw_icon(canvas, 72, 48, & I_ButtonRightHollow_4x7);
  73. } else {
  74. canvas_draw_icon(canvas, 85, 41, & I_ButtonUp_7x4);
  75. canvas_draw_icon(canvas, 85, 57, & I_ButtonDown_7x4);
  76. canvas_draw_icon(canvas, 59, 48, & I_ButtonLeft_4x7);
  77. canvas_draw_icon(canvas, 72, 48, & I_ButtonRight_4x7);
  78. }
  79. canvas_draw_icon(canvas, 3, 48, & I_Pin_star_7x7);
  80. canvas_set_font(canvas, FontPrimary);
  81. canvas_draw_str(canvas, 65, 55, "F");
  82. canvas_set_font(canvas, FontPrimary);
  83. canvas_draw_str(canvas, 85, 55, "S");
  84. //canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
  85. //canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
  86. if (Work) {
  87. canvas_draw_icon(canvas, 106, 46, & I_loading_10px);
  88. }
  89. }
  90. static void input_callback(InputEvent * input_event, void * ctx) {
  91. // Проверяем, что контекст не нулевой
  92. furi_assert(ctx);
  93. FuriMessageQueue * event_queue = ctx;
  94. ZeitrafferEvent event = {
  95. .type = EventTypeInput,
  96. .input = * input_event
  97. };
  98. furi_message_queue_put(event_queue, & event, FuriWaitForever);
  99. }
  100. static void timer_callback(FuriMessageQueue * event_queue) {
  101. // Проверяем, что контекст не нулевой
  102. furi_assert(event_queue);
  103. ZeitrafferEvent event = {
  104. .type = EventTypeTick
  105. };
  106. furi_message_queue_put(event_queue, & event, 0);
  107. }
  108. int32_t zeitraffer_app(void * p) {
  109. UNUSED(p);
  110. // Текущее событие типа кастомного типа ZeitrafferEvent
  111. ZeitrafferEvent event;
  112. // Очередь событий на 8 элементов размера ZeitrafferEvent
  113. FuriMessageQueue * event_queue = furi_message_queue_alloc(8, sizeof(ZeitrafferEvent));
  114. // Создаем новый view port
  115. ViewPort * view_port = view_port_alloc();
  116. // Создаем callback отрисовки, без контекста
  117. view_port_draw_callback_set(view_port, draw_callback, NULL);
  118. // Создаем callback нажатий на клавиши, в качестве контекста передаем
  119. // нашу очередь сообщений, чтоб запихивать в неё эти события
  120. view_port_input_callback_set(view_port, input_callback, event_queue);
  121. // Создаем GUI приложения
  122. Gui * gui = furi_record_open(RECORD_GUI);
  123. // Подключаем view port к GUI в полноэкранном режиме
  124. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  125. // Конфигурим пины
  126. gpio_item_configure_all_pins(GpioModeOutputPushPull);
  127. // Создаем периодический таймер с коллбэком, куда в качестве
  128. // контекста будет передаваться наша очередь событий
  129. FuriTimer * timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
  130. // Запускаем таймер
  131. //furi_timer_start(timer, 1500);
  132. // Включаем нотификации
  133. NotificationApp * notifications = furi_record_open(RECORD_NOTIFICATION);
  134. Storage * storage = furi_record_open(RECORD_STORAGE);
  135. // Загружаем настройки
  136. FlipperFormat * load = flipper_format_file_alloc(storage);
  137. do {
  138. if (!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
  139. notification_message(notifications, & sequence_error);
  140. break;
  141. }
  142. if (!flipper_format_file_open_existing(load, CONFIG_FILE_PATH)) {
  143. notification_message(notifications, & sequence_error);
  144. break;
  145. }
  146. if (!flipper_format_read_int32(load, "Time", & Time, 1)) {
  147. notification_message(notifications, & sequence_error);
  148. break;
  149. }
  150. if (!flipper_format_read_int32(load, "Count", & Count, 1)) {
  151. notification_message(notifications, & sequence_error);
  152. break;
  153. }
  154. if (!flipper_format_read_int32(load, "Backlight", & Backlight, 1)) {
  155. notification_message(notifications, & sequence_error);
  156. break;
  157. }
  158. if (!flipper_format_read_int32(load, "Delay", & Delay, 1)) {
  159. notification_message(notifications, & sequence_error);
  160. break;
  161. }
  162. notification_message(notifications, & sequence_success);
  163. } while (0);
  164. flipper_format_free(load);
  165. // Бесконечный цикл обработки очереди событий
  166. while (1) {
  167. // Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста)
  168. // и проверяем, что у нас получилось это сделать
  169. furi_check(furi_message_queue_get(event_queue, & event, FuriWaitForever) == FuriStatusOk);
  170. // Наше событие — это нажатие кнопки
  171. if (event.type == EventTypeInput) {
  172. if (event.input.type == InputTypeShort) { // Короткие нажатия
  173. if (event.input.key == InputKeyBack) {
  174. if (Work) { // Если таймер запущен - нефиг мацать кнопки!
  175. notification_message(notifications, & sequence_error);
  176. } else {
  177. WorkCount = Count;
  178. WorkTime = 3;
  179. if (Count == 0) {
  180. InfiniteShot = true;
  181. WorkCount = 1;
  182. } else
  183. InfiniteShot = false;
  184. notification_message(notifications, & sequence_success);
  185. }
  186. }
  187. if (event.input.key == InputKeyRight) {
  188. if (furi_timer_is_running(timer)) {
  189. notification_message(notifications, & sequence_error);
  190. } else {
  191. Count++;
  192. notification_message(notifications, & sequence_click);
  193. }
  194. }
  195. if (event.input.key == InputKeyLeft) {
  196. if (furi_timer_is_running(timer)) {
  197. notification_message(notifications, & sequence_error);
  198. } else {
  199. Count--;
  200. notification_message(notifications, & sequence_click);
  201. }
  202. }
  203. if (event.input.key == InputKeyUp) {
  204. if (furi_timer_is_running(timer)) {
  205. notification_message(notifications, & sequence_error);
  206. } else {
  207. Time++;
  208. notification_message(notifications, & sequence_click);
  209. }
  210. }
  211. if (event.input.key == InputKeyDown) {
  212. if (furi_timer_is_running(timer)) {
  213. notification_message(notifications, & sequence_error);
  214. } else {
  215. Time--;
  216. notification_message(notifications, & sequence_click);
  217. }
  218. }
  219. if (event.input.key == InputKeyOk) {
  220. if (furi_timer_is_running(timer)) {
  221. notification_message(notifications, & sequence_click);
  222. furi_timer_stop(timer);
  223. Work = false;
  224. } else {
  225. furi_timer_start(timer, 1000);
  226. Work = true;
  227. if (WorkCount == 0)
  228. WorkCount = Count;
  229. if (WorkTime == 0)
  230. WorkTime = Delay;
  231. if (Count == 1)
  232. WorkTime = Time;
  233. if (Count == 0) {
  234. InfiniteShot = true;
  235. WorkCount = 1;
  236. } else
  237. InfiniteShot = false;
  238. if (Count == -1) {
  239. gpio_item_set_pin(4, true);
  240. gpio_item_set_pin(5, true);
  241. Bulb = true;
  242. WorkCount = 1;
  243. WorkTime = Time;
  244. } else
  245. Bulb = false;
  246. notification_message(notifications, & sequence_success);
  247. }
  248. }
  249. }
  250. if (event.input.type == InputTypeLong) { // Длинные нажатия
  251. // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
  252. if (event.input.key == InputKeyBack) {
  253. if (furi_timer_is_running(timer)) { // А если работает таймер - не выходим :D
  254. notification_message(notifications, & sequence_error);
  255. } else {
  256. notification_message(notifications, & sequence_click);
  257. gpio_item_set_all_pins(false);
  258. furi_timer_stop(timer);
  259. notification_message(notifications, & sequence_display_backlight_enforce_auto);
  260. break;
  261. }
  262. }
  263. if (event.input.key == InputKeyOk) {
  264. // Нам ваша подсветка и нахой не нужна! Или нужна?
  265. Backlight++;
  266. if (Backlight > 2) Backlight = 0;
  267. }
  268. }
  269. if (event.input.type == InputTypeRepeat) { // Зажатые кнопки
  270. if (event.input.key == InputKeyRight) {
  271. if (furi_timer_is_running(timer)) {
  272. notification_message(notifications, & sequence_error);
  273. } else {
  274. Count = Count + 10;
  275. }
  276. }
  277. if (event.input.key == InputKeyLeft) {
  278. if (furi_timer_is_running(timer)) {
  279. notification_message(notifications, & sequence_error);
  280. } else {
  281. Count = Count - 10;
  282. }
  283. }
  284. if (event.input.key == InputKeyUp) {
  285. if (furi_timer_is_running(timer)) {
  286. notification_message(notifications, & sequence_error);
  287. } else {
  288. Time = Time + 10;
  289. }
  290. }
  291. if (event.input.key == InputKeyDown) {
  292. if (furi_timer_is_running(timer)) {
  293. notification_message(notifications, & sequence_error);
  294. } else {
  295. Time = Time - 10;
  296. }
  297. }
  298. }
  299. }
  300. // Наше событие — это сработавший таймер
  301. else if (event.type == EventTypeTick) {
  302. WorkTime--;
  303. if (WorkTime < 1) { // фоткаем
  304. notification_message(notifications, & sequence_blink_white_100);
  305. if (Bulb) {
  306. gpio_item_set_all_pins(false);
  307. WorkCount = 0;
  308. } else {
  309. WorkCount--;
  310. view_port_update(view_port);
  311. notification_message(notifications, & sequence_click);
  312. // Дрыгаем ногами
  313. //gpio_item_set_all_pins(true);
  314. gpio_item_set_pin(4, true);
  315. gpio_item_set_pin(5, true);
  316. furi_delay_ms(400); // На короткие нажатия фотик плохо реагирует
  317. gpio_item_set_pin(4, false);
  318. gpio_item_set_pin(5, false);
  319. //gpio_item_set_all_pins(false);
  320. if (InfiniteShot) WorkCount++;
  321. WorkTime = Time;
  322. view_port_update(view_port);
  323. }
  324. } else {
  325. // Отправляем нотификацию мигания синим светодиодом
  326. notification_message(notifications, & sequence_blink_blue_100);
  327. }
  328. if (WorkCount < 1) { // закончили
  329. Work = false;
  330. gpio_item_set_all_pins(false);
  331. furi_timer_stop(timer);
  332. notification_message(notifications, & sequence_audiovisual_alert);
  333. WorkTime = 3;
  334. WorkCount = 0;
  335. }
  336. switch (Backlight) { // чо по подсветке?
  337. case 1:
  338. notification_message(notifications, & sequence_display_backlight_on);
  339. break;
  340. case 2:
  341. notification_message(notifications, & sequence_display_backlight_off);
  342. break;
  343. default:
  344. notification_message(notifications, & sequence_display_backlight_enforce_auto);
  345. }
  346. }
  347. if (Time < 1) Time = 1; // Не даём открутить таймер меньше единицы
  348. if (Count < -1) Count = 0; // А тут даём, бо 0 кадров это бесконечная съёмка, а -1 кадров - BULB
  349. }
  350. // Схороняем настройки
  351. FlipperFormat * save = flipper_format_file_alloc(storage);
  352. do {
  353. if (!flipper_format_file_open_always(save, CONFIG_FILE_PATH)) {
  354. notification_message(notifications, & sequence_error);
  355. break;
  356. }
  357. if (!flipper_format_write_header_cstr(save, "Zeitraffer", 1)) {
  358. notification_message(notifications, & sequence_error);
  359. break;
  360. }
  361. if (!flipper_format_write_comment_cstr(save, "Zeitraffer app settings: № of frames, interval time, backlight type, Delay")) {
  362. notification_message(notifications, & sequence_error);
  363. break;
  364. }
  365. if (!flipper_format_write_int32(save, "Time", & Time, 1)) {
  366. notification_message(notifications, & sequence_error);
  367. break;
  368. }
  369. if (!flipper_format_write_int32(save, "Count", & Count, 1)) {
  370. notification_message(notifications, & sequence_error);
  371. break;
  372. }
  373. if (!flipper_format_write_int32(save, "Backlight", & Backlight, 1)) {
  374. notification_message(notifications, & sequence_error);
  375. break;
  376. }
  377. if (!flipper_format_write_int32(save, "Delay", & Delay, 1)) {
  378. notification_message(notifications, & sequence_error);
  379. break;
  380. }
  381. } while (0);
  382. flipper_format_free(save);
  383. furi_record_close(RECORD_STORAGE);
  384. // Очищаем таймер
  385. furi_timer_free(timer);
  386. // Специальная очистка памяти, занимаемой очередью
  387. furi_message_queue_free(event_queue);
  388. // Чистим созданные объекты, связанные с интерфейсом
  389. gui_remove_view_port(gui, view_port);
  390. view_port_free(view_port);
  391. furi_record_close(RECORD_GUI);
  392. // Очищаем нотификации
  393. furi_record_close(RECORD_NOTIFICATION);
  394. return 0;
  395. }