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

Add support for camera flash and saving pictures.

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

+ 76 - 4
src-fap/views/camera_suite_view_camera.c

@@ -82,6 +82,73 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* _model) {
     }
     }
 }
 }
 
 
+static void save_image(void* _model) {
+    UartDumpModel* model = _model;
+
+    // This pointer is used to access the storage.
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    // This pointer is used to access the filesystem.
+    File* file = storage_file_alloc(storage);
+
+    // Store path in local variable.
+    const char* folderName = EXT_PATH("DCIM");
+
+    // Create the folder name for the image file if it does not exist.
+    if(storage_common_stat(storage, folderName, NULL) == FSE_NOT_EXIST) {
+        storage_simply_mkdir(storage, folderName);
+    }
+
+    // This pointer is used to access the file name.
+    FuriString* file_name = furi_string_alloc();
+
+    // Get the current date and time.
+    FuriHalRtcDateTime datetime = {0};
+    furi_hal_rtc_get_datetime(&datetime);
+
+    // Create the file name.
+    furi_string_printf(
+        file_name,
+        EXT_PATH("DCIM/%.4d%.2d%.2d-%.2d%.2d%.2d.bmp"),
+        datetime.year,
+        datetime.month,
+        datetime.day,
+        datetime.hour,
+        datetime.minute,
+        datetime.second
+    );
+
+    // Open the file for writing. If the file does not exist (it shouldn't), 
+    // create it.
+    bool result = storage_file_open(
+        file, 
+        furi_string_get_cstr(file_name), 
+        FSAM_WRITE, FSOM_OPEN_ALWAYS
+    );
+
+    // Free the file name after use.
+    furi_string_free(file_name);
+
+    // If the file was opened successfully, write the bitmap header and the
+    // image data.
+    if (result){
+        storage_file_write(file, bitmap_header, BITMAP_HEADER_LENGTH);
+        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);
+        }
+    }
+
+    // Close the file.
+    storage_file_close(file);
+
+    // Freeing up memory.
+    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) {
     for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) {
     for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) {
         model->pixels[i] = 0;
         model->pixels[i] = 0;
@@ -106,7 +173,6 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
                 true);
                 true);
             break;
             break;
         }
         }
-        // Send `data` to the ESP32-CAM
     } else if(event->type == InputTypePress) {
     } else if(event->type == InputTypePress) {
         uint8_t data[1];
         uint8_t data[1];
         switch(event->key) {
         switch(event->key) {
@@ -185,19 +251,25 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
             break;
             break;
         case InputKeyOk:
         case InputKeyOk:
             // Switch dithering types.
             // Switch dithering types.
-            data[0] = 'D';
+            // data[0] = 'D';
+            data[0] = 'P';
+            // Initialize the ESP32-CAM onboard torch immediately.
+            furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
+            // Delay for 500ms to make sure flash is on before taking picture.
+            furi_delay_ms(500);
+            // Take picture.
             with_view_model(
             with_view_model(
                 instance->view,
                 instance->view,
                 UartDumpModel * model,
                 UartDumpModel * model,
                 {
                 {
-                    UNUSED(model);
                     camera_suite_play_happy_bump(instance->context);
                     camera_suite_play_happy_bump(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_play_input_sound(instance->context);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
                     camera_suite_led_set_rgb(instance->context, 0, 0, 255);
+                    save_image(model);
                     instance->callback(CameraSuiteCustomEventSceneCameraOk, instance->context);
                     instance->callback(CameraSuiteCustomEventSceneCameraOk, instance->context);
                 },
                 },
                 true);
                 true);
-            break;
+            return true;
         case InputKeyMAX:
         case InputKeyMAX:
             break;
             break;
         }
         }

+ 11 - 1
src-fap/views/camera_suite_view_camera.h

@@ -23,6 +23,16 @@
 #define ROW_BUFFER_LENGTH 16
 #define ROW_BUFFER_LENGTH 16
 #define RING_BUFFER_LENGTH 19
 #define RING_BUFFER_LENGTH 19
 #define LAST_ROW_INDEX 1008
 #define LAST_ROW_INDEX 1008
+#define BITMAP_HEADER_LENGTH 62
+
+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
+};
 
 
 extern const Icon I_DolphinCommon_56x48;
 extern const Icon I_DolphinCommon_56x48;
 
 
@@ -58,4 +68,4 @@ typedef enum {
     WorkerEventRx = (1 << 2),
     WorkerEventRx = (1 << 2),
 } WorkerEventFlags;
 } WorkerEventFlags;
 
 
