Kaynağa Gözat

Merge camera_suite from https://github.com/CodyTolene/Flipper-Zero-Camera-Suite

Willy-JL 2 yıl önce
ebeveyn
işleme
53ff144032

+ 1 - 1
camera_suite/application.fam

@@ -7,7 +7,7 @@ App(
     fap_category="GPIO",
     fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.",
     fap_icon="icons/camera_suite.png",
-    fap_version="1.3",
+    fap_version="1.4",
     fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam",
     name="[ESP32] Camera Suite",
     order=1,

+ 6 - 5
camera_suite/camera_suite.h

@@ -1,10 +1,5 @@
 #pragma once
 
-#include "helpers/camera_suite_storage.h"
-#include "scenes/camera_suite_scene.h"
-#include "views/camera_suite_view_guide.h"
-#include "views/camera_suite_view_start.h"
-#include "views/camera_suite_view_camera.h"
 #include <furi.h>
 #include <furi_hal.h>
 #include <gui/gui.h>
@@ -17,6 +12,12 @@
 #include <notification/notification_messages.h>
 #include <stdlib.h>
 
+#include "scenes/camera_suite_scene.h"
+#include "views/camera_suite_view_guide.h"
+#include "views/camera_suite_view_start.h"
+#include "views/camera_suite_view_camera.h"
+#include "helpers/camera_suite_storage.h"
+
 #define TAG "Camera Suite"
 
 typedef struct {

+ 15 - 2
camera_suite/docs/CHANGELOG.md

@@ -1,10 +1,23 @@
 ## Roadmap
 
-- Store images to onboard ESP32-CAM SD card (currently in progress, #24).
+- Store images to onboard ESP32-CAM SD card (partially completed, #24).
 - Camera preview GUI overlay (#21).
 - Full screen 90 degree and 270 degree fill (#6).
 
-## v1.3 (current)
+## v1.4
+
+- Add RC builds to repo.
+- Improve FAP code.
+- Improve Firmware code (requires reflash).
+- Improve Firmware flashing utility code.
+- Improve GitHub actions code.
+- Look to mitigate issue "Mirrored Image" #27.
+
+## v1.3.1 (patch)
+
+- Addressed new linting issue with "ufbt" tools.
+
+## v1.3
 
 - Important: Firmware Update Required! Ensure you update your firmware to fully utilize the new features. Backwards compatibility should be ok.
 - New Feature: Introducing the Firmware Flash utility, simplifying the firmware flashing process. Refer to the project readme for detailed instructions. (Closes #26)

+ 2 - 2
camera_suite/docs/README.md

@@ -18,7 +18,7 @@ Button mappings:
 
 **Right** = Toggle dithering on/off.
 
-**Center** = Take a picture and save to the "DCIM" folder at the root of your SD card. Image will be saved as a bitmap file with a timestamp as the filename ("YYYYMMDD-HHMMSS.bmp"). If flash is on in the settings (enabled by default) the ESP32-CAM onboard LED will light up when the picture is taken.
+**Center** = Take a picture and save to the "DCIM" folder at the root of your SD card. Image will be saved as a bitmap file with a timestamp as the filename ("YYYYMMDD-HHMMSS.bmp"). If flash is on in the settings (enabled by default) the ESP32-CAM onboard LED will light up when the camera is opened.
 
 **Back** = Go back.
 
@@ -26,7 +26,7 @@ Settings:
 
 **Orientation** = Rotate the camera image 90 degrees counter-clockwise starting at zero by default (0, 90, 180, 270). This is useful if you have your camera module mounted in a different orientation than the default.
 
-**Flash** Toggle the ESP32-CAM onboard LED on/off when taking a picture.
+**Flash** Toggle the ESP32-CAM onboard LED on/off while using the camera.
 
 **Dithering Type** Change between the Cycle Floyd–Steinberg, Jarvis-Judice-Ninke, and Stucki dithering types.
 

+ 1 - 1
camera_suite/helpers/camera_suite_storage.h

@@ -26,4 +26,4 @@ void camera_suite_save_settings(void* context);
 
 void camera_suite_read_settings(void* context);
 
-#endif
+#endif

+ 74 - 82
camera_suite/views/camera_suite_view_camera.c

@@ -60,7 +60,7 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* model) {
     }
 
     // Draw the guide if the camera is not initialized.
-    if(!uartDumpModel->initialized) {
+    if(!uartDumpModel->is_initialized) {
         canvas_draw_icon(canvas, 74, 16, &I_DolphinCommon_56x48);
         canvas_set_font(canvas, FontSecondary);
         canvas_draw_str(canvas, 8, 12, "Connect the ESP32-CAM");
@@ -71,7 +71,7 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* model) {
     }
 }
 
-static void save_image(void* model) {
+static void save_image_to_flipper_sd_card(void* model) {
     furi_assert(model);
 
     UartDumpModel* uartDumpModel = model;
@@ -116,7 +116,7 @@ static void save_image(void* model) {
     // Free the file name after use.
     furi_string_free(file_name);
 
-    if(!uartDumpModel->inverted) {
+    if(!uartDumpModel->is_inverted) {
         for(size_t i = 0; i < FRAME_BUFFER_LENGTH; ++i) {
             uartDumpModel->pixels[i] = ~uartDumpModel->pixels[i];
         }
@@ -155,14 +155,14 @@ static void
     furi_assert(model);
     furi_assert(instance_context);
 
+    model->is_dithering_enabled = true;
+    model->is_inverted = false;
+    uint32_t orientation = instance_context->orientation;
+    model->orientation = orientation;
+
     for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) {
         model->pixels[i] = 0;
     }
-
-    uint32_t orientation = instance_context->orientation;
-    model->flash = instance_context->flash;
-    model->inverted = false;
-    model->orientation = orientation;
 }
 
 static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
@@ -187,157 +187,156 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
             break;
         }
     } else if(event->type == InputTypePress) {
-        uint8_t data[1] = {'X'};
         switch(event->key) {
-        // Camera: Stop stream.
         case InputKeyBack: {
-            // Set the camera flash to off.
-            uint8_t flash_off = 'f';
-            furi_hal_uart_tx(UART_CH, &flash_off, 1);
-            furi_delay_ms(50);
-            // Stop camera stream.
-            uint8_t stop_camera = 's';
-            furi_hal_uart_tx(UART_CH, &stop_camera, 1);
-            // Go back to the main menu.
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+
+                    // Stop camera stream.
+                    furi_hal_uart_tx(UART_CH, (uint8_t[]){'s'}, 1);
+                    furi_delay_ms(50);
+
+                    // Go back to the main menu.
                     instance->callback(CameraSuiteCustomEventSceneCameraBack, instance->context);
                 },
                 true);
             break;
         }
-        // Camera: Toggle invert on the ESP32-CAM.
         case InputKeyLeft: {
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
-                    UNUSED(model);
+                    // Play sound.
                     camera_suite_play_happy_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
-                    if(model->inverted) {
-                        data[0] = 'i';
-                        model->inverted = false;
+
+                    if(model->is_inverted) {
+                        // Camera: Set invert to false on the ESP32-CAM.
+                        furi_hal_uart_tx(UART_CH, (uint8_t[]){'i'}, 1);
+                        furi_delay_ms(50);
+
+                        model->is_inverted = false;
                     } else {
-                        data[0] = 'I';
-                        model->inverted = true;
+                        // Camera: Set invert to true on the ESP32-CAM.
+                        furi_hal_uart_tx(UART_CH, (uint8_t[]){'I'}, 1);
+                        furi_delay_ms(50);
+
+                        model->is_inverted = true;
                     }
+
                     instance->callback(CameraSuiteCustomEventSceneCameraLeft, instance->context);
                 },
                 true);
             break;
         }
-        // Camera: Enable/disable dithering.
         case InputKeyRight: {
-            data[0] = '>';
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
-                    UNUSED(model);
+                    // Play sound.
                     camera_suite_play_happy_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
+
+                    if(model->is_dithering_enabled) {
+                        // Camera: Disable dithering.
+                        furi_hal_uart_tx(UART_CH, (uint8_t[]){'d'}, 1);
+                        furi_delay_ms(50);
+
+                        model->is_dithering_enabled = false;
+                    } else {
+                        // Camera: Enable dithering.
+                        furi_hal_uart_tx(UART_CH, (uint8_t[]){'D'}, 1);
+                        furi_delay_ms(50);
+
+                        model->is_dithering_enabled = true;
+                    }
+
                     instance->callback(CameraSuiteCustomEventSceneCameraRight, instance->context);
                 },
                 true);
             break;
         }
-        // Camera: Increase contrast.
         case InputKeyUp: {
-            data[0] = 'C';
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+
+                    // Play sound.
                     camera_suite_play_happy_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
+
+                    // Camera: Increase contrast.
+                    furi_hal_uart_tx(UART_CH, (uint8_t[]){'C'}, 1);
+                    furi_delay_ms(50);
+
                     instance->callback(CameraSuiteCustomEventSceneCameraUp, instance->context);
                 },
                 true);
             break;
         }
-        // Camera: Reduce contrast.
         case InputKeyDown: {
-            data[0] = 'c';
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+
+                    // Play sound.
                     camera_suite_play_happy_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
+
+                    // Camera: Reduce contrast.
+                    furi_hal_uart_tx(UART_CH, (uint8_t[]){'c'}, 1);
+                    furi_delay_ms(50);
+
                     instance->callback(CameraSuiteCustomEventSceneCameraDown, instance->context);
                 },
                 true);
             break;
         }
