ESP32 CAM Firebase – Lưu hình ảnh lên Firebase

Trong bài hướng dẫn chủ đề ESP32 CAM Firebase này, chúng ta sẽ cùng tìm hiểu cách chụp và upload hình ảnh từ ESP32 CAM lên lưu trữ trên Firebase Storage.

Bạn có thể tạo một dự án Firebase với Storage để lưu trữ các tập tin, sau đó truy cập vào bảng điều khiển Firebase để xem hình ảnh một cách trực quan hơn hoặc tạo một Web Server để hiển thị chúng.

Firebase là gì?

Firebase là một nền tảng do Google phát triển, cho phép chúng ta có thể xây dựng, phát triển và tối ưu ứng dụng của mình.

Hiện nay, Firebase được dùng để quản lý dữ liệu. Chúng tương thích với tất cả các ứng dụng như iOS, Android hoặc các ứng dụng Web như xác thực, cơ sở dữ liệu thời gian thực, lưu trữ, hosting,…

Tổng quan dự án ESP32 CAM Firebase

Chúng ta sẽ chụp hình ảnh bằng ESP32 CAM, sau đó upload những hình đó lên Firebase Storage. Cụ thể, mỗi khi nhấn nút RST (reset), ESP32 CAM sẽ chụp và gửi ảnh tới Firebase.

Bạn có thể sáng tạo dự án thú vị hơn, chẳng hạn như sử dụng nút nhấn hoặc cảm biến chuyển động PIR phát hiện có người để kích hoạt việc chụp & gửi ảnh.

Tổng quan dự án ESP32 CAM Firebase
  • Khi vừa khởi động lần đầu, ESP32 CAM sẽ chụp 1 ảnh mới và lưu và LittleFS (hệ thống tập tin)
  • ESP32 CAM kết nối với Firebase thông qua email và password người dùng.
  • ESP32 CAM gửi hình ảnh đến Firebase Storage.
  • Bạn truy cập vào Firebase để xem ảnh, hoặc bạn có thể tạo một ứng dụng Web có thể truy cập vào từ mọi nơi để xem hình do ESP32 chụp được.

Dưới đây là các bước hướng dẫn chi tiết cách thực hiện dự án ESP32 CAM Firebase này:

Tạo dự án Firebase

Truy cập vào Firebase và đăng nhập vào bằng tài khoản Google.

Click vào Get Started >> Add project.

Đặt tên cho dự án của bạn, chẳng hạn như ESP Add Firebase Demo.

Cách tạo dự án ESP32 CAM Firebase

Tắt option Enable Google Analytics và click vào Create project:

Cách tạo dự án ESP32 CAM Firebase

Chờ đợi vài phút để hoàn thành việc tạo dự án, sau đó click vào Continue.

Cuối cùng, bạn sẽ được chuyển hướng đến trang giới thiệu dự án.

Cấu hình phương thức xác thực

Để hệ thống có thể xác thực bằng email và password, đầu tiên bạn cần cài đặt phương thức xác thực theo các bước sau:

1. Click vào Build >> Authentication >> Get started:

Cách xác thực cho dự án ESP32 CAM Firebase

2. Chọn vào option Email/Password.

3. Kích hoạt phương thức xác thực và nhấn vào Save để lưu lại. Khi đó, phương thức xác thực bằng email / mật khẩu sẽ được bật.

Cách xác thực cho dự án ESP32 CAM Firebase

4. Bạn tiến hành thêm người dùng, bằng cách chọn vào tab Users >> Add user:

Thêm người dùng vào dự án ESP32 CAM Firebase

5. Thêm địa chỉ email cho người dùng được ủy quyền, đồng thời việc thêm mật khẩu giúp bạn có thể login vào ứng dụng của mình và xem các file đã được lưu.

Thêm người dùng vào dự án ESP32 CAM Firebase

6. Sau khi nhấn add user, bạn đã thêm người dùng thành công. Lưu ý rằng, Firebase tạo một UID riêng cho mỗi người dùng đã đăng ký. UID cho phép chúng ta xác định người dùng và theo dõi họ để cung cấp hoặc từ chối quyền truy cập vào cơ sở dữ liệu.

Tạo Storage Bucket để lưu trữ

1. Click vào Storage >> Get started:

Tạo Storage Bucket để lưu trữ trong dự án ESP32 CAM Firebase

2. Chọn Start trong test mode >> click Next

Tạo Storage Bucket để lưu trữ trong dự án ESP32 CAM Firebase

3. Chọn vị trí lưu trữ của bạn, và chúng nên gần với quốc gia bạn đang ở (đa số là Việt Nam).

4. Chờ vài giây để hệ thống tại Storage Bucket.