-#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx)
+#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx)

+ 36 - 13
src-firmware/esp32_cam_uart_stream/esp32_cam_uart_stream.ino

@@ -1,23 +1,24 @@
 #include "esp_camera.h"
 #include "esp_camera.h"
 
 
 // Pin definitions
 // Pin definitions
+#define FLASH_GPIO_NUM    4
+#define HREF_GPIO_NUM     23
+#define PCLK_GPIO_NUM     22
 #define PWDN_GPIO_NUM     32
 #define PWDN_GPIO_NUM     32
 #define RESET_GPIO_NUM    -1
 #define RESET_GPIO_NUM    -1
-#define XCLK_GPIO_NUM      0
-#define SIOD_GPIO_NUM     26
 #define SIOC_GPIO_NUM     27
 #define SIOC_GPIO_NUM     27
+#define SIOD_GPIO_NUM     26
+#define XCLK_GPIO_NUM      0
+#define VSYNC_GPIO_NUM    25
 
 
-#define Y9_GPIO_NUM       35
-#define Y8_GPIO_NUM       34
-#define Y7_GPIO_NUM       39
-#define Y6_GPIO_NUM       36
-#define Y5_GPIO_NUM       21
-#define Y4_GPIO_NUM       19
-#define Y3_GPIO_NUM       18
 #define Y2_GPIO_NUM        5
 #define Y2_GPIO_NUM        5
-#define VSYNC_GPIO_NUM    25
-#define HREF_GPIO_NUM     23
-#define PCLK_GPIO_NUM     22
+#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
 
 
 // Camera configuration
 // Camera configuration
 camera_config_t config;
 camera_config_t config;
@@ -42,6 +43,7 @@ DitheringAlgorithm ditherAlgorithm = FLOYD_STEINBERG;
 // Serial input flags
 // Serial input flags
 bool disableDithering = false;
 bool disableDithering = false;
 bool invert = false;
 bool invert = false;
+bool isFlashOn = false;
 bool rotated = false;
 bool rotated = false;
 bool stopStream = false;
 bool stopStream = false;
 
 
@@ -88,7 +90,18 @@ void handleSerialInput() {
       case 'c': // Remove contrast
       case 'c': // Remove contrast
         cameraSensor->set_contrast(cameraSensor, cameraSensor->status.contrast - 1);
         cameraSensor->set_contrast(cameraSensor, cameraSensor->status.contrast - 1);
         break;
         break;
-      case 'P': // TODO: Take a picture
+      case 'P': // Picture sequence.
+        if (!isFlashOn) {
+          isFlashOn = true;
+          pinMode(FLASH_GPIO_NUM, OUTPUT);
+          // Turn on torch.
+          digitalWrite(FLASH_GPIO_NUM, HIGH); 
+          delay(2000);
+          // Turn off torch.
+          digitalWrite(FLASH_GPIO_NUM, LOW); 
+          delay(50);
+          isFlashOn = false;
+        }
         break;
         break;
       case 'M': // Toggle Mirror
       case 'M': // Toggle Mirror
         cameraSensor->set_hmirror(cameraSensor, !cameraSensor->status.hmirror);
         cameraSensor->set_hmirror(cameraSensor, !cameraSensor->status.hmirror);
@@ -102,6 +115,9 @@ void handleSerialInput() {
       case 'D': // Change dithering algorithm.
       case 'D': // Change dithering algorithm.
         ditherAlgorithm = static_cast<DitheringAlgorithm>((ditherAlgorithm + 1) % 3);
         ditherAlgorithm = static_cast<DitheringAlgorithm>((ditherAlgorithm + 1) % 3);
         break;
         break;
+      default:
+        // Do nothing.
+        break;
     }
     }
   }
   }
 }
 }
@@ -131,6 +147,13 @@ void initializeCamera() {
   config.frame_size = FRAMESIZE_QQVGA;
   config.frame_size = FRAMESIZE_QQVGA;
   config.fb_count = 1;
   config.fb_count = 1;
 
 
+  if (isFlashOn) {
+    pinMode(FLASH_GPIO_NUM, OUTPUT);
+    // Turn off torch.
+    digitalWrite(FLASH_GPIO_NUM, LOW); 
+    isFlashOn = false;
+  }
+
   // Initialize camera
   // Initialize camera
   esp_err_t err = esp_camera_init(&config);
   esp_err_t err = esp_camera_init(&config);
   if (err != ESP_OK) {
   if (err != ESP_OK) {