-        // Camera: Take picture.
         case InputKeyOk: {
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
+                    // Play sound.
                     camera_suite_play_long_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
 
-                    // Save picture directly to ESP32-CAM.
-                    // @todo - Add this functionality.
-                    // data[0] = 'P';
-                    // furi_hal_uart_tx(UART_CH, data, 1);
-
-                    // if(model->flash) {
-                    //     data[0] = 'F';
-                    //     furi_hal_uart_tx(UART_CH, data, 1);
-                    //     furi_delay_ms(50);
-                    // }
+                    // @todo - Save picture directly to ESP32-CAM.
+                    // furi_hal_uart_tx(UART_CH, (uint8_t[]){'P'}, 1);
 
-                    // Take a picture.
-                    save_image(model);
+                    // Save currently displayed image to the Flipper Zero SD card.
+                    save_image_to_flipper_sd_card(model);
 
-                    // if(model->flash) {
-                    //     data[0] = 'f';
-                    // }
                     instance->callback(CameraSuiteCustomEventSceneCameraOk, instance->context);
                 },
                 true);
             break;
         }
-        // Camera: Do nothing.
         case InputKeyMAX:
         default: {
             break;
         }
         }
-
-        if(data[0] != 'X') {
-            // Send `data` to the ESP32-CAM.
-            furi_hal_uart_tx(UART_CH, data, 1);
-        }
     }