5. Sau khi tạo xong, bạn copy ID của Storage Bucket vừa tạo (chúng ta sẽ sử dụng chúng sau). Lưu ý là bạn chỉ copy phần được khoanh vùng trong khu vực màu đỏ bên dưới:

Tạo Storage Bucket để lưu trữ trong dự án ESP32 CAM Firebase

Đến đây, chúng ta sẽ thay đổi quy tắc lưu trữ thành chỉ người dùng được xác thực mới có thể upload file lên Storage Bucket. Đầu tiên, bạn chọn tab Rules:

Thay đổi rule phục vụ dự án ESP32 CAM Firebase

Bạn thay đổi data rules thành như sau:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth !=null;
    }
  }
}

Sau khi hoàn thành, bạn click Finish.

Lấy Project API Key

Để giao tiếp với dự án ESP32 CAM Firebase, bạn cần lấy cặp khóa API (API key) theo hướng dẫn:

1. Click vào Project settings:

Lấy Project API Key phục vụ dự án ESP32 CAM Firebase

2. Sao chép Web API Key và lưu lại, chúng ta sẽ dùng đến chúng sau:

Lấy Project API Key phục vụ dự án ESP32 CAM Firebase

Gửi ảnh từ ESP32 CAM đến Firebase Storage

Trước khi làm theo hướng dẫn bên dưới, bạn cần chuẩn bị cho mình mạch ESP32 phù hợp. Ngoài ra, bạn cần đảm bảo trên Arduino đã cài đặt tiện ích ESP32.

Sau đó, bạn tiến hành cài các thư viện sau:

Bạn có thể cài thư viện này trên VS Code + PlatformIO hoặc Arduino đều được, tùy vào nhu cầu sử dụng của bạn.

Chương trình đầy đủ

Copy đoạn mã sau vào Arduino IDE (hoặc vào file main.cpp nếu bạn dùng VS Code). Đoạn code này giúp chụp hình và gửi đến Firebase khi chúng khởi động (bạn đừng quên đổi thông tin mạng WiFi, ID Storage Bucket và API key thành của bạn nhé!)

#include "Arduino.h"
#include "WiFi.h"
#include "esp_camera.h"
#include "soc/soc.h"           // Disable brownout problems
#include "soc/rtc_cntl_reg.h"  // Disable brownout problems
#include "driver/rtc_io.h"
#include <LittleFS.h>
#include <FS.h>
#include <Firebase_ESP_Client.h>
//Provide the token generation process info.
#include <addons/TokenHelper.h>

//Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Insert Firebase project API Key
#define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY"

// Insert Authorized Email and Corresponding Password
#define USER_EMAIL "REPLACE_WITH_THE_AUTHORIZED_USER_EMAIL"
#define USER_PASSWORD "REPLACE_WITH_THE_AUTHORIZED_USER_PASSWORD"

// Insert Firebase storage bucket ID e.g bucket-name.appspot.com
#define STORAGE_BUCKET_ID "REPLACE_WITH_YOUR_STORAGE_BUCKET_ID"
// For example:
//#define STORAGE_BUCKET_ID "esp-iot-app.appspot.com"

// Photo File Name to save in LittleFS
#define FILE_PHOTO_PATH "/photo.jpg"
#define BUCKET_PHOTO "/data/photo.jpg"

// OV2640 camera module pins (CAMERA_MODEL_AI_THINKER)
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#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 VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

boolean takeNewPhoto = true;

//Define Firebase Data objects
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig configF;

void fcsUploadCallback(FCS_UploadStatusInfo info);

bool taskCompleted = false;

// Capture Photo and Save it to LittleFS
void capturePhotoSaveLittleFS( void ) {
  // Dispose first pictures because of bad quality
  camera_fb_t* fb = NULL;
  // Skip first 3 frames (increase/decrease number as needed).
  for (int i = 0; i < 4; i++) {
    fb = esp_camera_fb_get();
    esp_camera_fb_return(fb);
    fb = NULL;
  }
    
  // Take a new photo
  fb = NULL;  
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }  

  // Photo file name
  Serial.printf("Picture file name: %s\n", FILE_PHOTO_PATH);
  File file = LittleFS.open(FILE_PHOTO_PATH, FILE_WRITE);

  // Insert the data in the photo file
  if (!file) {
    Serial.println("Failed to open file in writing mode");
  }
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    Serial.print("The picture has been saved in ");
    Serial.print(FILE_PHOTO_PATH);
    Serial.print(" - Size: ");
    Serial.print(fb->len);
    Serial.println(" bytes");
  }
  // Close the file
  file.close();
  esp_camera_fb_return(fb);
}

void initWiFi(){
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
}

void initLittleFS(){
  if (!LittleFS.begin(true)) {
    Serial.println("An Error has occurred while mounting LittleFS");
    ESP.restart();
  }
  else {
    delay(500);
    Serial.println("LittleFS mounted successfully");
  }
}

