zeitraffer.c 18 KB

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