-    return true;
+    return false;
 }
 
 static void camera_suite_view_camera_exit(void* context) {
-    UNUSED(context);
-
-    // Set the camera flash to off.
-    uint8_t flash_off = 'f';
-    furi_hal_uart_tx(UART_CH, &flash_off, 1);
-    furi_delay_ms(50);
-
-    // Stop camera stream.
-    uint8_t stop_camera = 's';
-    furi_hal_uart_tx(UART_CH, &stop_camera, 1);
-    furi_delay_ms(50);
+    furi_assert(context);
 }
 
 static void camera_suite_view_camera_enter(void* context) {
@@ -350,30 +349,23 @@ static void camera_suite_view_camera_enter(void* context) {
     CameraSuite* instance_context = instance->context;
 
     // Start camera stream.
-    uint8_t start_camera = 'S';
-    furi_hal_uart_tx(UART_CH, &start_camera, 1);
-    furi_delay_ms(75);
+    furi_hal_uart_tx(UART_CH, (uint8_t[]){'S'}, 1);
+    furi_delay_ms(50);
 
     // Get/set dither type.
     uint8_t dither_type = instance_context->dither;
     furi_hal_uart_tx(UART_CH, &dither_type, 1);
-    furi_delay_ms(75);
+    furi_delay_ms(50);
 
     // Make sure the camera is not inverted.
-    uint8_t invert_camera = 'i';
-    furi_hal_uart_tx(UART_CH, &invert_camera, 1);
-    furi_delay_ms(75);
+    furi_hal_uart_tx(UART_CH, (uint8_t[]){'i'}, 1);
+    furi_delay_ms(50);
 
-    // Toggle flash on or off based on the current state. This will keep the
-    // flash on initially. However we're toggling it for now on input.
+    // Toggle flash on or off based on the current state. If the user has this
+    // on the flash will stay on the entire time the user is in the camera view.
     uint8_t flash_state = instance_context->flash ? 'F' : 'f';
     furi_hal_uart_tx(UART_CH, &flash_state, 1);
-    furi_delay_ms(75);
-
-    // Make sure we start with the flash off.
-    // uint8_t flash_state = 'f';
-    // furi_hal_uart_tx(UART_CH, &flash_state, 1);
-    // furi_delay_ms(75);
+    furi_delay_ms(50);
 
     with_view_model(
         instance->view,
@@ -429,7 +421,7 @@ static void process_ringbuffer(UartDumpModel* model, uint8_t const byte) {
     // Check whether the ring buffer is filled.
     if(model->ringbuffer_index >= RING_BUFFER_LENGTH) {
         model->ringbuffer_index = 0; // Reset the ring buffer index.
-        model->initialized = true; // Set the connection as successfully established.
+        model->is_initialized = true; // Set the connection as successfully established.
 
         // Compute the starting index for the row in the pixel buffer.
         size_t row_start_index = model->row_identifier * ROW_BUFFER_LENGTH;

+ 5 - 4
camera_suite/views/camera_suite_view_camera.h

@@ -1,6 +1,5 @@
 #pragma once
 
-#include "../helpers/camera_suite_custom_event.h"
 #include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal_console.h>
@@ -16,6 +15,8 @@
 #include <storage/filesystem_api_defines.h>
 #include <storage/storage.h>
 
+#include "../helpers/camera_suite_custom_event.h"
+
 #include <xtreme.h>
 
 #define UART_CH \
@@ -59,9 +60,9 @@ typedef struct CameraSuiteViewCamera {
 } CameraSuiteViewCamera;
 
 typedef struct UartDumpModel {
-    bool flash;
-    bool initialized;
-    bool inverted;
+    bool is_dithering_enabled;
+    bool is_initialized;
+    bool is_inverted;
     int rotation_angle;
     uint32_t orientation;
     uint8_t pixels[FRAME_BUFFER_LENGTH];