Reorg, added more sources

This commit is contained in:
UberGuidoZ
2022-12-28 22:34:27 -08:00
parent 034d4cd172
commit 24f88bf731
1646 changed files with 39096 additions and 0 deletions
@@ -0,0 +1,72 @@
# zeitraffer
[![Build FAP](https://github.com/theageoflove/flipperzero-zeitraffer/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/theageoflove/flipperzero-zeitraffer/actions/workflows/build.yml)
english version [below](#eng)
![zeitraffer for flipper zero](https://theageoflove.ru/uploads/2022/11/photo_2022-11-10_15-54-25.jpg)
Видео работы: https://youtu.be/VPSpRLJXYAc
Готовый фап под последнюю релизную прошивку [можно скачать здесь](https://nightly.link/theageoflove/flipperzero-zeitraffer/workflows/build/main/zeitraffer.fap.zip).
Я ненастоящий сварщик, не обессудьте. Делал для своей Sony DSLR A100, подходит для любых камер, поддерживающих проводной пульт с тремя контактами.
Основано на хелловорлде https://github.com/zmactep/flipperzero-hello-world
### Управление:
- **вверх-вниз** - время.
- **влево-вправо** - количество кадров
0 кадров - бесконечный режим, -1 кадров - BULB
- **зажатие стрелок** - ±10 кадров/секунд
- **ОК** - пуск/пауза
- Длинное нажатие **ОК** - включить/выключить подсветку
- **назад** - сброс
- длинное нажатие **назад** - выход
При работающем таймере блокируются все кнопки кроме ОК.
При запуске даётся три секунды на отскочить.
## Чо надо
- две оптопары типа EL817C
- кусок гребёнки на три пина
- немного провода
- термоусадка
- разъём пульта от камеры. Где взять или из чего сделать - думайте
## Как собрать
Берём оптопары, соединяем по схеме.
![](https://theageoflove.ru/uploads/2022/11/camera_cable.jpg)
Где какой пин у камеры, можно узнать например тут: https://www.doc-diy.net/photo/remote_pinout/
# <a name="eng"></a>English
Simple timelapse app for Flipper Zero.
[Get latest release](https://nightly.link/theageoflove/flipperzero-zeitraffer/workflows/build/main/zeitraffer.fap.zip)
based on https://github.com/zmactep/flipperzero-hello-world
### Control:
- Up and down - time.
- Left and right - number of frames
- Long press arrows - ±10 frames/seconds
- OK - start/pause
- Long press OK - turn on/off the backlight
- Back - reset
- Long press back - exit
When the timer is running, all buttons are blocked except OK.
## What you need:
- two EL817C optocouplers
- pin header connector 1x3 2,54mm male
- some wire
- heat shrink
- camera remote connector
## How to assemble
Take optocouplers, connect according to the scheme.
![](https://theageoflove.ru/uploads/2022/11/camera_cable_en.jpg)
Camera pinout can be found here: https://www.doc-diy.net/photo/remote_pinout/
@@ -0,0 +1,21 @@
App(
appid="GPIO_Intervalometer",
name="[GPIO] Intervalometer",
apptype=FlipperAppType.EXTERNAL,
entry_point="zeitraffer_app",
cdefines=["APP_ZEITRAFFER"],
requires=[
"gui",
"input",
"notification",
"gpio"
],
stack_size=2 * 1024,
order=90,
fap_icon_assets="icons",
fap_icon="zeitraffer.png",
fap_category="GPIO",
fap_description="Simple intervalometer app",
fap_author="Aurelius Rosenbaum",
fap_weburl="https://github.com/theageoflove/flipperzero-zeitraffer",
)
@@ -0,0 +1,51 @@
#include "gpio_item.h"
#include <furi_hal_resources.h>
typedef struct {
const char* name;
const GpioPin* pin;
} GpioItem;
static const GpioItem gpio_item[GPIO_ITEM_COUNT] = {
{"1.2: PA7", &gpio_ext_pa7},
{"1.3: PA6", &gpio_ext_pa6},
{"1.4: PA4", &gpio_ext_pa4},
{"1.5: PB3", &gpio_ext_pb3},
{"1.6: PB2", &gpio_ext_pb2},
{"1.7: PC3", &gpio_ext_pc3},
{"2.7: PC1", &gpio_ext_pc1},
{"2.8: PC0", &gpio_ext_pc0},
};
void gpio_item_configure_pin(uint8_t index, GpioMode mode) {
furi_assert(index < GPIO_ITEM_COUNT);
furi_hal_gpio_write(gpio_item[index].pin, false);
furi_hal_gpio_init(gpio_item[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh);
}
void gpio_item_configure_all_pins(GpioMode mode) {
for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) {
gpio_item_configure_pin(i, mode);
}
}
void gpio_item_set_pin(uint8_t index, bool level) {
furi_assert(index < GPIO_ITEM_COUNT);
furi_hal_gpio_write(gpio_item[index].pin, level);
}
void gpio_item_set_all_pins(bool level) {
for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) {
gpio_item_set_pin(i, level);
}
}
const char* gpio_item_get_pin_name(uint8_t index) {
furi_assert(index < GPIO_ITEM_COUNT + 1);
if(index == GPIO_ITEM_COUNT) {
return "ALL";
} else {
return gpio_item[index].name;
}
}
@@ -0,0 +1,15 @@
#pragma once
#include <furi_hal_gpio.h>
#define GPIO_ITEM_COUNT 8
void gpio_item_configure_pin(uint8_t index, GpioMode mode);
void gpio_item_configure_all_pins(GpioMode mode);
void gpio_item_set_pin(uint8_t index, bool level);
void gpio_item_set_all_pins(bool level);
const char* gpio_item_get_pin_name(uint8_t index);
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,435 @@
#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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B