void initCamera(){
 // OV2640 camera module
  camera_config_t config;
  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_JPEG;
  config.grab_mode = CAMERA_GRAB_LATEST;

  if (psramFound()) {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 1;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    ESP.restart();
  } 
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  initWiFi();
  initLittleFS();
  // Turn-off the 'brownout detector'
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  initCamera();

  //Firebase
  // Assign the api key
  configF.api_key = API_KEY;
  //Assign the user sign in credentials
  auth.user.email = USER_EMAIL;
  auth.user.password = USER_PASSWORD;
  //Assign the callback function for the long running token generation task
  configF.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h

  Firebase.begin(&configF, &auth);
  Firebase.reconnectWiFi(true);
}

void loop() {
  if (takeNewPhoto) {
    capturePhotoSaveLittleFS();
    takeNewPhoto = false;
  }
  delay(1);
  if (Firebase.ready() && !taskCompleted){
    taskCompleted = true;
    Serial.print("Uploading picture... ");

    //MIME type should be valid to avoid the download problem.
    //The file systems for flash and SD/SDMMC can be changed in FirebaseFS.h.
    if (Firebase.Storage.upload(&fbdo, STORAGE_BUCKET_ID /* Firebase Storage bucket id */, FILE_PHOTO_PATH /* path to local file */, mem_storage_type_flash /* memory storage type, mem_storage_type_flash and mem_storage_type_sd */, BUCKET_PHOTO /* path of remote file stored in the bucket */, "image/jpeg" /* mime type */,fcsUploadCallback)){
      Serial.printf("\nDownload URL: %s\n", fbdo.downloadURL().c_str());
    }
    else{
      Serial.println(fbdo.errorReason());
    }
  }
}

// The Firebase Storage upload callback function
void fcsUploadCallback(FCS_UploadStatusInfo info){
    if (info.status == firebase_fcs_upload_status_init){
        Serial.printf("Uploading file %s (%d) to %s\n", info.localFileName.c_str(), info.fileSize, info.remoteFileName.c_str());
    }
    else if (info.status == firebase_fcs_upload_status_upload)
    {
        Serial.printf("Uploaded %d%s, Elapsed time %d ms\n", (int)info.progress, "%", info.elapsedTime);
    }
    else if (info.status == firebase_fcs_upload_status_complete)
    {
        Serial.println("Upload completed\n");
        FileMetaInfo meta = fbdo.metaData();
        Serial.printf("Name: %s\n", meta.name.c_str());
        Serial.printf("Bucket: %s\n", meta.bucket.c_str());
        Serial.printf("contentType: %s\n", meta.contentType.c_str());
        Serial.printf("Size: %d\n", meta.size);
        Serial.printf("Generation: %lu\n", meta.generation);
        Serial.printf("Metageneration: %lu\n", meta.metageneration);
        Serial.printf("ETag: %s\n", meta.etag.c_str());
        Serial.printf("CRC32: %s\n", meta.crc32.c_str());
        Serial.printf("Tokens: %s\n", meta.downloadTokens.c_str());
        Serial.printf("Download URL: %s\n\n", fbdo.downloadURL().c_str());
    }
    else if (info.status == firebase_fcs_upload_status_error){
        Serial.printf("Upload failed, %s\n", info.errorMsg.c_str());
    }
}

Demo

Sau khi chỉnh sửa thông tin và nạp code vào ESP32 CAM, chương trình sẽ hoạt động. Bạn hãy bật Serial Monitor ở tốc độ 115200, nhấn nút RST trên mạch ESP32 và quan sát.

Hệ thống sẽ log in vào Firebase, chụp ảnh và lưu chúng vào LittleFS. Sau đó, chúng upload hình ảnh lên Firebase Storage. Bạn có thể quan sát cách hoạt động của dự án ESP32 CAM Firebase qua màn hình Serial:

Demo dự án ESP32 CAM Firebase

Bây giờ, bạn mở Firebase console, chọn tab Storage. Trong đó sẽ có 1 thư mục data chứa các hình ảnh mà ESP32 CAM đã gửi lên.

Bạn có thể xem thông tin của 1 hình ảnh và xem chúng ở full size, đồng thời bạn có thể truy cập vào URL Download của hình ảnh trong Serial Monitor:

Demo dự án ESP32 CAM Firebase

Lời kết

Trong bài viết trên, IoTZone đã giới thiệu đến bạn cách tạo Firebase Storage để lưu trữ file trên Cloud. Sau đó, chúng ta sử dụng ESP32 CAM để chụp hình và upload lên Firebase. Bạn đã thực hiện thành công dự án ESP32 CAM Firebase này chưa? Hãy chia sẻ với IoTZone nhé!

IoTZone – Chuyên cung cấp thiết bị điện tử & tài liệu cho Makers

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *