Просмотр исходного кода

Stabilize firmware and software builds.

Cody Tolene 2 лет назад
Родитель
Сommit
bfade7f5f1

+ 1 - 1
.gitignore

@@ -1,4 +1,5 @@
 *.exe
+*.flag
 *.zip
 .DS_Store
 .clang-format
@@ -13,4 +14,3 @@ fap/.clang-format
 fap/.editorconfig
 fap/.vscode
 fap/dist/*
-firmware/compile.flag

+ 18 - 0
README.md

@@ -74,6 +74,22 @@ On the ESP32-CAM module itself you'll also need to connect the `IO0` pin to `GND
 
 ## Firmware Installation <a name="firmware-installation"></a>
 
+<details>
+
+<summary>Windows 10+ Script (the new, easy way)</summary>
+
+1. Download/clone this repository to your computer.
+2. Run the script found at the root of this directory: `firmware-flash.bat`.
+3. Follow the on screen instructions to continue.
+
+That's it, let me know if you have any issues!
+
+</details>
+
+<details>
+
+<summary>Arduino IDE (the old, hard way)</summary>
+
 1. Download and install the Arduino IDE from [here][arduino-ide].
 2. Go to the [releases section][flipper-zero-camera-suite-releases] for this repo and download the `esp32_cam_uart_stream.zip` file.
 3. Extract the contents of `esp32_cam_uart_stream.zip` to disk. Be sure to keep the `.ino` file nested in the folder with the same name.
@@ -96,6 +112,8 @@ On the ESP32-CAM module itself you'll also need to connect the `IO0` pin to `GND
 
 Note the upload may fail a few times, this is normal, try again. If it still fails, try pressing the RST button on the back of the ESP32-CAM module again or checking your connections.
 
+</details>
+
 <p align="right">[ <a href="#index">Back to top</a> ]</p>
 
 ## Software Installation <a name="software-installation"></a>

+ 3 - 3
arduino-cli.yaml

@@ -7,9 +7,9 @@ build_cache:
 daemon:
   port: "50051"
 directories:
-  data: C:\temp\arduino-cli\data
-  downloads: C:\temp\arduino-cli\staging
-  user: C:\temp\arduino-cli\user
+  data: C:\Users\NULL\AppData\Local\Temp\arduino-cli\data
+  downloads: C:\Users\NULL\AppData\Local\Temp\arduino-cli\downloads
+  user: C:\Users\NULL\AppData\Local\Temp\arduino-cli\user
 library:
   enable_unsafe_install: false
 logging:

+ 87 - 32
fap/views/camera_suite_view_camera.c

@@ -8,17 +8,6 @@
 #include "../helpers/camera_suite_speaker.h"
 #include "../helpers/camera_suite_led.h"
 
-void camera_suite_view_camera_set_callback(
-    CameraSuiteViewCamera* instance,
-    CameraSuiteViewCameraCallback callback,
-    void* context) {
-    furi_assert(instance);
-    furi_assert(callback);
-    instance->callback = callback;
-    instance->context = context;
-}
-
-// Function to draw pixels on the canvas based on camera orientation
 static void draw_pixel_by_orientation(Canvas* canvas, uint8_t x, uint8_t y, uint8_t orientation) {
     furi_assert(canvas);
     furi_assert(x);
@@ -127,6 +116,12 @@ static void save_image(void* model) {
     // Free the file name after use.
     furi_string_free(file_name);
 
+    if(!uartDumpModel->inverted) {
+        for(size_t i = 0; i < FRAME_BUFFER_LENGTH; ++i) {
+            uartDumpModel->pixels[i] = ~uartDumpModel->pixels[i];
+        }
+    }
+
     // If the file was opened successfully, write the bitmap header and the
     // image data.
     if(result) {
@@ -155,12 +150,19 @@ static void save_image(void* model) {
     storage_file_free(file);
 }
 
-static void camera_suite_view_camera_model_init(UartDumpModel* const model) {
+static void
+    camera_suite_view_camera_model_init(UartDumpModel* const model, CameraSuite* instance_context) {
     furi_assert(model);
+    furi_assert(instance_context);
 
     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) {
@@ -185,10 +187,17 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
             break;
         }
     } else if(event->type == InputTypePress) {
-        uint8_t data[1];
+        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(FuriHalUartIdUSART1, &flash_off, 1);
+            furi_delay_ms(50);
+            // Stop camera stream.
+            uint8_t stop_camera = 's';
+            furi_hal_uart_tx(FuriHalUartIdUSART1, &stop_camera, 1);
             // Go back to the main menu.
             with_view_model(
                 instance->view,
@@ -202,14 +211,21 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
         }
         // Camera: Toggle invert on the ESP32-CAM.
         case InputKeyLeft: {
-            data[0] = '<';
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
+                    UNUSED(model);
                     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;
+                    } else {
+                        data[0] = 'I';
+                        model->inverted = true;
+                    }
                     instance->callback(CameraSuiteCustomEventSceneCameraLeft, instance->context);
                 },
                 true);
@@ -265,10 +281,6 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
         }
         // Camera: Take picture.
         case InputKeyOk: {
-            // Save picture directly to ESP32-CAM.
-            // @todo - Add this functionality.
-            // data[0] = 'P';
-            // furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
@@ -276,11 +288,28 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
                     camera_suite_play_long_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
-                    // Take a picture if the data is 'P'.
+
+                    // Save picture directly to ESP32-CAM.
+                    // @todo - Add this functionality.
+                    // data[0] = 'P';
+                    // furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
+
+                    // if(model->flash) {
+                    //     data[0] = 'F';
+                    //     furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
+                    //     furi_delay_ms(50);
+                    // }
+
+                    // Take a picture.
                     save_image(model);
+
+                    // if(model->flash) {
+                    //     data[0] = 'f';
+                    // }
                     instance->callback(CameraSuiteCustomEventSceneCameraOk, instance->context);
                 },
                 true);
+            break;
         }
         // Camera: Do nothing.
         case InputKeyMAX:
@@ -289,8 +318,10 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
         }
         }
 
-        // Send `data` to the ESP32-CAM.
-        furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
+        if(data[0] != 'X') {
+            // Send `data` to the ESP32-CAM.
+            furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
+        }
     }
     return true;
 }
@@ -298,6 +329,11 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
 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(FuriHalUartIdUSART1, &flash_off, 1);
+    furi_delay_ms(50);
+
     // Stop camera stream.
     uint8_t stop_camera = 's';
     furi_hal_uart_tx(FuriHalUartIdUSART1, &stop_camera, 1);
@@ -316,17 +352,33 @@ static void camera_suite_view_camera_enter(void* context) {
     // Start camera stream.
     uint8_t start_camera = 'S';
     furi_hal_uart_tx(FuriHalUartIdUSART1, &start_camera, 1);
-    furi_delay_ms(50);
+    furi_delay_ms(75);
 
     // Get/set dither type.
     uint8_t dither_type = instance_context->dither;
     furi_hal_uart_tx(FuriHalUartIdUSART1, &dither_type, 1);
-    furi_delay_ms(50);
+    furi_delay_ms(75);
+
+    // Make sure the camera is not inverted.
+    uint8_t invert_camera = 'i';
+    furi_hal_uart_tx(FuriHalUartIdUSART1, &invert_camera, 1);
+    furi_delay_ms(75);
+
+    // 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.
+    uint8_t flash_state = instance_context->flash ? 'F' : 'f';
+    furi_hal_uart_tx(FuriHalUartIdUSART1, &flash_state, 1);
+    furi_delay_ms(75);
+
+    // Make sure we start with the flash off.
+    // uint8_t flash_state = 'f';
+    // furi_hal_uart_tx(FuriHalUartIdUSART1, &flash_state, 1);
+    // furi_delay_ms(75);
 
     with_view_model(
         instance->view,
         UartDumpModel * model,
-        { camera_suite_view_camera_model_init(model); },
+        { camera_suite_view_camera_model_init(model, instance_context); },
         true);
 }
 
@@ -463,13 +515,6 @@ CameraSuiteViewCamera* camera_suite_view_camera_alloc() {
     // Set exit callback
     view_set_exit_callback(instance->view, camera_suite_view_camera_exit);
 
-    // Initialize camera model
-    with_view_model(
-        instance->view,
-        UartDumpModel * model,
-        { camera_suite_view_camera_model_init(model); },
-        true);
-
     // Allocate a thread for this camera to run on.
     FuriThread* thread = furi_thread_alloc_ex("UsbUartWorker", 2048, camera_worker, instance);
     instance->worker_thread = thread;
@@ -500,7 +545,7 @@ void camera_suite_view_camera_free(CameraSuiteViewCamera* instance) {
     furi_stream_buffer_free(instance->rx_stream);
 
     // Re-enable the console.
-    furi_hal_console_enable();
+    // furi_hal_console_enable();
 
     with_view_model(
         instance->view, UartDumpModel * model, { UNUSED(model); }, true);
@@ -512,3 +557,13 @@ View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* instance) {
     furi_assert(instance);
     return instance->view;
 }
+
+void camera_suite_view_camera_set_callback(
+    CameraSuiteViewCamera* instance,
+    CameraSuiteViewCameraCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}

+ 3 - 1
fap/views/camera_suite_view_camera.h

@@ -48,11 +48,13 @@ typedef struct CameraSuiteViewCamera {
     CameraSuiteViewCameraCallback callback;
     FuriStreamBuffer* rx_stream;
     FuriThread* worker_thread;
+    NotificationApp* notification;
     View* view;
     void* context;
 } CameraSuiteViewCamera;
 
 typedef struct UartDumpModel {
+    bool flash;
     bool initialized;
     bool inverted;
     int rotation_angle;
@@ -65,8 +67,8 @@ typedef struct UartDumpModel {
 
 // Function Prototypes
 CameraSuiteViewCamera* camera_suite_view_camera_alloc();
-void camera_suite_view_camera_free(CameraSuiteViewCamera* camera_suite_static);
 View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* camera_suite_static);
+void camera_suite_view_camera_free(CameraSuiteViewCamera* camera_suite_static);
 void camera_suite_view_camera_set_callback(
     CameraSuiteViewCamera* camera_suite_view_camera,
     CameraSuiteViewCameraCallback callback,

+ 1 - 1
firmware-flash.bat

@@ -5,7 +5,7 @@ rem λ
 
 set CLI_FOUND_FOLLOW_UP=0
 set CLI_TEMP=%TEMP%\arduino-cli
-set COMPILE_FLAG=firmware\compile.flag
+set COMPILE_FLAG=firmware\.compile.flag
 set CONFIG_FILE=--config-file .\arduino-cli.yaml
 set DEFAULT_BOARD_FQBN=esp32:esp32:esp32cam
 set FIRMWARE_SRC=firmware\firmware.ino

+ 0 - 59
firmware-update/camera_model.h

@@ -1,59 +0,0 @@
-#ifndef CAMERA_MODEL_H
-#define CAMERA_MODEL_H
-
-#include <stdint.h>
-
-/**
- * The dithering algorithms available.
- */
-enum DitheringAlgorithm {
-    FLOYD_STEINBERG,
-    JARVIS_JUDICE_NINKE,
-    STUCKI,
-};
-
-class CameraModel {
-  private:
-    static CameraModel* instance;
-    // Private constructor to prevent instantiation.
-    CameraModel();
-    /**
-     * Flag to enable or disable dithering.
-     */
-    bool isDitheringDisabled;
-    /**
-     * Flag to represent the flash state when saving pictures to the Flipper.
-     */
-    bool isFlashEnabled;
-    /**
-     * Flag to invert pixel colors.
-     */
-    bool isInverted;
-    /**
-     * Flag to stop or start the stream.
-     */
-    bool isStreamEnabled;
-    /**
-     * Holds the currently selected dithering algorithm.
-     */
-    DitheringAlgorithm ditherAlgorithm;
-
-  public:
-    static CameraModel* getInstance();
-
-    // Getter functions
-    bool getIsDitheringDisabled();
-    bool getIsFlashEnabled();
-    bool getIsInverted();
-    bool getIsStreamEnabled();
-    DitheringAlgorithm getDitherAlgorithm();
-
-    // Setter functions
-    void setIsDitheringDisabled(bool value);
-    void setIsFlashEnabled(bool value);
-    void setIsInverted(bool value);
-    void setIsStreamEnabled(bool value);
-    void setDitherAlgorithm(DitheringAlgorithm value);
-};
-
-#endif

+ 0 - 45
firmware-update/camera_model.ino

@@ -1,45 +0,0 @@
-#include "camera_model.h"
-
-CameraModel* CameraModel::instance = nullptr;
-
-CameraModel::CameraModel() {
-    // Set up defaults.
-    isDitheringDisabled = false;
-    isFlashEnabled = false;
-    isInverted = false;
-    isStreamEnabled = true;
-    ditherAlgorithm = FLOYD_STEINBERG;
-}
-
-CameraModel* CameraModel::getInstance() {
-    if (instance == nullptr) {
-        instance = new CameraModel();
-    }
-    return instance;
-}
-
-// Getter implementations
-bool CameraModel::getIsDitheringDisabled() { return isDitheringDisabled; }
-
-bool CameraModel::getIsFlashEnabled() { return isFlashEnabled; }
-
-bool CameraModel::getIsInverted() { return isInverted; }
-
-bool CameraModel::getIsStreamEnabled() { return isStreamEnabled; }
-
-DitheringAlgorithm CameraModel::getDitherAlgorithm() { return ditherAlgorithm; }
-
-// Setter implementations
-void CameraModel::setIsDitheringDisabled(bool value) {
-    isDitheringDisabled = value;
-}
-
-void CameraModel::setIsFlashEnabled(bool value) { isFlashEnabled = value; }
-
-void CameraModel::setIsInverted(bool value) { isInverted = value; }
-
-void CameraModel::setIsStreamEnabled(bool value) { isStreamEnabled = value; }
-
-void CameraModel::setDitherAlgorithm(DitheringAlgorithm algorithm) {
-    ditherAlgorithm = algorithm;
-}

+ 0 - 67
firmware-update/firmware-update.ino

@@ -1,67 +0,0 @@
-#include <esp_camera.h>
-
-#include "camera_model.h"
-#include "initialize.h"
-#include "process_image.h"
-#include "serial_commands.h"
-
-camera_config_t config;
-
-// Entry point of the program.
-void setup() {
-    // Set up the camera model.
-    CameraModel* model = CameraModel::getInstance();
-
-    // Set up the model defaults.
-    model->setIsDitheringDisabled(false);
-    model->setIsInverted(false);
-    model->setIsFlashEnabled(false);
-    model->setIsStreamEnabled(true);
-    model->setDitherAlgorithm(FLOYD_STEINBERG);
-
-    // Set initial camera configurations for grayscale.
-    config.ledc_channel = LEDC_CHANNEL_0;
-    config.ledc_timer = LEDC_TIMER_0;
-    config.pin_d0 = Y2_GPIO_NUM;
-    config.pin_d1 = Y3_GPIO_NUM;
-    config.pin_d2 = Y4_GPIO_NUM;
-    config.pin_d3 = Y5_GPIO_NUM;
-    config.pin_d4 = Y6_GPIO_NUM;
-    config.pin_d5 = Y7_GPIO_NUM;
-    config.pin_d6 = Y8_GPIO_NUM;
-    config.pin_d7 = Y9_GPIO_NUM;
-    config.pin_xclk = XCLK_GPIO_NUM;
-    config.pin_pclk = PCLK_GPIO_NUM;
-    config.pin_vsync = VSYNC_GPIO_NUM;
-    config.pin_href = HREF_GPIO_NUM;
-    config.pin_sscb_sda = SIOD_GPIO_NUM;
-    config.pin_sscb_scl = SIOC_GPIO_NUM;
-    config.pin_pwdn = PWDN_GPIO_NUM;
-    config.pin_reset = RESET_GPIO_NUM;
-    config.xclk_freq_hz = 20000000;
-    config.pixel_format = PIXFORMAT_GRAYSCALE;
-    config.frame_size = FRAMESIZE_QQVGA;
-    config.fb_count = 1;
-
-    // Begin serial communication.
-    Serial.begin(230400); // 115200
-
-    // Initialize the camera.
-    initialize(&config);
-}
-
-// Main loop of the program.
-void loop() {
-    // Get the camera model reference.
-    CameraModel* model = CameraModel::getInstance();
-    if (model->getIsStreamEnabled()) {
-        camera_fb_t* frame_buffer = esp_camera_fb_get();
-        if (frame_buffer) {
-            process_image(frame_buffer);
-            // Return the frame buffer back to the camera driver.
-            esp_camera_fb_return(frame_buffer);
-        }
-        delay(25);
-    }
-    serial_commands();
-}

+ 0 - 12
firmware-update/initialize.h

@@ -1,12 +0,0 @@
-#ifndef INITIALIZE_H
-#define INITIALIZE_H
-
-#include <FS.h>
-#include <esp_camera.h>
-
-#include "camera_model.h"
-#include "pins.h"
-
-void initialize(camera_config_t* config);
-
-#endif

+ 0 - 41
firmware-update/initialize.ino

@@ -1,41 +0,0 @@
-#include "initialize.h"
-
-void initialize(camera_config_t* config) {
-    // Initialize camera.
-    esp_err_t err = esp_camera_init(config);
-    if (err != ESP_OK) {
-        return;
-    }
-
-    // Get the camera model reference.
-    CameraModel* model = CameraModel::getInstance();
-
-    // Check if the flash is already on, if it is turn it off.
-    if (model->getIsFlashEnabled()) {
-        pinMode(FLASH_GPIO_NUM, OUTPUT);
-        digitalWrite(FLASH_GPIO_NUM, LOW);
-        model->setIsFlashEnabled(false);
-    }
-
-    // Get the camera sensor reference.
-    sensor_t* cam = esp_camera_sensor_get();
-
-    // Set up the frame buffer reference.
-    camera_fb_t* frame_buffer = esp_camera_fb_get();
-
-    // Set initial brightness.
-    cam->set_brightness(cam, 0);
-
-    // Set initial contrast.
-    cam->set_contrast(cam, 0);
-
-    // Set initial rotation.
-    cam->set_vflip(cam, true);
-    cam->set_hmirror(cam, true);
-
-    // Set initial saturation.
-    cam->set_saturation(cam, 0);
-
-    // Set initial sharpness.
-    cam->set_sharpness(cam, 0);
-}

+ 22 - 0
firmware/camera.h

@@ -0,0 +1,22 @@
+#ifndef INITIALIZE_CAMERA_H
+#define INITIALIZE_CAMERA_H
+
+#include <FS.h>
+#include <esp_camera.h>
+
+#include "camera_config.h"
+#include "camera_model.h"
+
+/** Initialize the camera. */
+void initialize_camera();
+
+/** Turn the flash off. */
+void toggle_flash_off();
+
+/** Turn the flash on. */
+void toggle_flash_on();
+
+/** If the flash state ever gets out of sync with the camera model, fix it. */
+void handle_flash_state();
+
+#endif

+ 47 - 0
firmware/camera.ino

@@ -0,0 +1,47 @@
+#include "camera.h"
+
+void initialize_camera() {
+    // Initialize camera.
+    esp_err_t err = esp_camera_init(&camera_config);
+    if (err != ESP_OK) {
+        return;
+    }
+
+    // Check if the flash is already on, if it is turn it off.
+    if (camera_model.isFlashEnabled) {
+        toggle_flash_off();
+    }
+
+    // Get the camera sensor reference.
+    sensor_t* cam = esp_camera_sensor_get();
+
+    cam->set_contrast(cam, 0); // Set initial contrast.
+    cam->set_vflip(cam, true); // Set initial vertical flip.
+
+    // cam->set_hmirror(cam, false); // Set initial horizontal mirror.
+    // cam->set_brightness(cam, 0);  // Set initial brightness.
+    // cam->set_saturation(cam, 0); // Set initial saturation.
+    // cam->set_sharpness(cam, 0); // Set initial sharpness.
+}
+
+void toggle_flash_off() {
+    pinMode(FLASH_GPIO_NUM, OUTPUT);
+    digitalWrite(FLASH_GPIO_NUM, LOW);
+    camera_model.isFlashEnabled = false;
+}
+
+void toggle_flash_on() {
+    pinMode(FLASH_GPIO_NUM, OUTPUT);
+    digitalWrite(FLASH_GPIO_NUM, HIGH);
+    camera_model.isFlashEnabled = true;
+}
+
+void handle_flash_state() {
+    // If the flash state ever gets out of sync with the camera model, fix it.
+    if (!camera_model.isFlashEnabled) {
+        int flashState = digitalRead(FLASH_GPIO_NUM);
+        if (flashState == HIGH) {
+            toggle_flash_off();
+        }
+    }
+}

+ 14 - 0
firmware/camera_config.h

@@ -0,0 +1,14 @@
+#ifndef CAMERA_CONFIG_H
+#define CAMERA_CONFIG_H
+
+#include <esp_camera.h>
+
+#include "pins.h"
+
+/** The camera configuration model. */
+extern camera_config_t camera_config;
+
+/** Initialize the camera configuration. */
+void initialize_camera_config();
+
+#endif

+ 30 - 0
firmware/camera_config.ino

@@ -0,0 +1,30 @@
+#include "camera_config.h"
+
+/** The camera configuration model. */
+camera_config_t camera_config;
+
+void initialize_camera_config() {
+    // Set initial camera configurations.
+    camera_config.ledc_channel = LEDC_CHANNEL_0;
+    camera_config.ledc_timer = LEDC_TIMER_0;
+    camera_config.pin_d0 = Y2_GPIO_NUM;
+    camera_config.pin_d1 = Y3_GPIO_NUM;
+    camera_config.pin_d2 = Y4_GPIO_NUM;
+    camera_config.pin_d3 = Y5_GPIO_NUM;
+    camera_config.pin_d4 = Y6_GPIO_NUM;
+    camera_config.pin_d5 = Y7_GPIO_NUM;
+    camera_config.pin_d6 = Y8_GPIO_NUM;
+    camera_config.pin_d7 = Y9_GPIO_NUM;
+    camera_config.pin_xclk = XCLK_GPIO_NUM;
+    camera_config.pin_pclk = PCLK_GPIO_NUM;
+    camera_config.pin_vsync = VSYNC_GPIO_NUM;
+    camera_config.pin_href = HREF_GPIO_NUM;
+    camera_config.pin_sscb_sda = SIOD_GPIO_NUM;
+    camera_config.pin_sscb_scl = SIOC_GPIO_NUM;
+    camera_config.pin_pwdn = PWDN_GPIO_NUM;
+    camera_config.pin_reset = RESET_GPIO_NUM;
+    camera_config.xclk_freq_hz = 20000000;
+    camera_config.pixel_format = PIXFORMAT_GRAYSCALE;
+    camera_config.frame_size = FRAMESIZE_QQVGA;
+    camera_config.fb_count = 1;
+}

+ 43 - 0
firmware/camera_model.h

@@ -0,0 +1,43 @@
+#ifndef CAMERA_MODEL_H
+#define CAMERA_MODEL_H
+
+#include <stdint.h>
+
+/**
+ * The dithering algorithms available.
+ */
+typedef enum {
+    FLOYD_STEINBERG,
+    JARVIS_JUDICE_NINKE,
+    STUCKI,
+} DitheringAlgorithm;
+
+typedef struct {
+    /**
+     * Flag to enable or disable dithering.
+     */
+    bool isDitheringEnabled;
+    /**
+     * Flag to represent the flash state when saving pictures to the Flipper.
+     */
+    bool isFlashEnabled;
+    /**
+     * Flag to invert pixel colors.
+     */
+    bool isInvertEnabled;
+    /**
+     * Flag to stop or start the stream.
+     */
+    bool isStreamEnabled;
+    /**
+     * Holds the currently selected dithering algorithm.
+     */
+    DitheringAlgorithm ditherAlgorithm;
+} CameraModel;
+
+/** The camera model. */
+extern CameraModel camera_model;
+
+void initialize_camera_model();
+
+#endif

+ 13 - 0
firmware/camera_model.ino

@@ -0,0 +1,13 @@
+#include "camera_model.h"
+
+/** The camera model. */
+CameraModel camera_model;
+
+void initialize_camera_model() {
+    // Set up camera model defaults.
+    camera_model.isDitheringEnabled = true;
+    camera_model.isFlashEnabled = false;
+    camera_model.isInvertEnabled = false;
+    camera_model.isStreamEnabled = true;
+    camera_model.ditherAlgorithm = FLOYD_STEINBERG;
+}

+ 3 - 2
firmware-update/dithering.h → firmware/dither_image.h

@@ -1,10 +1,11 @@
-#ifndef DITHERING_H
-#define DITHERING_H
+#ifndef DITHER_IMAGE_H
+#define DITHER_IMAGE_H
 
 #include <esp_camera.h>
 
 #include "camera_model.h"
 
+/** Dither the image using the selected algorithm. */
 void dither_image(camera_fb_t* frame_buffer);
 
 #endif

+ 2 - 5
firmware-update/dithering.ino → firmware/dither_image.ino

@@ -1,9 +1,6 @@
-#include "dithering.h"
+#include "dither_image.h"
 
 void dither_image(camera_fb_t* frame_buffer) {
-    // Get the camera model reference.
-    CameraModel* model = CameraModel::getInstance();
-
     for (uint8_t y = 0; y < frame_buffer->height; ++y) {
         for (uint8_t x = 0; x < frame_buffer->width; ++x) {
             size_t current = (y * frame_buffer->width) + x;
@@ -13,7 +10,7 @@ void dither_image(camera_fb_t* frame_buffer) {
             int8_t quant_error = oldpixel - newpixel;
 
             // Apply error diffusion based on the selected algorithm
-            switch (model->getDitherAlgorithm()) {
+            switch (camera_model.ditherAlgorithm) {
             case JARVIS_JUDICE_NINKE:
                 frame_buffer->buf[(y * frame_buffer->width) + x + 1] +=
                     quant_error * 7 / 48;

+ 12 - 0
firmware/firmware.h

@@ -0,0 +1,12 @@
+#ifndef FIRMWARE_H
+#define FIRMWARE_H
+
+#include <esp_camera.h>
+
+#include "camera.h"
+#include "camera_config.h"
+#include "camera_model.h"
+#include "process_image.h"
+#include "process_serial_commands.h"
+
+#endif

+ 10 - 380
firmware/firmware.ino

@@ -1,84 +1,22 @@
-#include <FS.h>
-#include <esp_camera.h>
-#include <stdint.h>
+#include "firmware.h"
 
-// #include <SD_MMC.h>
-
-// Define Pin numbers used by the camera.
-#define FLASH_GPIO_NUM 4
-#define HREF_GPIO_NUM 23
-#define PCLK_GPIO_NUM 22
-#define PWDN_GPIO_NUM 32
-#define RESET_GPIO_NUM -1
-#define SIOC_GPIO_NUM 27
-#define SIOD_GPIO_NUM 26
-#define VSYNC_GPIO_NUM 25
-#define XCLK_GPIO_NUM 0
-#define Y2_GPIO_NUM 5
-#define Y3_GPIO_NUM 18
-#define Y4_GPIO_NUM 19
-#define Y5_GPIO_NUM 21
-#define Y6_GPIO_NUM 36
-#define Y7_GPIO_NUM 39
-#define Y8_GPIO_NUM 34
-#define Y9_GPIO_NUM 35
-
-/** Initialize the camera model. */
-void initialize_camera();
-
-/** Dither the image using the selected algorithm. */
-void dither_image(camera_fb_t* frame_buffer);
-
-/** Process and send grayscale images back to the Flipper Zero. */
-void process_image(camera_fb_t* frame_buffer);
-
-/** Handle the serial input commands coming from the Flipper Zero. */
-void process_serial_commands();
-
-/**
- * Save the current picture to the onboard SD card.
- * @todo - Future feature.
- */
-// void save_picture_to_sd();
-
-/** Turn the flash off. */
-void toggle_flash_off();
-
-/** Turn the flash on. */
-void toggle_flash_on();
-
-/** The dithering algorithms available. */
-typedef enum {
-    FLOYD_STEINBERG,
-    JARVIS_JUDICE_NINKE,
-    STUCKI,
-} DitheringAlgorithm;
-
-/** Flag to enable or disable dithering. */
-bool isDitheringEnabled = true;
-/** Flag to represent the flash state when saving pictures to the Flipper. */
-bool isFlashEnabled = false;
-/** Flag to invert pixel colors. */
-bool isInvertEnabled = false;
-/** Flag to stop or start the stream. */
-bool isStreamEnabled = true;
-/** Holds the current camera configuration. */
-camera_config_t camera_config;
-/** Holds the currently selected dithering algorithm. */
-DitheringAlgorithm ditherAlgorithm = FLOYD_STEINBERG;
-
-/** Entry point of the program. */
 void setup() {
     // Begin serial communication.
     Serial.begin(230400); // 115200
 
+    // Initialize the camera model.
+    initialize_camera_model();
+
+    // Initialize the camera configuration.
+    initialize_camera_config();
+
     // Initialize the camera.
     initialize_camera();
 }
 
-/** Main loop of the program. */
+// Main loop of the program.
 void loop() {
-    if (isStreamEnabled) {
+    if (camera_model.isStreamEnabled) {
         camera_fb_t* frame_buffer = esp_camera_fb_get();
         if (frame_buffer) {
             process_image(frame_buffer);
@@ -87,314 +25,6 @@ void loop() {
         }
         delay(50);
     }
+    handle_flash_state();
     process_serial_commands();
 }
-
-void initialize_camera() {
-    // Set initial configurations.
-    camera_config.ledc_channel = LEDC_CHANNEL_0;
-    camera_config.ledc_timer = LEDC_TIMER_0;
-    camera_config.pin_d0 = Y2_GPIO_NUM;
-    camera_config.pin_d1 = Y3_GPIO_NUM;
-    camera_config.pin_d2 = Y4_GPIO_NUM;
-    camera_config.pin_d3 = Y5_GPIO_NUM;
-    camera_config.pin_d4 = Y6_GPIO_NUM;
-    camera_config.pin_d5 = Y7_GPIO_NUM;
-    camera_config.pin_d6 = Y8_GPIO_NUM;
-    camera_config.pin_d7 = Y9_GPIO_NUM;
-    camera_config.pin_xclk = XCLK_GPIO_NUM;
-    camera_config.pin_pclk = PCLK_GPIO_NUM;
-    camera_config.pin_vsync = VSYNC_GPIO_NUM;
-    camera_config.pin_href = HREF_GPIO_NUM;
-    camera_config.pin_sscb_sda = SIOD_GPIO_NUM;
-    camera_config.pin_sscb_scl = SIOC_GPIO_NUM;
-    camera_config.pin_pwdn = PWDN_GPIO_NUM;
-    camera_config.pin_reset = RESET_GPIO_NUM;
-    camera_config.xclk_freq_hz = 20000000;
-    camera_config.pixel_format = PIXFORMAT_GRAYSCALE;
-    camera_config.frame_size = FRAMESIZE_QQVGA;
-    camera_config.fb_count = 1;
-
-    esp_err_t err = esp_camera_init(&camera_config);
-    if (err != ESP_OK) {
-        return;
-    }
-
-    // Check if the flash is already on, if it is turn it off.
-    if (isFlashEnabled) {
-        toggle_flash_off();
-    }
-
-    // Get the camera sensor reference.
-    sensor_t* cam = esp_camera_sensor_get();
-
-    cam->set_contrast(cam, 0); // Set initial contrast.
-    cam->set_vflip(cam, true); // Set initial rotation (vertical).
-
-    // cam->set_brightness(cam, 0); // Set initial brightness.
-    // cam->set_hmirror(cam, true); // Set initial rotation (horizontal).
-    // cam->set_saturation(cam, 0); // Set initial saturation.
-    // cam->set_sharpness(cam, 0);  // Set initial sharpness.
-}
-
-void dither_image(camera_fb_t* frame_buffer) {
-    for (uint8_t y = 0; y < frame_buffer->height; ++y) {
-        for (uint8_t x = 0; x < frame_buffer->width; ++x) {
-            size_t current = (y * frame_buffer->width) + x;
-            uint8_t oldpixel = frame_buffer->buf[current];
-            uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
-            frame_buffer->buf[current] = newpixel;
-            int8_t quant_error = oldpixel - newpixel;
-
-            // Apply error diffusion based on the selected algorithm.
-            switch (ditherAlgorithm) {
-            case JARVIS_JUDICE_NINKE:
-                frame_buffer->buf[(y * frame_buffer->width) + x + 1] +=
-                    quant_error * 7 / 48;
-                frame_buffer->buf[(y * frame_buffer->width) + x + 2] +=
-                    quant_error * 5 / 48;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 2] +=
-                    quant_error * 3 / 48;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] +=
-                    quant_error * 5 / 48;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x] +=
-                    quant_error * 7 / 48;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] +=
-                    quant_error * 5 / 48;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 2] +=
-                    quant_error * 3 / 48;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 2] +=
-                    quant_error * 1 / 48;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 1] +=
-                    quant_error * 3 / 48;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x] +=
-                    quant_error * 5 / 48;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 1] +=
-                    quant_error * 3 / 48;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 2] +=
-                    quant_error * 1 / 48;
-                break;
-            case STUCKI:
-                frame_buffer->buf[(y * frame_buffer->width) + x + 1] +=
-                    quant_error * 8 / 42;
-                frame_buffer->buf[(y * frame_buffer->width) + x + 2] +=
-                    quant_error * 4 / 42;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 2] +=
-                    quant_error * 2 / 42;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] +=
-                    quant_error * 4 / 42;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x] +=
-                    quant_error * 8 / 42;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] +=
-                    quant_error * 4 / 42;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 2] +=
-                    quant_error * 2 / 42;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 2] +=
-                    quant_error * 1 / 42;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 1] +=
-                    quant_error * 2 / 42;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x] +=
-                    quant_error * 4 / 42;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 1] +=
-                    quant_error * 2 / 42;
-                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 2] +=
-                    quant_error * 1 / 42;
-                break;
-            case FLOYD_STEINBERG:
-            default:
-                frame_buffer->buf[(y * frame_buffer->width) + x + 1] +=
-                    quant_error * 7 / 16;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] +=
-                    quant_error * 3 / 16;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x] +=
-                    quant_error * 5 / 16;
-                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] +=
-                    quant_error * 1 / 16;
-                break;
-            }
-        }
-    }
-}
-
-void process_image(camera_fb_t* frame_buffer) {
-    // If dithering is not disabled, perform dithering on the image. Dithering
-    // is the process of approximating the look of a high-resolution grayscale
-    // image in a lower resolution by binary values (black & white), thereby
-    // representing different shades of gray.
-    if (isDitheringEnabled) {
-        // Invokes the dithering process on the frame buffer.
-        dither_image(frame_buffer);
-    }
-
-    uint8_t flipper_y = 0;
-
-    // Iterating over specific rows of the frame buffer.
-    for (uint8_t y = 28; y < 92; ++y) {
-        Serial.print("Y:");      // Print "Y:" for every new row.
-        Serial.write(flipper_y); // Send the row identifier as a byte.
-
-        // Calculate the actual y index in the frame buffer 1D array by
-        // multiplying the y value with the width of the frame buffer. This
-        // gives the starting index of the row in the 1D array.
-        size_t true_y = y * frame_buffer->width;
-
-        // Iterating over specific columns of each row in the frame buffer.
-        for (uint8_t x = 16; x < 144;
-             x += 8) { // step by 8 as we're packing 8 pixels per byte.
-            uint8_t packed_pixels = 0;
-            // Packing 8 pixel values into one byte.
-            for (uint8_t bit = 0; bit < 8; ++bit) {
-                // Check the invert flag and pack the pixels accordingly.
-                if (isInvertEnabled) {
-                    // If invert is true, consider pixel as 1 if it's more than
-                    // 127.
-                    if (frame_buffer->buf[true_y + x + bit] > 127) {
-                        packed_pixels |= (1 << (7 - bit));
-                    }
-                } else {
-                    // If invert is false, consider pixel as 1 if it's less than
-                    // 127.
-                    if (frame_buffer->buf[true_y + x + bit] < 127) {
-                        packed_pixels |= (1 << (7 - bit));
-                    }
-                }
-            }
-            Serial.write(packed_pixels); // Sending packed pixel byte.
-        }
-        // Move to the next row.
-        ++flipper_y;
-        // Ensure all data in the Serial buffer is sent before moving to the
-        // next iteration.
-        Serial.flush();
-    }
-}
-
-void process_serial_commands() {
-    if (Serial.available() > 0) {
-        char input = Serial.read();
-        sensor_t* cam = esp_camera_sensor_get();
-
-        switch (input) {
-        case '>': // Toggle dithering.
-            isDitheringEnabled = !isDitheringEnabled;
-            break;
-        case '<': // Toggle invert.
-            isInvertEnabled = !isInvertEnabled;
-            break;
-        case 'b': // Remove brightness.
-            cam->set_contrast(cam, cam->status.brightness - 1);
-            break;
-        case 'B': // Add brightness.
-            cam->set_contrast(cam, cam->status.brightness + 1);
-            break;
-        case 'c': // Remove contrast.
-            cam->set_contrast(cam, cam->status.contrast - 1);
-            break;
-        case 'C': // Add contrast.
-            cam->set_contrast(cam, cam->status.contrast + 1);
-            break;
-        case 'f': // Turn the flash off.
-            toggle_flash_off();
-            break;
-        case 'F': // Turn the flash on.
-            toggle_flash_on();
-            break;
-        case 'P': // Save image to the onboard SD card.
-            // @todo - Future feature.
-            // save_picture_to_sd();
-            break;
-        case 'M': // Toggle Mirror.
-            cam->set_hmirror(cam, !cam->status.hmirror);
-            break;
-        case 's': // Stop stream.
-            isStreamEnabled = false;
-            break;
-        case 'S': // Start stream.
-            isStreamEnabled = true;
-            break;
-        case '0': // Use Floyd Steinberg dithering.
-            ditherAlgorithm = FLOYD_STEINBERG;
-            break;
-        case '1': // Use Jarvis Judice dithering.
-            ditherAlgorithm = JARVIS_JUDICE_NINKE;
-            break;
-        case '2': // Use Stucki dithering.
-            ditherAlgorithm = STUCKI;
-            break;
-        default:
-            // Do nothing.
-            break;
-        }
-    }
-}
-
-/**
-void save_picture_to_sd() {
-    sensor_t* cam = esp_camera_sensor_get();
-
-    // Check if the sensor is valid.
-    if (!cam) {
-        Serial.println("Failed to acquire camera sensor");
-        return;
-    }
-
-    // Set pixel format to JPEG for saving picture.
-    cam->set_pixformat(cam, PIXFORMAT_JPEG);
-
-    // Set frame size based on available PSRAM.
-    if (psramFound()) {
-        cam->set_framesize(cam, FRAMESIZE_UXGA);
-    } else {
-        cam->set_framesize(cam, FRAMESIZE_SVGA);
-    }
-
-    // Get a frame buffer from camera.
-    camera_fb_t* frame_buffer = esp_camera_fb_get();
-    if (!frame_buffer) {
-        // Camera capture failed
-        return;
-    }
-
-    if (!SD_MMC.begin()) {
-        // SD Card Mount Failed.
-        esp_camera_fb_return(frame_buffer);
-        return;
-    }
-
-    // Generate a unique filename.
-    String path = "/picture";
-    path += String(millis());
-    path += ".jpg";
-
-    fs::FS& fs = SD_MMC;
-    File file = fs.open(path.c_str(), FILE_WRITE);
-
-    if (!file) {
-        // Failed to open file in writing mode
-    } else {
-        if (file.write(frame_buffer->buf, frame_buffer->len) !=
-            frame_buffer->len) {
-            // Failed to write the image to the file
-        }
-        file.close(); // Close the file in any case.
-    }
-
-    // Update framesize back to the default.
-    cam->set_framesize(cam, FRAMESIZE_QQVGA);
-
-    // Return the frame buffer back to the camera driver.
-    esp_camera_fb_return(frame_buffer);
-}
-*/
-
-void toggle_flash_off() {
-    pinMode(FLASH_GPIO_NUM, OUTPUT);
-    digitalWrite(FLASH_GPIO_NUM, LOW);
-    isFlashEnabled = false;
-}
-
-void toggle_flash_on() {
-    pinMode(FLASH_GPIO_NUM, OUTPUT);
-    digitalWrite(FLASH_GPIO_NUM, HIGH);
-    isFlashEnabled = true;
-}

+ 2 - 2
firmware-update/pins.h → firmware/pins.h

@@ -1,5 +1,5 @@
-#ifndef GLOBALS_H
-#define GLOBALS_H
+#ifndef PINS_H
+#define PINS_H
 
 // Define Pin numbers used by the camera.
 #define FLASH_GPIO_NUM 4

+ 1 - 1
firmware-update/process_image.h → firmware/process_image.h

@@ -5,7 +5,7 @@
 #include <esp_camera.h>
 
 #include "camera_model.h"
-#include "dithering.h"
+#include "dither_image.h"
 
 /** Process and send grayscale images back to the Flipper Zero. */
 void process_image(camera_fb_t* frame_buffer);

+ 9 - 11
firmware-update/process_image.ino → firmware/process_image.ino

@@ -1,16 +1,13 @@
 #include "process_image.h"
 
 void process_image(camera_fb_t* frame_buffer) {
-    // Get the camera model reference.
-    CameraModel* model = CameraModel::getInstance();
-
     // If dithering is not disabled, perform dithering on the image. Dithering
     // is the process of approximating the look of a high-resolution grayscale
     // image in a lower resolution by binary values (black & white), thereby
     // representing different shades of gray.
-    if (!model->getIsDitheringDisabled()) {
-        dither_image(frame_buffer); // Invokes the dithering process on the
-                                    // frame buffer.
+    if (camera_model.isDitheringEnabled) {
+        // Invokes the dithering process on the frame buffer.
+        dither_image(frame_buffer);
     }
 
     uint8_t flipper_y = 0;
@@ -32,7 +29,7 @@ void process_image(camera_fb_t* frame_buffer) {
             // Packing 8 pixel values into one byte.
             for (uint8_t bit = 0; bit < 8; ++bit) {
                 // Check the invert flag and pack the pixels accordingly.
-                if (model->getIsInverted()) {
+                if (camera_model.isInvertEnabled) {
                     // If invert is true, consider pixel as 1 if it's more than
                     // 127.
                     if (frame_buffer->buf[true_y + x + bit] > 127) {
@@ -48,9 +45,10 @@ void process_image(camera_fb_t* frame_buffer) {
             }
             Serial.write(packed_pixels); // Sending packed pixel byte.
         }
-
-        ++flipper_y;    // Move to the next row.
-        Serial.flush(); // Ensure all data in the Serial buffer is sent before
-                        // moving to the next iteration.
+        // Move to the next row.
+        ++flipper_y;
+        // Ensure all data in the Serial buffer is sent before moving to the
+        // next iteration.
+        Serial.flush();
     }
 }

+ 4 - 4
firmware-update/serial_commands.h → firmware/process_serial_commands.h

@@ -1,13 +1,13 @@
-#ifndef SERIAL_COMMANDS_H
-#define SERIAL_COMMANDS_H
+#ifndef PROCESS_SERIAL_COMMANDS_H
+#define PROCESS_SERIAL_COMMANDS_H
 
-#include <FS.h>
 #include <esp_camera.h>
 
+#include "camera.h"
 #include "camera_model.h"
 #include "pins.h"
 
 /** Handle the serial input commands coming from the Flipper Zero. */
-void serial_commands();
+void process_serial_commands();
 
 #endif

+ 16 - 20
firmware-update/serial_commands.ino → firmware/process_serial_commands.ino

@@ -1,19 +1,19 @@
-#include "serial_commands.h"
+#include "process_serial_commands.h"
 
-void serial_commands() {
+void process_serial_commands() {
     if (Serial.available() > 0) {
-        // Get the camera model reference.
-        CameraModel* model = CameraModel::getInstance();
-
         char input = Serial.read();
         sensor_t* cam = esp_camera_sensor_get();
 
         switch (input) {
         case '>': // Toggle dithering.
-            model->setIsDitheringDisabled(!model->getIsDitheringDisabled());
+            camera_model.isDitheringEnabled = !camera_model.isDitheringEnabled;
+            break;
+        case 'i': // Turn invert off.
+            camera_model.isInvertEnabled = false;
             break;
-        case '<': // Toggle invert.
-            model->setIsInverted(!model->getIsInverted());
+        case 'I': // Turn invert on.
+            camera_model.isInvertEnabled = true;
             break;
         case 'b': // Remove brightness.
             cam->set_contrast(cam, cam->status.brightness - 1);
@@ -28,36 +28,32 @@ void serial_commands() {
             cam->set_contrast(cam, cam->status.contrast + 1);
             break;
         case 'f': // Turn the flash off.
-            pinMode(FLASH_GPIO_NUM, OUTPUT);
-            digitalWrite(FLASH_GPIO_NUM, LOW);
-            model->setIsFlashEnabled(false);
+            toggle_flash_off();
             break;
         case 'F': // Turn the flash on.
-            pinMode(FLASH_GPIO_NUM, OUTPUT);
-            digitalWrite(FLASH_GPIO_NUM, HIGH);
-            model->setIsFlashEnabled(true);
+            toggle_flash_on();
             break;
         case 'P': // Save image to the onboard SD card.
             // @todo - Future feature.
-            // save_picture();
+            // save_picture_to_sd_card();
             break;
         case 'M': // Toggle Mirror.
             cam->set_hmirror(cam, !cam->status.hmirror);
             break;
         case 's': // Stop stream.
-            model->setIsStreamEnabled(false);
+            camera_model.isStreamEnabled = false;
             break;
         case 'S': // Start stream.
-            model->setIsStreamEnabled(true);
+            camera_model.isStreamEnabled = true;
             break;
         case '0': // Use Floyd Steinberg dithering.
-            model->setDitherAlgorithm(FLOYD_STEINBERG);
+            camera_model.ditherAlgorithm = FLOYD_STEINBERG;
             break;
         case '1': // Use Jarvis Judice dithering.
-            model->setDitherAlgorithm(JARVIS_JUDICE_NINKE);
+            camera_model.ditherAlgorithm = JARVIS_JUDICE_NINKE;
             break;
         case '2': // Use Stucki dithering.
-            model->setDitherAlgorithm(STUCKI);
+            camera_model.ditherAlgorithm = STUCKI;
             break;
         default:
             // Do nothing.

+ 3 - 3
firmware-update/save_picture.h → firmware/save_picture_to_sd_card.h

@@ -1,5 +1,5 @@
-#ifndef SAVE_PICTURE_H
-#define SAVE_PICTURE_H
+#ifndef SAVE_PICTURE_TO_SD_CARD_H
+#define SAVE_PICTURE_TO_SD_CARD_H
 
 #include <SD_MMC.h>
 #include <esp_camera.h>
@@ -8,6 +8,6 @@
  * Save the current picture to the onboard SD card.
  * @todo - Future feature.
  */
-void save_picture();
+void save_picture_to_sd_card();
 
 #endif

+ 2 - 2
firmware-update/save_picture.ino → firmware/save_picture_to_sd_card.ino

@@ -1,6 +1,6 @@
-#include "save_picture.h"
+#include "save_picture_to_sd_card.h"
 
-void save_picture() {
+void save_picture_to_sd_card() {
     sensor_t* cam = esp_camera_sensor_get();
 
     // Check if the sensor is valid.