Flipper/Applications/Official/DEV_FW/source/grnch/timelapse/zeitraffer.c
2022-12-28 22:34:27 -08:00

436 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification_messages.h>
#include <flipper_format/flipper_format.h>
#include "gpio_item.h"
#include "GPIO_Intervalometer_icons.h"
#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/intravelometer"
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/intravelometer.conf"
// Часть кода покрадена из https://github.com/zmactep/flipperzero-hello-world
int32_t Time = 10; // Таймер
int32_t Count = 10; // Количество кадров
int32_t WorkTime = 0; // Счётчик таймера
int32_t WorkCount = 0; // Счётчик кадров
bool InfiniteShot = false; // Бесконечная съёмка
bool Bulb = false; // Режим BULB
int32_t Backlight = 0; // Подсветка: вкл/выкл/авто
int32_t Delay = 3; // Задержка на отскочить
bool Work = false;
const NotificationSequence sequence_click = {
&message_note_c7,
&message_delay_50,
&message_sound_off,
NULL,
};
typedef enum {
EventTypeTick,
EventTypeInput,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} ZeitrafferEvent;
static void draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
char temp_str[36];
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
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, 13, 55, "ON");
break;
case 2:
canvas_draw_str(canvas, 13, 55, "OFF");
break;
default:
canvas_draw_str(canvas, 13, 55, "AUTO");
}
//canvas_draw_icon(canvas, 90, 17, &I_ButtonUp_7x4);
//canvas_draw_icon(canvas, 100, 17, &I_ButtonDown_7x4);
//canvas_draw_icon(canvas, 27, 17, &I_ButtonLeftSmall_3x5);
//canvas_draw_icon(canvas, 37, 17, &I_ButtonRightSmall_3x5);
//canvas_draw_icon(canvas, 3, 48, &I_Pin_star_7x7);
canvas_draw_icon(canvas, 85, 41, &I_ButtonUp_7x4);
canvas_draw_icon(canvas, 85, 57, &I_ButtonDown_7x4);
canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
canvas_draw_icon(canvas, 3, 48, &I_Pin_star_7x7);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 65, 55, "F");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 85, 55, "S");
canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7);
canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7);
if(Work) {
canvas_draw_icon(canvas, 106, 46, &I_loading_10px);
}
}
static void input_callback(InputEvent* input_event, void* ctx) {
// Проверяем, что контекст не нулевой
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
ZeitrafferEvent event = {.type = EventTypeInput, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void timer_callback(FuriMessageQueue* event_queue) {
// Проверяем, что контекст не нулевой
furi_assert(event_queue);
ZeitrafferEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
int32_t zeitraffer_app(void* p) {
UNUSED(p);
// Текущее событие типа кастомного типа ZeitrafferEvent
ZeitrafferEvent event;
// Очередь событий на 8 элементов размера ZeitrafferEvent
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(ZeitrafferEvent));
// Создаем новый view port
ViewPort* view_port = view_port_alloc();
// Создаем callback отрисовки, без контекста
view_port_draw_callback_set(view_port, draw_callback, NULL);
// Создаем callback нажатий на клавиши, в качестве контекста передаем
// нашу очередь сообщений, чтоб запихивать в неё эти события
view_port_input_callback_set(view_port, input_callback, event_queue);
// Создаем GUI приложения
Gui* gui = furi_record_open(RECORD_GUI);
// Подключаем view port к GUI в полноэкранном режиме
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Конфигурим пины
gpio_item_configure_all_pins(GpioModeOutputPushPull);
// Создаем периодический таймер с коллбэком, куда в качестве
// контекста будет передаваться наша очередь событий
FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
// Запускаем таймер
//furi_timer_start(timer, 1500);
// Включаем нотификации
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.input.key == InputKeyBack) {
if(Work) { // Если таймер запущен - нефиг мацать кнопки!
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);
Work = false;
} else {
furi_timer_start(timer, 1000);
Work = true;
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.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--;
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) { // закончили
Work = false;
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
}
// Схороняем настройки
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);
// Специальная очистка памяти, занимаемой очередью
furi_message_queue_free(event_queue);
// Чистим созданные объекты, связанные с интерфейсом
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
// Очищаем нотификации
furi_record_close(RECORD_NOTIFICATION);
return 0;
}