diff --git a/applications/plugins/timelapse/.gitignore b/applications/plugins/timelapse/.gitignore index 722d5e71d..9df1745a1 100644 --- a/applications/plugins/timelapse/.gitignore +++ b/applications/plugins/timelapse/.gitignore @@ -1 +1,2 @@ .vscode +dist diff --git a/applications/plugins/timelapse/README.md b/applications/plugins/timelapse/README.md index cd7b670b8..fe3ed171a 100644 --- a/applications/plugins/timelapse/README.md +++ b/applications/plugins/timelapse/README.md @@ -6,7 +6,7 @@ english version [below](#eng) ![zeitraffer for flipper zero](https://theageoflove.ru/uploads/2022/11/photo_2022-11-10_15-54-25.jpg) -Видео работы: https://youtube.com/shorts/QKyQa7_s7IE +Видео работы: https://youtu.be/VPSpRLJXYAc Готовый фап под последнюю релизную прошивку [можно скачать здесь](https://nightly.link/theageoflove/flipperzero-zeitraffer/workflows/build/main/zeitraffer.fap.zip). diff --git a/applications/plugins/timelapse/application.fam b/applications/plugins/timelapse/application.fam index afc0ab675..9e9a119bd 100644 --- a/applications/plugins/timelapse/application.fam +++ b/applications/plugins/timelapse/application.fam @@ -1,14 +1,17 @@ App( - appid="GPIO_Timelapse", - name="[GPIO] Timelapse", + appid="GPIO_Intervalometer", + name="[GPIO] Intervalometer", apptype=FlipperAppType.EXTERNAL, entry_point="zeitraffer_app", cdefines=["APP_ZEITRAFFER"], requires=[ "gui", ], - stack_size=1 * 1024, + stack_size=2 * 1024, order=90, fap_icon="zeitraffer.png", fap_category="GPIO", + fap_description="Simple intervalometer app", + fap_author="Aurelius Rosenbaum", + fap_weburl="https://github.com/theageoflove/flipperzero-zeitraffer", ) \ No newline at end of file diff --git a/applications/plugins/timelapse/zeitraffer.c b/applications/plugins/timelapse/zeitraffer.c index 7031ec7de..d8d304486 100644 --- a/applications/plugins/timelapse/zeitraffer.c +++ b/applications/plugins/timelapse/zeitraffer.c @@ -3,18 +3,22 @@ #include #include #include -//#include "gpio/gpio_item.h" +#include #include "gpio_item.h" +#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps/Misc" +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/zeitraffer.conf" + // Часть кода покрадена из https://github.com/zmactep/flipperzero-hello-world -int Time = 10; // Таймер -int Count = 10; // Количество кадров -int WorkTime = 0; // Счётчик таймера -int WorkCount = 0; // Счётчик кадров +int32_t Time = 10; // Таймер +int32_t Count = 10; // Количество кадров +int32_t WorkTime = 0; // Счётчик таймера +int32_t WorkCount = 0; // Счётчик кадров bool InfiniteShot = false; // Бесконечная съёмка bool Bulb = false; // Режим BULB -int Backlight = 0; // Подсветка: вкл/выкл/авто +int32_t Backlight = 0; // Подсветка: вкл/выкл/авто +int32_t Delay = 3; // Задержка на отскочить const NotificationSequence sequence_click = { &message_note_c7, @@ -35,33 +39,34 @@ typedef struct { static void draw_callback(Canvas* canvas, void* ctx) { UNUSED(ctx); - char temp_str[36]; + char temp_str[36]; canvas_clear(canvas); canvas_set_font(canvas, FontPrimary); - switch(Count) { - case -1: - snprintf(temp_str, sizeof(temp_str), "Set: BULB %i sec", Time); - break; - case 0: - snprintf(temp_str, sizeof(temp_str), "Set: infinite, %i sec", Time); - break; - default: - snprintf(temp_str, sizeof(temp_str), "Set: %i frames, %i sec", Count, Time); - } - canvas_draw_str(canvas, 3, 15, temp_str); - snprintf(temp_str, sizeof(temp_str), "Left: %i frames, %i sec", WorkCount, WorkTime); - canvas_draw_str(canvas, 3, 35, temp_str); + switch (Count) { + case -1: + snprintf(temp_str,sizeof(temp_str),"Set: BULB %li sec",Time); + break; + case 0: + snprintf(temp_str,sizeof(temp_str),"Set: infinite, %li sec",Time); + break; + default: + snprintf(temp_str,sizeof(temp_str),"Set: %li frames, %li sec",Count,Time); + } + canvas_draw_str(canvas, 3, 15, temp_str); + snprintf(temp_str,sizeof(temp_str),"Left: %li frames, %li sec",WorkCount,WorkTime); + canvas_draw_str(canvas, 3, 35, temp_str); + + switch (Backlight) { + case 1: + canvas_draw_str(canvas, 3, 55, "Backlight: ON"); + break; + case 2: + canvas_draw_str(canvas, 3, 55, "Backlight: OFF"); + break; + default: + canvas_draw_str(canvas, 3, 55, "Backlight: AUTO"); + } - switch(Backlight) { - case 1: - canvas_draw_str(canvas, 3, 55, "Backlight: ON"); - break; - case 2: - canvas_draw_str(canvas, 3, 55, "Backlight: OFF"); - break; - default: - canvas_draw_str(canvas, 3, 55, "Backlight: AUTO"); - } } static void input_callback(InputEvent* input_event, void* ctx) { @@ -83,7 +88,7 @@ static void timer_callback(FuriMessageQueue* event_queue) { int32_t zeitraffer_app(void* p) { UNUSED(p); - + // Текущее событие типа кастомного типа ZeitrafferEvent ZeitrafferEvent event; // Очередь событий на 8 элементов размера ZeitrafferEvent @@ -101,9 +106,9 @@ int32_t zeitraffer_app(void* p) { Gui* gui = furi_record_open(RECORD_GUI); // Подключаем view port к GUI в полноэкранном режиме gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - // Конфигурим пины - gpio_item_configure_all_pins(GpioModeOutputPushPull); + + // Конфигурим пины + gpio_item_configure_all_pins(GpioModeOutputPushPull); // Создаем периодический таймер с коллбэком, куда в качестве // контекста будет передаваться наша очередь событий @@ -113,203 +118,260 @@ int32_t zeitraffer_app(void* p) { // Включаем нотификации NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + + + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Загружаем настройки + FlipperFormat* load = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_existing(load, CONFIG_FILE_PATH)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_read_int32(load, "Time", &Time, 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_read_int32(load, "Count", &Count, 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_read_int32(load, "Backlight", &Backlight, 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_read_int32(load, "Delay", &Delay, 1)) {notification_message(notifications, &sequence_error); break;} + notification_message(notifications, &sequence_success); + + } while(0); + + flipper_format_free(load); + // Бесконечный цикл обработки очереди событий while(1) { // Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста) // и проверяем, что у нас получилось это сделать furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk); - // Наше событие — это нажатие кнопки - if(event.type == EventTypeInput) { - if(event.input.type == InputTypeShort) { // Короткие нажатия + // Наше событие — это нажатие кнопки + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeShort) { // Короткие нажатия + + if(event.input.key == InputKeyBack) { + if(furi_timer_is_running(timer)) { // Если таймер запущен - нефиг мацать кнопки! + notification_message(notifications, &sequence_error); + } + else { + WorkCount = Count; + WorkTime = 3; + if (Count == 0) { + InfiniteShot = true; + WorkCount = 1; + } + else + InfiniteShot = false; + + notification_message(notifications, &sequence_success); + } + } + if(event.input.key == InputKeyRight) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Count++; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyLeft) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Count--; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyUp) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Time++; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyDown) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Time--; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyOk) { + + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_click); + furi_timer_stop(timer); + } + else { + furi_timer_start(timer, 1000); + + if (WorkCount == 0) + WorkCount = Count; + + if (WorkTime == 0) + WorkTime = Delay; + + if (Count == 0) { + InfiniteShot = true; + WorkCount = 1; + } + else + InfiniteShot = false; + + if (Count == -1) { + gpio_item_set_pin(4, true); + gpio_item_set_pin(5, true); + Bulb = true; + WorkCount = 1; + WorkTime = Time; + } + else + Bulb = false; + + notification_message(notifications, &sequence_success); + } + } + } + if(event.input.type == InputTypeLong) { // Длинные нажатия + // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения + if(event.input.key == InputKeyBack) { + if(furi_timer_is_running(timer)) { // А если работает таймер - не выходим :D + notification_message(notifications, &sequence_error); + } + else { + notification_message(notifications, &sequence_click); + gpio_item_set_all_pins(false); + furi_timer_stop(timer); + notification_message(notifications, &sequence_display_backlight_enforce_auto); + break; + } + } + if(event.input.key == InputKeyOk) { + // Нам ваша подсветка и нахой не нужна! Или нужна? + Backlight++; + if (Backlight > 2) Backlight = 0; + } - if(event.input.key == InputKeyBack) { - if(furi_timer_is_running( - timer)) { // Если таймер запущен - нефиг мацать кнопки! - notification_message(notifications, &sequence_error); - } else { - WorkCount = Count; - WorkTime = 3; - if(Count == 0) { - InfiniteShot = true; - WorkCount = 1; - } else - InfiniteShot = false; + } - notification_message(notifications, &sequence_success); - } - } - if(event.input.key == InputKeyRight) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Count++; - notification_message(notifications, &sequence_click); - } - } - if(event.input.key == InputKeyLeft) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Count--; - notification_message(notifications, &sequence_click); - } - } - if(event.input.key == InputKeyUp) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Time++; - notification_message(notifications, &sequence_click); - } - } - if(event.input.key == InputKeyDown) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Time--; - notification_message(notifications, &sequence_click); - } - } - if(event.input.key == InputKeyOk) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_click); - furi_timer_stop(timer); - } else { - furi_timer_start(timer, 1000); - - if(WorkCount == 0) WorkCount = Count; - - if(WorkTime == 0) WorkTime = 3; - - if(Count == 0) { - InfiniteShot = true; - WorkCount = 1; - } else - InfiniteShot = false; - - if(Count == -1) { - gpio_item_set_pin(4, true); - gpio_item_set_pin(5, true); - Bulb = true; - WorkCount = 1; - WorkTime = Time; - } else - Bulb = false; - - notification_message(notifications, &sequence_success); - } - } - } - if(event.input.type == InputTypeLong) { // Длинные нажатия - // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения - if(event.input.key == InputKeyBack) { - if(furi_timer_is_running(timer)) { // А если работает таймер - не выходим :D - notification_message(notifications, &sequence_error); - } else { - notification_message(notifications, &sequence_click); - gpio_item_set_all_pins(false); - furi_timer_stop(timer); - notification_message( - notifications, &sequence_display_backlight_enforce_auto); - break; - } - } - if(event.input.key == InputKeyOk) { - // Нам ваша подсветка и нахой не нужна! Или нужна? - Backlight++; - if(Backlight > 2) Backlight = 0; - } - } - - if(event.input.type == InputTypeRepeat) { // Зажатые кнопки - if(event.input.key == InputKeyRight) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Count = Count + 10; - } - } - if(event.input.key == InputKeyLeft) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Count = Count - 10; - } - } - if(event.input.key == InputKeyUp) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Time = Time + 10; - } - } - if(event.input.key == InputKeyDown) { - if(furi_timer_is_running(timer)) { - notification_message(notifications, &sequence_error); - } else { - Time = Time - 10; - } - } - } + if(event.input.type == InputTypeRepeat) { // Зажатые кнопки + if(event.input.key == InputKeyRight) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Count = Count+10; + } + } + if(event.input.key == InputKeyLeft) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Count = Count-10; + } + } + if(event.input.key == InputKeyUp) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Time = Time+10; + } + } + if(event.input.key == InputKeyDown) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } + else { + Time = Time-10; + } + } + } } - + // Наше событие — это сработавший таймер - else if(event.type == EventTypeTick) { - WorkTime--; + else if(event.type == EventTypeTick) { + + WorkTime--; + + if( WorkTime < 1 ) { // фоткаем + notification_message(notifications, &sequence_blink_white_100); + if (Bulb) { + gpio_item_set_all_pins(false); WorkCount = 0; + } + else { + WorkCount--; + view_port_update(view_port); + notification_message(notifications, &sequence_click); + // Дрыгаем ногами + //gpio_item_set_all_pins(true); + gpio_item_set_pin(4, true); + gpio_item_set_pin(5, true); + furi_delay_ms(400); // На короткие нажатия фотик плохо реагирует + gpio_item_set_pin(4, false); + gpio_item_set_pin(5, false); + //gpio_item_set_all_pins(false); - if(WorkTime < 1) { // фоткаем - notification_message(notifications, &sequence_blink_white_100); - if(Bulb) { - gpio_item_set_all_pins(false); - WorkCount = 0; - } else { - WorkCount--; - view_port_update(view_port); - notification_message(notifications, &sequence_click); - // Дрыгаем ногами - //gpio_item_set_all_pins(true); - gpio_item_set_pin(4, true); - gpio_item_set_pin(5, true); - furi_delay_ms(400); // На короткие нажатия фотик плохо реагирует - gpio_item_set_pin(4, false); - gpio_item_set_pin(5, false); - //gpio_item_set_all_pins(false); - - if(InfiniteShot) WorkCount++; - - WorkTime = Time; - view_port_update(view_port); - } - } else { - // Отправляем нотификацию мигания синим светодиодом - notification_message(notifications, &sequence_blink_blue_100); - } - - if(WorkCount < 1) { // закончили - gpio_item_set_all_pins(false); - furi_timer_stop(timer); - notification_message(notifications, &sequence_audiovisual_alert); - WorkTime = 3; - WorkCount = 0; - } - - switch(Backlight) { // чо по подсветке? - case 1: - notification_message(notifications, &sequence_display_backlight_on); - break; - case 2: - notification_message(notifications, &sequence_display_backlight_off); - break; - default: - notification_message(notifications, &sequence_display_backlight_enforce_auto); - } + if (InfiniteShot) WorkCount++; + + WorkTime = Time; + view_port_update(view_port); + } + } + else { + // Отправляем нотификацию мигания синим светодиодом + notification_message(notifications, &sequence_blink_blue_100); + } + + if( WorkCount < 1 ) { // закончили + gpio_item_set_all_pins(false); + furi_timer_stop(timer); + notification_message(notifications, &sequence_audiovisual_alert); + WorkTime = 3; + WorkCount = 0; + } + + switch (Backlight) { // чо по подсветке? + case 1: + notification_message(notifications, &sequence_display_backlight_on); + break; + case 2: + notification_message(notifications, &sequence_display_backlight_off); + break; + default: + notification_message(notifications, &sequence_display_backlight_enforce_auto); + } + } - if(Time < 1) Time = 1; // Не даём открутить таймер меньше единицы - if(Count < -1) - Count = 0; // А тут даём, бо 0 кадров это бесконечная съёмка, а -1 кадров - BULB - } + if (Time < 1) Time = 1; // Не даём открутить таймер меньше единицы + if (Count < -1) Count = 0; // А тут даём, бо 0 кадров это бесконечная съёмка, а -1 кадров - BULB + } + + + // Схороняем настройки + FlipperFormat* save = flipper_format_file_alloc(storage); + + do { + + if(!flipper_format_file_open_always(save, CONFIG_FILE_PATH)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_write_header_cstr(save, "Zeitraffer", 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_write_comment_cstr(save, "Zeitraffer app settings: № of frames, interval time, backlight type, Delay")) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_write_int32(save, "Time", &Time, 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_write_int32(save, "Count", &Count, 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_write_int32(save, "Backlight", &Backlight, 1)) {notification_message(notifications, &sequence_error); break;} + if(!flipper_format_write_int32(save, "Delay", &Delay, 1)) {notification_message(notifications, &sequence_error); break;} + + } while(0); + + flipper_format_free(save); + + furi_record_close(RECORD_STORAGE); // Очищаем таймер furi_timer_free(timer);