ESP32-CAM: сохранение фотографий в Firebase Storage
В этом руководстве вы узнаете, как делать и загружать фотографии в Firebase Storage с помощью ESP32-CAM. Вы создадите проект Firebase с Storage, который позволяет хранить ваши файлы. Затем вы сможете перейти в консоль Firebase для просмотра фотографий или создать веб-приложение для их отображения (мы сделаем это в будущем уроке). ESP32-CAM будет запрограммирован с использованием Arduino IDE.
Примечание
Этот проект совместим с любой платой ESP32 Camera с камерой OV2640. Вам просто нужно убедиться, что вы используете правильную распиновку для вашей платы.
Что такое Firebase?
Firebase — это платформа разработки мобильных приложений от Google, которая помогает создавать, улучшать и развивать ваше приложение. Она имеет множество сервисов для управления данными из любого Android-, iOS- или веб-приложения, таких как аутентификация, база данных реального времени, хостинг, хранилище и т.д.
Обзор проекта
Этот простой урок демонстрирует, как делать и отправлять фотографии, сделанные ESP32-CAM, в Firebase Storage. ESP32-CAM делает снимок и отправляет его в Firebase каждый раз при перезагрузке (нажатие кнопки RST). Идея в том, чтобы добавить какой-либо триггер, который может быть полезен для ваших проектов, например PIR-датчик движения или кнопку.
Когда ESP32 запускается впервые, он делает новый снимок и сохраняет его в файловой системе (LittleFS);
ESP32-CAM подключается к Firebase как пользователь с email и паролем;
ESP32-CAM отправляет фотографию в Firebase Storage;
После этого вы можете перейти в консоль Firebase для просмотра фотографий;
Позже вы сможете создать веб-приложение, к которому можно получить доступ откуда угодно, для отображения фотографий ESP32-CAM (мы создадим его в будущем уроке).
Вот краткое описание шагов, которые необходимо выполнить для создания этого проекта:
Создание проекта Firebase
Настройка методов аутентификации
Создание Storage Bucket
Получение API-ключа проекта
ESP32-CAM — отправка фотографий в Firebase Storage
1) Создание проекта Firebase
Следуйте приведённым ниже инструкциям для создания нового проекта в Firebase.
Перейдите на Firebase и войдите с помощью аккаунта Google.
Перейдите в Firebase Console и создайте новый проект.
Дайте имя вашему проекту, например: ESP-Project, и нажмите Continue.
Далее включите или отключите AI-помощника для вашего проекта. Это необязательно.
Отключите опцию Enable Google Analytics для этого проекта, так как она не нужна. Затем нажмите Create project.
Настройка проекта займёт несколько секунд. Нажмите Continue, когда он будет готов.
Вы будете перенаправлены на страницу консоли вашего проекта.
2) Настройка методов аутентификации
Вам нужно настроить методы аутентификации для вашего приложения.
«Большинство приложений должны знать личность пользователя. Другими словами, это обеспечивает вход в систему и идентификацию пользователей (в данном случае ESP32). Знание личности пользователя позволяет приложению безопасно сохранять пользовательские данные в облаке…» Чтобы узнать больше о методах аутентификации, вы можете прочитать документацию.
На левой боковой панели нажмите Build > Authentication, а затем Get started.
Существует несколько методов аутентификации, таких как email и пароль, аккаунт Google, аккаунт Facebook и другие.
Выберите Email/Password и включите этот метод аутентификации. Затем нажмите Save.
Затем вверху нажмите на вкладку Users. Затем нажмите Add user.
Создайте нового пользователя с email и паролем. Email может быть вашим личным. Создайте пароль для этого пользователя (вам нужно будет помнить пароль позже). Наконец, нажмите Add user.
Пользователь появится в списке пользователей. Вы можете увидеть информацию о пользователе, например, когда он был создан, когда последний раз входил в систему и его UID.
3) Создание Storage Bucket
1) На левой боковой панели нажмите Build > Storage, а затем Get started.
Для использования Firebase Storage вам нужно обновить проект до платного плана. Но не волнуйтесь — они предлагают 5 ГБ бесплатного хранилища, чего более чем достаточно для запуска этого и многих других проектов в течение долгого времени без оплаты. Кроме того, вы можете установить максимальный лимит расходов.
2) Создайте платёжный аккаунт для привязки к вашему проекту.
Установите тарифный план Pay as you go Blaze.
3) После создания и настройки вашего платёжного аккаунта и привязки его к проекту, вернитесь в консоль проекта для создания Storage Bucket.
4) Выберите расположение базы данных.
5) Выберите Start in **test mode** — нажмите Next. Мы изменим правила хранилища позже.
6) Storage bucket теперь настроен. Скопируйте ID storage bucket — он понадобится вам позже. Не копируйте часть gs://.
Правила хранилища
Мы изменим правила хранилища, чтобы только аутентифицированные пользователи могли загружать файлы в storage bucket. Выберите вкладку Rules.
Измените правила вашей базы данных. Используйте следующие правила:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth !=null;
}
}
}
Когда закончите, нажмите Publish.
4) Получение API-ключа проекта
Для взаимодействия с вашим проектом Firebase с помощью ESP32-CAM, вам нужно получить API-ключ проекта. Следуйте приведённым ниже шагам для получения API-ключа.
Чтобы получить API-ключ проекта, на левой боковой панели нажмите Project Settings.
Скопируйте API Key в безопасное место, так как он понадобится вам позже.
Теперь у вас есть всё необходимое для взаимодействия ESP32 с базой данных.
5) ESP32-CAM — отправка фотографий в Firebase Storage
Перед тем как продолжить урок, убедитесь, что выполнены следующие предварительные требования.
Установка плат ESP32 в Arduino IDE
Мы будем программировать плату ESP32-CAM с помощью Arduino IDE. Поэтому вам нужна установленная Arduino IDE, а также ядро ESP32. Следуйте руководству ниже для установки, если вы ещё этого не сделали.
Установка библиотеки ESP Firebase Client
Для этого урока вам нужно установить библиотеку FirebaseClient.
Установка — Arduino IDE
Следуйте этому разделу, если вы используете Arduino IDE.
Перейдите в Sketch > Include Library > Manage Libraries, найдите библиотеку по имени и установите её.
Теперь вы готовы начать программирование плат ESP32 и ESP8266 для взаимодействия с базой данных.
Установка библиотек — VS Code
Следуйте приведённым ниже инструкциям, если вы используете VS Code с расширением PlatformIO или pioarduino.
Установка библиотеки FirebaseClient
Нажмите на значок PIO Home и выберите вкладку Libraries. Найдите «FirebaseClient». Выберите библиотеку FirebaseClient от Mobitz.
Затем нажмите Add to Project и выберите проект, над которым вы работаете.
Также измените скорость монитора на 115200, добавив следующую строку в файл platformio.ini вашего проекта:
monitor_speed = 115200
Теперь вы готовы начать программирование платы ESP32-CAM для отправки фотографий в Firebase Storage.
ESP32-CAM — отправка фотографий в Firebase — Код
Скопируйте следующий код в Arduino IDE или в файл main.cpp, если вы используете VS Code. Он делает снимок и отправляет его в Firebase при первой загрузке.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete instructions at: https://RandomNerdTutorials.com/esp32-cam-save-picture-firebase-storage/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files. The above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Software.
Based on the example provided by the ESP Firebase Client Library
*********/
#define ENABLE_USER_AUTH
#define ENABLE_STORAGE
#define ENABLE_FS
#include <Arduino.h>
#include <FirebaseClient.h>
#include <FS.h>
#include <LittleFS.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "esp_camera.h"
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"
#define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY"
#define USER_EMAIL "REPLACE_WITH_FIREBASE_PROJECT_EMAIL_USER"
#define USER_PASSWORD "REPLACE_WITH_FIREBASE_PROJECT_USER_PASS"
// Define the Firebase storage bucket ID e.g bucket-name.appspot.com */
#define STORAGE_BUCKET_ID "REPLACE_WITH_STORAGE_BUCKET_ID"
// Photo path in filesystem and photo path in Firebase bucket
#define FILE_PHOTO_PATH "/photo.jpg"
#define BUCKET_PHOTO_PATH "/data/photo.jpg"
// User functions
void processData(AsyncResult &aResult);
void file_operation_callback(File &file, const char *filename, file_operating_mode mode);
FileConfig media_file(FILE_PHOTO_PATH, file_operation_callback); // Can be set later with media_file.setFile("/image.png", file_operation_callback);
File myFile;
// Authentication
UserAuth user_auth(API_KEY, USER_EMAIL, USER_PASSWORD, 3000 /* expire period in seconds (<3600) */);
// Firebase components
FirebaseApp app;
WiFiClientSecure ssl_client;
using AsyncClient = AsyncClientClass;
AsyncClient aClient(ssl_client);
Storage storage;
bool taskComplete = false;
AsyncResult storageResult;
// 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;
// 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 < 10; 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);
delay(100);
}
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 initWiFi(){
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
}
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_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();
}
Serial.print("Camera init success");
}
void setup(){
Serial.begin(115200);
initWiFi();
initCamera();
Firebase.printf("Firebase Client v%s\n", FIREBASE_CLIENT_VERSION);
initLittleFS();
// Configure SSL client
ssl_client.setInsecure();
ssl_client.setConnectionTimeout(1000);
ssl_client.setHandshakeTimeout(5);
Serial.println("Initializing app...");
initializeApp(aClient, app, getAuth(user_auth), processData, "authTask");
app.getApp<Storage>(storage);
Serial.println("Listing files in LittleFS:");
File root = LittleFS.open("/");
File file = root.openNextFile();
while (file) {
Serial.println(file.name());
file = root.openNextFile();
}
}
void loop(){
// To maintain the authentication process.
app.loop();
if (app.ready() && !taskComplete && takeNewPhoto){
taskComplete = true;
takeNewPhoto = false;
capturePhotoSaveLittleFS();
// Async call with callback function.
storage.upload(aClient, FirebaseStorage::Parent(STORAGE_BUCKET_ID, BUCKET_PHOTO_PATH), getFile(media_file), "image/jpg", processData, "uploadTask");
}
}
void processData(AsyncResult &aResult)
{
// Exits when no result available when calling from the loop.
if (!aResult.isResult())
return;
if (aResult.isEvent())
{
Firebase.printf("Event task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.appEvent().message().c_str(), aResult.appEvent().code());
}
if (aResult.isDebug())
{
Firebase.printf("Debug task: %s, msg: %s\n", aResult.uid().c_str(), aResult.debug().c_str());
}
if (aResult.isError())
{
Firebase.printf("Error task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.error().message().c_str(), aResult.error().code());
}
if (aResult.downloadProgress())
{
Firebase.printf("Downloaded, task: %s, %d%s (%d of %d)\n", aResult.uid().c_str(), aResult.downloadInfo().progress, "%", aResult.downloadInfo().downloaded, aResult.downloadInfo().total);
if (aResult.downloadInfo().total == aResult.downloadInfo().downloaded)
{
Firebase.printf("Download task: %s, complete!\n", aResult.uid().c_str());
}
}
if (aResult.uploadProgress())
{
Firebase.printf("Uploaded, task: %s, %d%s (%d of %d)\n", aResult.uid().c_str(), aResult.uploadInfo().progress, "%", aResult.uploadInfo().uploaded, aResult.uploadInfo().total);
if (aResult.uploadInfo().total == aResult.uploadInfo().uploaded)
{
Firebase.printf("Upload task: %s, complete!\n", aResult.uid().c_str());
Serial.print("Download URL: ");
Serial.println(aResult.uploadInfo().downloadUrl);
}
}
}
void file_operation_callback(File &file, const char *filename, file_operating_mode mode){
// FILE_OPEN_MODE_READ, FILE_OPEN_MODE_WRITE and FILE_OPEN_MODE_APPEND are defined in this library
switch (mode) {
case file_mode_open_read:
myFile = LittleFS.open(filename, "r");
if (!myFile || !myFile.available()) {
Serial.println("[ERROR] Failed to open file for reading");
}
break;
case file_mode_open_write:
myFile = LittleFS.open(filename, "w");
break;
case file_mode_open_append:
myFile = LittleFS.open(filename, "a");
break;
case file_mode_remove:
LittleFS.remove(filename);
break;
default:
break;
}
// Set the internal FS object with global File object.
file = myFile;
}
Вам нужно вставить свои сетевые данные, email и пароль пользователя Firebase, URL storage bucket и API-ключ проекта, чтобы проект заработал.
Этот скетч основан на базовом примере, предоставленном библиотекой. Вы можете найти больше примеров здесь.
Как работает код
Продолжайте чтение, чтобы узнать, как работает код, или перейдите к разделу демонстрации.
Библиотеки
Сначала подключите необходимые библиотеки.
#include <Arduino.h>
#include <FirebaseClient.h>
#include <FS.h>
#include <LittleFS.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "esp_camera.h"
Сетевые данные
Вставьте свои сетевые данные в следующие переменные, чтобы ESP мог подключиться к интернету и взаимодействовать с Firebase.
//Replace with your network credentials
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"
API-ключ проекта Firebase
Вставьте API-ключ вашего проекта Firebase — смотрите раздел: 4) Получение API-ключа проекта.
#define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY"
Email и пароль пользователя
Вставьте авторизованный email и соответствующий пароль — смотрите раздел: 2) Настройка методов аутентификации.
#define USER_EMAIL "REPLACE_WITH_FIREBASE_PROJECT_EMAIL_USER"
#define USER_PASSWORD "REPLACE_WITH_FIREBASE_PROJECT_USER_PASS"
ID Firebase Storage Bucket
Вставьте ID Firebase storage bucket, например bucket-name.appspot.com. В моём случае это esp-project-598f5.firebasestorage.app. (Удалите все слэши «/» в конце или в начале bucket ID, иначе это не будет работать).
#define STORAGE_BUCKET_ID "REPLACE_WITH_YOUR_STORAGE_BUCKET_ID"
Путь к фотографии
Переменная FILE_PHOTO_PATH определяет путь в LittleFS, где будет сохранена фотография. Она будет сохранена с именем photo.jpg.
#define FILE_PHOTO "/photo.jpg"
У нас также есть переменная для хранения пути, по которому фотография будет сохранена в Storage Bucket в Firebase.
#define BUCKET_PHOTO "/data/photo.jpg"
Настройка файла
Создайте объект FileConfig для использования с функциями Firebase. Он принимает путь к файлу в bucket и callback-функцию для получения файла (будь то из файловой системы или SD-карты, например).
FileConfig media_file(FILE_PHOTO_PATH, file_operation_callback); // Can be set later with media_file.setFile("/image.png", file_operation_callback);
Наша callback-функция file_operation_callback() получает файл из LittleFS.
void file_operation_callback(File &file, const char *filename, file_operating_mode mode){
switch (mode) {
case file_mode_open_read:
myFile = LittleFS.open(filename, "r");
if (!myFile || !myFile.available()) {
Serial.println("[ERROR] Failed to open file for reading");
}
break;
case file_mode_open_write:
myFile = LittleFS.open(filename, "w");
break;
case file_mode_open_append:
myFile = LittleFS.open(filename, "a");
break;
case file_mode_remove:
LittleFS.remove(filename);
break;
default:
break;
}
// Set the internal FS object with global File object.
file = myFile;
}
Создайте глобальную переменную типа File с именем myFile, которая будет использоваться на протяжении всего кода для обращения к файлу, который мы хотим загрузить в файловую систему.
File myFile;
Объявление аутентификации и компонентов Firebase
Следующая строка создаёт объект аутентификации, используя API-ключ проекта, email пользователя проекта и пароль.
UserAuth user_auth(API_KEY, USER_EMAIL, USER_PASSWORD, 3000 /* expire period in seconds (<3600) */);
Это создаёт экземпляр FirebaseApp с именем app, который ссылается на приложение Firebase.
FirebaseApp app;
Следующие строки настраивают фреймворк асинхронного взаимодействия с Firebase. По сути, вы создаёте SSL-клиент с использованием библиотеки WiFiClientSecure. Затем вы создаёте экземпляр асинхронного клиента aClient, который обеспечивает безопасный HTTPS. Это позволит вам обрабатывать сетевые операции асинхронно.
WiFiClientSecure ssl_client;
using AsyncClient = AsyncClientClass;
AsyncClient aClient(ssl_client);
Следующая строка создаёт объект Storage с именем storage, который представляет Firebase storage bucket.
Storage storage;
Определение пинов ESP32-CAM
Следующие строки определяют пины ESP32-CAM. Это определение для модуля ESP32-CAM AI-Thinker. Если вы используете другой модуль ESP32-CAM, вам нужно изменить определение пинов — ознакомьтесь с этим руководством: ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide.
// 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
Другие переменные
Переменная takeNewPhoto проверяет, пора ли делать новый снимок. Мы установим её в true, чтобы снимок был сделан при первом запуске платы.
boolean takeNewPhoto = true;
Переменная taskCompleted — это булева переменная, которая проверяет, успешно ли мы подключились к Firebase.
bool taskCompleted = false;
Функция capturePhotoSaveLittleFS()
Функция capturePhotoSaveLittleFS() делает снимок и сохраняет его в файловой системе ESP32.
// 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);
}
Функция initWiFi()
Функция initWiFi() инициализирует Wi-Fi.
void initWiFi(){
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
}
Функция initLittleFS()
Функция initLittleFS() инициализирует файловую систему LittleFS.
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");
}
}
Функция initCamera()
Функция initCamera() инициализирует ESP32-CAM.
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;
if (psramFound()) {
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} 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();
}
}
setup()
В setup() инициализируются Serial Monitor, Wi-Fi, LittleFS и камера.
void setup(){
Serial.begin(115200);
initWiFi();
initCamera();
Firebase.printf("Firebase Client v%s\n", FIREBASE_CLIENT_VERSION);
initLittleFS();
Настройка SSL-клиента.
ssl_client.setInsecure();
ssl_client.setConnectionTimeout(1000);
ssl_client.setHandshakeTimeout(5);
Следующая строка инициализирует приложение Firebase с аутентификацией и устанавливает processData() как callback-функцию для асинхронных результатов (это означает, что любые результаты из функции initializeApp() будут обрабатываться в callback-функции processData()).
initializeApp(aClient, app, getAuth(user_auth), processData, "authTask");
Затем укажите, что вы хотите установить объект Storage, определённый ранее, в качестве хранилища для нашего приложения Firebase.
app.getApp<Storage>(storage);
Проверка и вывод списка файлов, хранящихся в файловой системе LittleFS (этот шаг необязателен).
Serial.println("Listing files in LittleFS:");
File root = LittleFS.open("/");
File file = root.openNextFile();
while (file) {
Serial.println(file.name());
file = root.openNextFile();
}
loop()
Библиотека Firebase, которую мы используем, работает асинхронно и с callback-функциями. Это означает, что когда происходит событие, запускаются соответствующие назначенные callback-функции. Чтобы поддерживать работу приложения Firebase, обработку аутентификации и асинхронных задач, нам нужно добавить app.loop() в начало нашей функции loop().
// To maintain the authentication process.
app.loop();
Команда app.ready() проверяет, завершена ли аутентификация Firebase и готова ли она, мы также проверяем, пора ли делать новый снимок.
if (app.ready() && !taskComplete && takeNewPhoto){
Если да, мы сбрасываем переменные taskComplete и takeNewPhoto в их исходные состояния.
taskComplete = true;
takeNewPhoto = false;
Мы вызываем функцию capturePhotoSaveLittleFS() для съёмки фотографии ESP32-CAM и сохранения её в файловой системе ESP32.
capturePhotoSaveLittleFS();
Наконец, отправляем фотографию в Firebase с помощью функции upload() объекта storage.
storage.upload(aClient, FirebaseStorage::Parent(STORAGE_BUCKET_ID, BUCKET_PHOTO_PATH), getFile(media_file), "image/jpg", processData, "uploadTask");
Давайте рассмотрим каждый аргумент этой функции:
aClient: это объект асинхронного клиента Firebase, используемый для подключения к Firebase.
FirebaseStorage::Parent(STORAGE_BUCKET_ID, BUCKET_PHOTO_PATH): указывает расположение в Firebase Storage, куда будет загружен файл:
STORAGE_BUCKET_ID: уникальный ID Firebase Storage bucket
BUCKET_PHOTO_PATH: путь или папка внутри bucket, где будет храниться файл
getFile(media_file): загружаемый файл, полученный с помощью функции getFile(). Мы определили файл ранее в начале кода.
«image/jpg»: MIME-тип файла, указывающий его формат. Здесь указано, что файл является JPEG-изображением.
processData: callback-функция, которая обрабатывает результат операции загрузки.
«uploadTask»: уникальный идентификатор для идентификации этой конкретной задачи позже в функции processData().
Функция processData — обработка задач Firebase
Функция processData() будет обрабатывать все события, связанные с операциями Firebase, включая загрузку файлов в Firebase storage.
void processData(AsyncResult &aResult)
{
// Exits when no result available when calling from the loop.
if (!aResult.isResult())
return;
if (aResult.isEvent())
{
Firebase.printf("Event task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.appEvent().message().c_str(), aResult.appEvent().code());
}
if (aResult.isDebug())
{
Firebase.printf("Debug task: %s, msg: %s\n", aResult.uid().c_str(), aResult.debug().c_str());
}
if (aResult.isError())
{
Firebase.printf("Error task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.error().message().c_str(), aResult.error().code());
}
if (aResult.downloadProgress())
{
Firebase.printf("Downloaded, task: %s, %d%s (%d of %d)\n", aResult.uid().c_str(), aResult.downloadInfo().progress, "%", aResult.downloadInfo().downloaded, aResult.downloadInfo().total);
if (aResult.downloadInfo().total == aResult.downloadInfo().downloaded)
{
Firebase.printf("Download task: %s, complete!\n", aResult.uid().c_str());
}
}
if (aResult.uploadProgress())
{
Firebase.printf("Uploaded, task: %s, %d%s (%d of %d)\n", aResult.uid().c_str(), aResult.uploadInfo().progress, "%", aResult.uploadInfo().uploaded, aResult.uploadInfo().total);
if (aResult.uploadInfo().total == aResult.uploadInfo().uploaded)
{
Firebase.printf("Upload task: %s, complete!\n", aResult.uid().c_str());
Serial.print("Download URL: ");
Serial.println(aResult.uploadInfo().downloadUrl);
}
}
}
Демонстрация
После ввода необходимых данных загрузите код на ваш ESP32-CAM. Если вы не знаете, как загрузить код на ESP32-CAM, вы можете следовать следующим руководствам:
Как запрограммировать / загрузить код на ESP32-CAM AI-Thinker (Arduino IDE)
Загрузка кода на ESP32-CAM AI-Thinker с помощью USB-программатора ESP32-CAM-MB
После загрузки кода откройте Serial Monitor на скорости 115200 бод. Нажмите встроенную кнопку RST на ESP32-CAM.
Он войдёт в Firebase, сделает снимок и сохранит его в LittleFS. После этого загрузит фотографию в Firebase Storage.
Теперь перейдите в консоль Firebase и выберите вкладку Storage. Там должна быть папка data, содержащая ваше фото.
Вы можете просмотреть метаданные фотографии и открыть её в полном размере. Вы также можете получить доступ к изображению по Download URL, напечатанному в Serial Monitor.
Заключение
В этом уроке вы узнали, как создать проект Firebase с Storage. Firebase Storage позволяет хранить файлы в облаке. Затем вы можете получить доступ к этим файлам, перейдя в консоль Firebase, или вы можете создать веб-приложение для отображения этих файлов (ознакомьтесь с этим уроком: ESP32-CAM: отображение фотографий в веб-приложении Firebase).
Мы показали простой пример отправки фотографии, сделанной ESP32-CAM, в Firebase Storage. Пример максимально прост, чтобы вы могли понять основы. Идея в том, чтобы модифицировать проект для создания чего-то полезного — например, делать снимок и загружать его в Firebase Storage при обнаружении движения, при открытии двери или при нажатии кнопки.
У нас есть другие уроки по Firebase, которые могут вам понравиться: