mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-12 17:48:35 -07:00
Temporarily backport app updates from apps repo
This commit is contained in:
15
applications/external/mayhem_camera/application.fam
vendored
Normal file
15
applications/external/mayhem_camera/application.fam
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
App(
|
||||
appid="mayhem_camera",
|
||||
name="[MAYHEM] Camera",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="camera_app",
|
||||
cdefines=["APP_CAMERA"],
|
||||
requires=["gui"],
|
||||
stack_size=8*1024,
|
||||
order=1,
|
||||
fap_icon="icon.png",
|
||||
fap_category="GPIO",
|
||||
fap_description="ESP32-CAM live feed and photo capture, use left/right for orientation/mode, up/down for brightness and center for saving a screenshot. [Unplug the USB cable to test with Mayhem]",
|
||||
fap_author="Z4urce",
|
||||
fap_weburl="https://github.com/Z4urce/flipper-camera"
|
||||
)
|
||||
310
applications/external/mayhem_camera/camera.c
vendored
Normal file
310
applications/external/mayhem_camera/camera.c
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
#include "camera.h"
|
||||
|
||||
static void camera_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
UartDumpModel* model = _model;
|
||||
|
||||
// Prepare canvas
|
||||
//canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(canvas, 0, 0, FRAME_WIDTH, FRAME_HEIGTH);
|
||||
|
||||
for(size_t p = 0; p < FRAME_BUFFER_LENGTH; ++p) {
|
||||
uint8_t x = p % ROW_BUFFER_LENGTH; // 0 .. 15
|
||||
uint8_t y = p / ROW_BUFFER_LENGTH; // 0 .. 63
|
||||
|
||||
for(uint8_t i = 0; i < 8; ++i) {
|
||||
if((model->pixels[p] & (1 << (7 - i))) != 0) {
|
||||
canvas_draw_dot(canvas, (x * 8) + i, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!model->initialized) {
|
||||
/*if(!model->marauderInitialized)
|
||||
{
|
||||
// Init marauder into stream mode
|
||||
uint8_t data[] = "\nstream\n";
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, data, sizeof(data));
|
||||
}*/
|
||||
|
||||
canvas_draw_icon(canvas, 74, 16, &I_DolphinCommon_56x48);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 8, 12, "Waiting ESP32-CAM...");
|
||||
canvas_draw_str(canvas, 20, 24, "VCC - 3V3/5V");
|
||||
canvas_draw_str(canvas, 20, 34, "GND - GND");
|
||||
canvas_draw_str(canvas, 20, 44, "U0R - TX");
|
||||
canvas_draw_str(canvas, 20, 54, "U0T - RX");
|
||||
}
|
||||
}
|
||||
|
||||
void get_timefilename(FuriString* name) {
|
||||
FuriHalRtcDateTime datetime = {0};
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
furi_string_printf(
|
||||
name,
|
||||
APP_DATA_PATH("%.4d%.2d%.2d-%.2d%.2d%.2d.bmp"),
|
||||
datetime.year,
|
||||
datetime.month,
|
||||
datetime.day,
|
||||
datetime.hour,
|
||||
datetime.minute,
|
||||
datetime.second);
|
||||
}
|
||||
|
||||
static void save_image(void* context) {
|
||||
UartEchoApp* app = context;
|
||||
furi_assert(app);
|
||||
|
||||
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// We need a storage struct (gain accesso to the filesystem API )
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// storage_file_alloc gives to us a File pointer using the Storage API.
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
if(storage_common_stat(storage, IMAGE_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
|
||||
storage_simply_mkdir(storage, IMAGE_FILE_DIRECTORY_PATH);
|
||||
}
|
||||
|
||||
// create file name
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
get_timefilename(file_name);
|
||||
|
||||
// this functions open a file, using write access and creates new file if not exist.
|
||||
bool result =
|
||||
storage_file_open(file, furi_string_get_cstr(file_name), FSAM_WRITE, FSOM_OPEN_ALWAYS);
|
||||
//bool result = storage_file_open(file, EXT_PATH("DCIM/test.bmp"), FSAM_WRITE, FSOM_OPEN_ALWAYS);
|
||||
furi_string_free(file_name);
|
||||
|
||||
if(result) {
|
||||
storage_file_write(file, bitmap_header, BITMAP_HEADER_LENGTH);
|
||||
with_view_model(
|
||||
app->view,
|
||||
UartDumpModel * model,
|
||||
{
|
||||
int8_t row_buffer[ROW_BUFFER_LENGTH];
|
||||
for(size_t i = 64; i > 0; --i) {
|
||||
for(size_t j = 0; j < ROW_BUFFER_LENGTH; ++j) {
|
||||
row_buffer[j] = model->pixels[((i - 1) * ROW_BUFFER_LENGTH) + j];
|
||||
}
|
||||
storage_file_write(file, row_buffer, ROW_BUFFER_LENGTH);
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
// Closing the "file descriptor"
|
||||
storage_file_close(file);
|
||||
|
||||
// Freeing up memory
|
||||
storage_file_free(file);
|
||||
|
||||
notification_message(notifications, result ? &sequence_success : &sequence_error);
|
||||
}
|
||||
|
||||
static bool camera_view_input_callback(InputEvent* event, void* context) {
|
||||
if(event->type == InputTypePress) {
|
||||
uint8_t data[1];
|
||||
if(event->key == InputKeyUp) {
|
||||
data[0] = 'C';
|
||||
} else if(event->key == InputKeyDown) {
|
||||
data[0] = 'c';
|
||||
} else if(event->key == InputKeyRight) {
|
||||
data[0] = '>';
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
data[0] = '<';
|
||||
} else if(event->key == InputKeyOk) {
|
||||
save_image(context);
|
||||
}
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t camera_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
static void camera_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
|
||||
furi_assert(context);
|
||||
UartEchoApp* app = context;
|
||||
|
||||
if(ev == UartIrqEventRXNE) {
|
||||
furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
|
||||
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx);
|
||||
}
|
||||
}
|
||||
|
||||
static void process_ringbuffer(UartDumpModel* model, uint8_t byte) {
|
||||
//// 1. Phase: filling the ringbuffer
|
||||
if(model->ringbuffer_index == 0 && byte != 'Y') { // First char has to be 'Y' in the buffer.
|
||||
return;
|
||||
}
|
||||
|
||||
if(model->ringbuffer_index == 1 &&
|
||||
byte != ':') { // Second char has to be ':' in the buffer or reset.
|
||||
model->ringbuffer_index = 0;
|
||||
process_ringbuffer(model, byte);
|
||||
return;
|
||||
}
|
||||
|
||||
model->row_ringbuffer[model->ringbuffer_index] =
|
||||
byte; // Assign current byte to the ringbuffer;
|
||||
++model->ringbuffer_index; // Increment the ringbuffer index
|
||||
|
||||
if(model->ringbuffer_index < RING_BUFFER_LENGTH) { // Let's wait 'till the buffer fills.
|
||||
return;
|
||||
}
|
||||
|
||||
//// 2. Phase: flushing the ringbuffer to the framebuffer
|
||||
model->ringbuffer_index = 0; // Let's reset the ringbuffer
|
||||
model->initialized = true; // We've successfully established the connection
|
||||
size_t row_start_index =
|
||||
model->row_ringbuffer[2] * ROW_BUFFER_LENGTH; // Third char will determine the row number
|
||||
|
||||
if(row_start_index > LAST_ROW_INDEX) { // Failsafe
|
||||
row_start_index = 0;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < ROW_BUFFER_LENGTH; ++i) {
|
||||
model->pixels[row_start_index + i] =
|
||||
model->row_ringbuffer[i + 3]; // Writing the remaining 16 bytes into the frame buffer
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t camera_worker(void* context) {
|
||||
furi_assert(context);
|
||||
UartEchoApp* app = context;
|
||||
|
||||
while(1) {
|
||||
uint32_t events =
|
||||
furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_check((events & FuriFlagError) == 0);
|
||||
|
||||
if(events & WorkerEventStop) break;
|
||||
if(events & WorkerEventRx) {
|
||||
size_t length = 0;
|
||||
do {
|
||||
size_t intended_data_size = 64;
|
||||
uint8_t data[intended_data_size];
|
||||
length = furi_stream_buffer_receive(app->rx_stream, data, intended_data_size, 0);
|
||||
|
||||
if(length > 0) {
|
||||
//furi_hal_uart_tx(FuriHalUartIdUSART1, data, length);
|
||||
with_view_model(
|
||||
app->view,
|
||||
UartDumpModel * model,
|
||||
{
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
process_ringbuffer(model, data[i]);
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
} while(length > 0);
|
||||
|
||||
notification_message(app->notification, &sequence_notification);
|
||||
with_view_model(
|
||||
app->view, UartDumpModel * model, { UNUSED(model); }, true);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UartEchoApp* camera_app_alloc() {
|
||||
UartEchoApp* app = malloc(sizeof(UartEchoApp));
|
||||
|
||||
app->rx_stream = furi_stream_buffer_alloc(2048, 1);
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
app->view = view_alloc();
|
||||
view_set_context(app->view, app);
|
||||
view_set_draw_callback(app->view, camera_view_draw_callback);
|
||||
view_set_input_callback(app->view, camera_view_input_callback);
|
||||
view_allocate_model(app->view, ViewModelTypeLocking, sizeof(UartDumpModel));
|
||||
|
||||
with_view_model(
|
||||
app->view,
|
||||
UartDumpModel * model,
|
||||
{
|
||||
for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) {
|
||||
model->pixels[i] = 0;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
view_set_previous_callback(app->view, camera_exit);
|
||||
view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||
|
||||
app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 2048, camera_worker, app);
|
||||
furi_thread_start(app->worker_thread);
|
||||
|
||||
// Enable uart listener
|
||||
furi_hal_console_disable();
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400);
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, camera_on_irq_cb, app);
|
||||
|
||||
furi_hal_power_disable_external_3_3v();
|
||||
furi_hal_power_disable_otg();
|
||||
furi_delay_ms(200);
|
||||
furi_hal_power_enable_external_3_3v();
|
||||
furi_hal_power_enable_otg();
|
||||
for(int i = 0; i < 2; i++) {
|
||||
furi_delay_ms(500);
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t[1]){'c'}, 1);
|
||||
}
|
||||
furi_delay_ms(1);
|
||||
return app;
|
||||
}
|
||||
|
||||
static void camera_app_free(UartEchoApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
furi_thread_free(app->worker_thread);
|
||||
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, 0);
|
||||
|
||||
view_free(app->view);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Close gui record
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
app->gui = NULL;
|
||||
|
||||
furi_stream_buffer_free(app->rx_stream);
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t camera_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
UartEchoApp* app = camera_app_alloc();
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
camera_app_free(app);
|
||||
|
||||
furi_hal_power_disable_otg();
|
||||
|
||||
return 0;
|
||||
}
|
||||
100
applications/external/mayhem_camera/camera.h
vendored
Normal file
100
applications/external/mayhem_camera/camera.h
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// TODO
|
||||
// (DONE) Fix performance when not being charged
|
||||
// (DONE) Add UART command parsing to Esp32
|
||||
// (DONE) Prepare application and icon on github
|
||||
// (DONE) Write snapshots to SD card
|
||||
// 5. Set a constant refresh rate to the Flipper's display
|
||||
// 6. Emulate grayscale
|
||||
// 7. Photo browser app
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/elements.h>
|
||||
#include <furi_hal_uart.h>
|
||||
#include <furi_hal_console.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define THREAD_ALLOC 2048
|
||||
|
||||
#define FRAME_WIDTH 128
|
||||
#define FRAME_HEIGTH 64
|
||||
#define FRAME_BIT_DEPTH 1
|
||||
#define FRAME_BUFFER_LENGTH \
|
||||
(FRAME_WIDTH * FRAME_HEIGTH * FRAME_BIT_DEPTH / 8) // 128*64*1 / 8 = 1024
|
||||
#define ROW_BUFFER_LENGTH (FRAME_WIDTH / 8) // 128/8 = 16
|
||||
#define LAST_ROW_INDEX (FRAME_BUFFER_LENGTH - ROW_BUFFER_LENGTH) // 1024 - 16 = 1008
|
||||
#define RING_BUFFER_LENGTH (ROW_BUFFER_LENGTH + 3) // ROW_BUFFER_LENGTH + Header => 16 + 3 = 19
|
||||
#define BITMAP_HEADER_LENGTH 62
|
||||
#define IMAGE_FILE_DIRECTORY_PATH EXT_PATH("DCIM")
|
||||
|
||||
static const unsigned char bitmap_header[BITMAP_HEADER_LENGTH] = {
|
||||
0x42, 0x4D, 0x3E, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00};
|
||||
|
||||
const uint8_t _I_DolphinCommon_56x48_0[] = {
|
||||
0x01, 0x00, 0xdf, 0x00, 0x00, 0x1f, 0xfe, 0x0e, 0x05, 0x3f, 0x04, 0x06, 0x78, 0x06, 0x30, 0x20,
|
||||
0xf8, 0x00, 0xc6, 0x12, 0x1c, 0x04, 0x0c, 0x0a, 0x38, 0x08, 0x08, 0x0c, 0x60, 0xc0, 0x21, 0xe0,
|
||||
0x04, 0x0a, 0x18, 0x02, 0x1b, 0x00, 0x18, 0xa3, 0x00, 0x21, 0x90, 0x01, 0x8a, 0x20, 0x02, 0x19,
|
||||
0x80, 0x18, 0x80, 0x64, 0x09, 0x20, 0x89, 0x81, 0x8c, 0x3e, 0x41, 0xe2, 0x80, 0x50, 0x00, 0x43,
|
||||
0x08, 0x01, 0x0c, 0xfc, 0x68, 0x40, 0x61, 0xc0, 0x50, 0x30, 0x00, 0x63, 0xa0, 0x7f, 0x80, 0xc4,
|
||||
0x41, 0x19, 0x07, 0xff, 0x02, 0x06, 0x18, 0x24, 0x03, 0x41, 0xf3, 0x2b, 0x10, 0x19, 0x38, 0x10,
|
||||
0x30, 0x31, 0x7f, 0xe0, 0x34, 0x08, 0x30, 0x19, 0x60, 0x80, 0x65, 0x86, 0x0a, 0x4c, 0x0c, 0x30,
|
||||
0x81, 0xb9, 0x41, 0xa0, 0x54, 0x08, 0xc7, 0xe2, 0x06, 0x8a, 0x18, 0x25, 0x02, 0x21, 0x0f, 0x19,
|
||||
0x88, 0xd8, 0x6e, 0x1b, 0x01, 0xd1, 0x1b, 0x86, 0x39, 0x66, 0x3a, 0xa4, 0x1a, 0x50, 0x06, 0x48,
|
||||
0x18, 0x18, 0xd0, 0x03, 0x01, 0x41, 0x98, 0xcc, 0x60, 0x39, 0x01, 0x49, 0x2d, 0x06, 0x03, 0x50,
|
||||
0xf8, 0x40, 0x3e, 0x02, 0xc1, 0x82, 0x86, 0xc7, 0xfe, 0x0f, 0x28, 0x2c, 0x91, 0xd2, 0x90, 0x9a,
|
||||
0x18, 0x19, 0x3e, 0x6d, 0x73, 0x12, 0x16, 0x00, 0x32, 0x49, 0x72, 0xc0, 0x7e, 0x5d, 0x44, 0xba,
|
||||
0x2c, 0x08, 0xa4, 0xc8, 0x82, 0x06, 0x17, 0xe0, 0x81, 0x90, 0x2a, 0x40, 0x61, 0xe1, 0xa2, 0x44,
|
||||
0x0c, 0x76, 0x2b, 0xe8, 0x89, 0x26, 0x43, 0x83, 0x31, 0x8c, 0x78, 0x0c, 0xb0, 0x48, 0x10, 0x1a,
|
||||
0xe0, 0x00, 0x63,
|
||||
};
|
||||
const uint8_t* const _I_DolphinCommon_56x48[] = {_I_DolphinCommon_56x48_0};
|
||||
const Icon I_DolphinCommon_56x48 = {
|
||||
.width = 56,
|
||||
.height = 48,
|
||||
.frame_count = 1,
|
||||
.frame_rate = 0,
|
||||
.frames = _I_DolphinCommon_56x48};
|
||||
|
||||
typedef struct UartDumpModel UartDumpModel;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
View* view;
|
||||
FuriThread* worker_thread;
|
||||
FuriStreamBuffer* rx_stream;
|
||||
} UartEchoApp;
|
||||
|
||||
struct UartDumpModel {
|
||||
uint8_t pixels[FRAME_BUFFER_LENGTH];
|
||||
|
||||
bool initialized;
|
||||
//bool marauderInitialized;
|
||||
uint8_t row_ringbuffer[RING_BUFFER_LENGTH];
|
||||
uint8_t ringbuffer_index;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
|
||||
WorkerEventStop = (1 << 1),
|
||||
WorkerEventRx = (1 << 2),
|
||||
} WorkerEventFlags;
|
||||
|
||||
#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx)
|
||||
|
||||
const NotificationSequence sequence_notification = {
|
||||
&message_display_backlight_on,
|
||||
&message_delay_10,
|
||||
NULL,
|
||||
};
|
||||
BIN
applications/external/mayhem_camera/icon.png
vendored
Normal file
BIN
applications/external/mayhem_camera/icon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 472 B |
Reference in New Issue
Block a user