ESP32-CAM: отправка изображений на локальный или облачный сервер с помощью PHP (менеджер фото)

Узнайте, как отправлять HTTP POST запросы с платы ESP32-CAM в Arduino IDE для загрузки фотографий на сервер. Мы покажем, как отправить изображение JPG/JPEG на локальный сервер (LAMP-сервер Raspberry Pi) или на облачный сервер (к которому можно получить доступ из любой точки мира). Фотографии будут отображаться в галерее, где их можно просматривать или удалять. Для сохранения изображений на сервере и создания галереи мы будем использовать PHP-скрипты.

ESP32-CAM отправка изображений на локальный или облачный сервер HTTP POST Arduino IDE

Обновлено 27 марта 2023

Для реализации этого проекта необходимо выполнить следующие шаги. Следуйте инструкциям для LAMP-сервера или хостинг-сервера в зависимости от того, хотите ли вы получать доступ к фотографиям локально или из любой точки мира.

  1. Размещение вашего PHP-приложения

    1. LAMP-сервер Raspberry Pi (локальный доступ)

    2. Хостинг-сервер (доступ из любой точки мира)

  2. PHP-скрипты для сохранения и отображения фотографий на сервере

    1. LAMP-сервер Raspberry Pi (локальный доступ)

    2. Хостинг-сервер (доступ из любой точки мира)

  3. Программирование ESP32-CAM в Arduino IDE

  4. Тестирование и финальная демонстрация

1. Размещение вашего PHP-приложения

Цель этого проекта – иметь локальный или облачный сервер для хранения и доступа к фотографиям ESP32-CAM.

1. Локальный сервер Raspberry Pi:

С LAMP-сервером Raspberry Pi, вы можете получить доступ к своим изображениям локально (как показано ниже).

ESP32-CAM отправка фото на LAMP-сервер Raspberry Pi с PHP-галереей

Настройка локального RPi LAMP-сервера >>

2. Облачный сервер (хостинг Bluehost)

Вы также можете просматривать фотографии ESP32-CAM из любой точки мира, обращаясь к собственному серверу + домену. Вот общая схема работы:

ESP32-CAM отправка фото на облачный сервер с PHP-галереей
  • Bluehost (удобный с cPanel): бесплатное доменное имя при подписке на 3-летний план. Рекомендую выбрать вариант с неограниченным количеством сайтов; Обратите внимание, что любой хостинг-сервис с поддержкой PHP подойдёт для этого руководства. Если у вас нет хостинг-аккаунта, рекомендую зарегистрироваться на Bluehost.

Получить хостинг и доменное имя на Bluehost >>

При покупке хостинг-аккаунта вам также потребуется приобрести доменное имя. Именно это делает проект интересным: вы сможете перейти по вашему доменному имени (http://example.com) и увидеть фотографии ESP32-CAM. Если вам нравятся наши проекты, рассмотрите возможность регистрации на Bluehost, так как вы поддержите нашу работу.

Метод HTTP POST запроса

Протокол передачи гипертекста (HTTP) работает как протокол «запрос-ответ» между клиентом и сервером. Вот пример:

  • ESP32 (клиент) отправляет HTTP-запрос серверу (например: локальный RPi LAMP-сервер или example.com);

  • Сервер возвращает ответ ESP32 (клиенту);

HTTP POST используется для отправки данных на сервер с целью создания/обновления ресурса. Например, публикация изображения на сервере.

POST /upload.php HTTP/1.1
Host: example.com
Content-Type: image/jpeg

2.1. Подготовка файлов .php и папки uploads (LAMP-сервер Raspberry Pi)

Этот раздел подготавливает ваши файлы .php и папку uploads для LAMP-сервера Raspberry Pi. Если вы используете собственный сервер + доменное имя, перейдите к следующему разделу.

Имея Raspberry Pi с Apache и PHP, в окне терминала Raspberry Pi перейдите в директорию /var/www/html/:

pi@raspberrypi:~ $ cd /var/www/html/

Создайте новую папку с именем uploads:

pi@raspberrypi:/var/www/html $ mkdir uploads
pi@raspberrypi:/var/www/html $ ls
uploads

На данный момент /var/www/html принадлежит root, используйте следующие команды, чтобы сменить владельца на пользователя pi и дать ему все разрешения, чтобы можно было сохранять фотографии с помощью PHP-скрипта позже.

sudo chown -R pi:pi /var/www/html
chmod -R 777 /var/www/html/

Наконец, создайте новый файл upload.php:

pi@raspberrypi:/var/www/html $ nano upload.php

Этот PHP-скрипт отвечает за приём входящих изображений от ESP32-CAM, переименование изображений с меткой времени и сохранение их в папке uploads. Отредактируйте только что созданный файл (upload.php) и скопируйте следующий фрагмент:

<?php
// Rui Santos
// Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/
// Code Based on this example: w3schools.com/php/php_file_upload.asp

$target_dir = "uploads/";
$datum = mktime(date('H')+0, date('i'), date('s'), date('m'), date('d'), date('y'));
$target_file = $target_dir . date('Y.m.d_H:i:s_', $datum) . basename($_FILES["imageFile"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));

// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
  $check = getimagesize($_FILES["imageFile"]["tmp_name"]);
  if($check !== false) {
    echo "File is an image - " . $check["mime"] . ".";
    $uploadOk = 1;
  }
  else {
    echo "File is not an image.";
    $uploadOk = 0;
  }
}

// Check if file already exists
if (file_exists($target_file)) {
  echo "Sorry, file already exists.";
  $uploadOk = 0;
}

// Check file size
if ($_FILES["imageFile"]["size"] > 500000) {
  echo "Sorry, your file is too large.";
  $uploadOk = 0;
}

// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif" ) {
  echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
  $uploadOk = 0;
}

// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
  echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
}
else {
  if (move_uploaded_file($_FILES["imageFile"]["tmp_name"], $target_file)) {
    echo "The file ". basename( $_FILES["imageFile"]["name"]). " has been uploaded.";
  }
  else {
    echo "Sorry, there was an error uploading your file.";
  }
}
?>

Просмотреть исходный код

Ваш файл upload.php должен выглядеть так. Сохраните файл и выйдите (Ctrl+X, Y и Enter):

Скрипт upload.php для сохранения изображений на сервере ESP32-CAM

Затем создайте новый файл gallery.php:

pi@raspberrypi:/var/www/html $ nano gallery.php

Отредактируйте только что созданный файл (gallery.php) и скопируйте следующий фрагмент:

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/

  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.
-->
<!DOCTYPE html>
<html>
<head>
  <title>ESP32-CAM Photo Gallery</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    .flex-container {
      display: flex;
      flex-wrap: wrap;
    }
    .flex-container > div {
      text-align: center;
      margin: 10px;
    }
  </style>
</head><body>
<h2>ESP32-CAM Photo Gallery</h2>
<?php
  // Image extensions
  $image_extensions = array("png","jpg","jpeg","gif");

  // Check delete HTTP GET request - remove images
  if(isset($_GET["delete"])){
    $imageFileType = strtolower(pathinfo($_GET["delete"],PATHINFO_EXTENSION));
    if (file_exists($_GET["delete"]) && ($imageFileType == "jpg" ||  $imageFileType == "png" ||  $imageFileType == "jpeg") ) {
      echo "File found and deleted: " .  $_GET["delete"];
      unlink($_GET["delete"]);
    }
    else {
      echo 'File not found - <a href="gallery.php">refresh</a>';
    }
  }
  // Target directory
  $dir = 'uploads/';
  if (is_dir($dir)){
    echo '<div class="flex-container">';
    $count = 1;
    $files = scandir($dir);
    rsort($files);
    foreach ($files as $file) {
      if ($file != '.' && $file != '..') {?>
        <div>
          <p><a href="gallery.php?delete=<?php echo $dir . $file; ?>">Delete file</a> - <?php echo $file; ?></p>
          <a href="<?php echo $dir . $file; ?>">
            <img src="<?php echo $dir . $file; ?>" style="width: 350px;" alt="" title=""/>
          </a>
       </div>
<?php
       $count++;
      }
    }
  }
  if($count==1) { echo "<p>No images found</p>"; }
?>
  </div>
</body>
</html>

Просмотреть исходный код

Этот PHP-скрипт отвечает за отображение изображений в галерее. Ваш файл gallery.php должен выглядеть так. Сохраните файл и выйдите (Ctrl+X, Y и Enter):

Скрипт gallery.php для просмотра изображений на сервере ESP32-CAM

2.2. Подготовка файлов .php и папки uploads (хостинг-сервис)

Если вы предпочитаете запускать сервер удалённо и получать доступ к фотографиям из любой точки мира, вам нужен хостинг-аккаунт. После регистрации хостинг-аккаунта и настройки доменного имени, вы можете войти в свою cPanel или аналогичную панель управления. После этого откройте File Manager.

Откройте вкладку «Advanced» и выберите «File Manager»:

Bluehost открытие Advanced и File Manager для создания файла upload.php и папки uploads

Затем выберите опцию public_html. Нажмите кнопку «+ File» для создания нового файла upload.php и нового файла gallery.php. Затем нажмите кнопку «+Folder» для создания папки Uploads.

ESP32-CAM CPanel создание нового файла Upload PHP и папки Uploads

После создания трёх элементов, отредактируйте файл upload.php:

ESP32-CAM CPanel созданные PHP-файлы и папка Uploads

Этот PHP-скрипт отвечает за приём входящих изображений от ESP32-CAM, переименование изображений с меткой времени и сохранение их в папке uploads. Отредактируйте только что созданный файл (upload.php) и скопируйте следующий фрагмент:

<?php
// Rui Santos
// Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/
// Code Based on this example: w3schools.com/php/php_file_upload.asp

$target_dir = "uploads/";
$datum = mktime(date('H')+0, date('i'), date('s'), date('m'), date('d'), date('y'));
$target_file = $target_dir . date('Y.m.d_H:i:s_', $datum) . basename($_FILES["imageFile"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));

// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
  $check = getimagesize($_FILES["imageFile"]["tmp_name"]);
  if($check !== false) {
    echo "File is an image - " . $check["mime"] . ".";
    $uploadOk = 1;
  }
  else {
    echo "File is not an image.";
    $uploadOk = 0;
  }
}

// Check if file already exists
if (file_exists($target_file)) {
  echo "Sorry, file already exists.";
  $uploadOk = 0;
}

// Check file size
if ($_FILES["imageFile"]["size"] > 500000) {
  echo "Sorry, your file is too large.";
  $uploadOk = 0;
}

// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif" ) {
  echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
  $uploadOk = 0;
}

// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
  echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
}
else {
  if (move_uploaded_file($_FILES["imageFile"]["tmp_name"], $target_file)) {
    echo "The file ". basename( $_FILES["imageFile"]["name"]). " has been uploaded.";
  }
  else {
    echo "Sorry, there was an error uploading your file.";
  }
}
?>

Просмотреть исходный код

Сохраните файл и выйдите.

Затем отредактируйте файл gallery.php и скопируйте следующий фрагмент. Он отвечает за отображение изображений в галерее.

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/

  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.
-->
<!DOCTYPE html>
<html>
<head>
  <title>ESP32-CAM Photo Gallery</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    .flex-container {
      display: flex;
      flex-wrap: wrap;
    }
    .flex-container > div {
      text-align: center;
      margin: 10px;
    }
  </style>
</head><body>
<h2>ESP32-CAM Photo Gallery</h2>
<?php
  // Image extensions
  $image_extensions = array("png","jpg","jpeg","gif");

  // Check delete HTTP GET request - remove images
  if(isset($_GET["delete"])){
    $imageFileType = strtolower(pathinfo($_GET["delete"],PATHINFO_EXTENSION));
    if (file_exists($_GET["delete"]) && ($imageFileType == "jpg" ||  $imageFileType == "png" ||  $imageFileType == "jpeg") ) {
      echo "File found and deleted: " .  $_GET["delete"];
      unlink($_GET["delete"]);
    }
    else {
      echo 'File not found - <a href="gallery.php">refresh</a>';
    }
  }
  // Target directory
  $dir = 'uploads/';
  if (is_dir($dir)){
    echo '<div class="flex-container">';
    $count = 1;
    $files = scandir($dir);
    rsort($files);
    foreach ($files as $file) {
      if ($file != '.' && $file != '..') {?>
        <div>
          <p><a href="gallery.php?delete=<?php echo $dir . $file; ?>">Delete file</a> - <?php echo $file; ?></p>
          <a href="<?php echo $dir . $file; ?>">
            <img src="<?php echo $dir . $file; ?>" style="width: 350px;" alt="" title=""/>
          </a>
       </div>
<?php
       $count++;
      }
    }
  }
  if($count==1) { echo "<p>No images found</p>"; }
?>
  </div>
</body>
</html>

Просмотреть исходный код

Сохраните файл и выйдите. Вот и всё! Ваш сервер готов.

3. ESP32-CAM: HTTP POST изображений/фотографий на сервер

Теперь, когда ваш сервер готов (LAMP-сервер Raspberry Pi или облачный сервер), пришло время подготовить ESP32-CAM с кодом для публикации нового изображения на сервер каждые 30 секунд. Прежде чем продолжить это руководство, убедитесь, что вы выполнили следующие предварительные требования.

Необходимые компоненты

Для этого руководства вам потребуются следующие компоненты:

Arduino IDE

Мы будем программировать ESP32-CAM в Arduino IDE, поэтому убедитесь, что у вас установлен аддон ESP32.

Проверка PHP URL

Вы должны попробовать открыть локальный IP-адрес Raspberry Pi или внешнее доменное имя example.com, с добавлением /upload.php, что должно вернуть:

Sorry, only JPG, JPEG, PNG & GIF files are allowed.Sorry, your file was not uploaded.
ESP32-CAM тестирование URL upload.php

Если вы видите это сообщение, сохраните ваш URL/доменное имя и путь, ваш сервер должен быть готов и вы можете продолжить это руководство.

Дополнительно попробуйте получить доступ к пути /gallery.php. Вы должны увидеть что-то подобное:

ESP32-CAM файл gallery.php просмотр и удаление фотографий

Код ESP32-CAM

Если вы используете локальный сервер без TLS/SSL, или облачный сервер, который не поддерживает HTTPS, используйте код HTTP POST запроса.

Если вы используете облачный сервер, требующий HTTPS-запросов, используйте этот код: код HTTPS POST запроса.

ESP32-CAM HTTP POST запрос

Следующий скетч отправляет изображение на сервер с помощью HTTP POST. Скопируйте код ниже в Arduino IDE.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/

  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.
*/

#include <Arduino.h>
#include <WiFi.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

String serverName = "192.168.1.XXX";   // REPLACE WITH YOUR Raspberry Pi IP ADDRESS
//String serverName = "example.com";   // OR REPLACE WITH YOUR DOMAIN NAME

String serverPath = "/upload.php";     // The default serverPath should be upload.php

const int serverPort = 80;

WiFiClient client;

// 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

const int timerInterval = 30000;    // time between each HTTP POST image
unsigned long previousMillis = 0;   // last time image was sent

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  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;

  // init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_CIF;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    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);
    delay(1000);
    ESP.restart();
  }

  sendPhoto();
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= timerInterval) {
    sendPhoto();
    previousMillis = currentMillis;
  }
}

String sendPhoto() {
  String getAll;
  String getBody;

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }

  Serial.println("Connecting to server: " + serverName);

  if (client.connect(serverName.c_str(), serverPort)) {
    Serial.println("Connection successful!");
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint32_t imageLen = fb->len;
    uint32_t extraLen = head.length() + tail.length();
    uint32_t totalLen = imageLen + extraLen;

    client.println("POST " + serverPath + " HTTP/1.1");
    client.println("Host: " + serverName);
    client.println("Content-Length: " + String(totalLen));
    client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    client.println();
    client.print(head);

    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0; n<fbLen; n=n+1024) {
      if (n+1024 < fbLen) {
        client.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        client.write(fbBuf, remainder);
      }
    }
    client.print(tail);

    esp_camera_fb_return(fb);

    int timoutTimer = 10000;
    long startTimer = millis();
    boolean state = false;

    while ((startTimer + timoutTimer) > millis()) {
      Serial.print(".");
      delay(100);
      while (client.available()) {
        char c = client.read();
        if (c == '\n') {
          if (getAll.length()==0) { state=true; }
          getAll = "";
        }
        else if (c != '\r') { getAll += String(c); }
        if (state==true) { getBody += String(c); }
        startTimer = millis();
      }
      if (getBody.length()>0) { break; }
    }
    Serial.println();
    client.stop();
    Serial.println(getBody);
  }
  else {
    getBody = "Connection to " + serverName +  " failed.";
    Serial.println(getBody);
  }
  return getBody;
}

Просмотреть исходный код

ESP32-CAM HTTPS POST запрос

Следующий скетч отправляет изображение на сервер с помощью HTTPS POST. Скопируйте код ниже в Arduino IDE.

/*
  Rui Santos
  Complete project details at:
  https://RandomNerdTutorials.com/esp32-cam-http-post-php-arduino/
  https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/

  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.
*/

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

String serverName = "example.com";   //REPLACE WITH YOUR DOMAIN NAME

String serverPath = "/upload.php";     // The default serverPath should be upload.php

const int serverPort = 443; //server port for HTTPS

WiFiClientSecure client;

// 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

const int timerInterval = 30000;    // time between each HTTP POST image
unsigned long previousMillis = 0;   // last time image was sent

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  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;

  // init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_CIF;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    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);
    delay(1000);
    ESP.restart();
  }

  sendPhoto();
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= timerInterval) {
    sendPhoto();
    previousMillis = currentMillis;
  }
}

String sendPhoto() {
  String getAll;
  String getBody;

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }

  Serial.println("Connecting to server: " + serverName);

  client.setInsecure(); //skip certificate validation
  if (client.connect(serverName.c_str(), serverPort)) {
    Serial.println("Connection successful!");
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint32_t imageLen = fb->len;
    uint32_t extraLen = head.length() + tail.length();
    uint32_t totalLen = imageLen + extraLen;

    client.println("POST " + serverPath + " HTTP/1.1");
    client.println("Host: " + serverName);
    client.println("Content-Length: " + String(totalLen));
    client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    client.println();
    client.print(head);

    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0; n<fbLen; n=n+1024) {
      if (n+1024 < fbLen) {
        client.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        client.write(fbBuf, remainder);
      }
    }
    client.print(tail);

    esp_camera_fb_return(fb);

    int timoutTimer = 10000;
    long startTimer = millis();
    boolean state = false;

    while ((startTimer + timoutTimer) > millis()) {
      Serial.print(".");
      delay(100);
      while (client.available()) {
        char c = client.read();
        if (c == '\n') {
          if (getAll.length()==0) { state=true; }
          getAll = "";
        }
        else if (c != '\r') { getAll += String(c); }
        if (state==true) { getBody += String(c); }
        startTimer = millis();
      }
      if (getBody.length()>0) { break; }
    }
    Serial.println();
    client.stop();
    Serial.println(getBody);
  }
  else {
    getBody = "Connection to " + serverName +  " failed.";
    Serial.println(getBody);
  }
  return getBody;
}

Просмотреть исходный код

Подробнее о HTTPS-запросах с ESP32: ESP32 HTTPS Requests (Arduino IDE).

Ввод сетевых учётных данных, камеры и данных сервера

Перед загрузкой кода вам нужно ввести ваши сетевые учётные данные в следующие переменные:

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Убедитесь, что выбрана правильная модель камеры. В данном случае мы используем модель AI-THINKER. Если вы используете другую модель камеры, можете прочитать это Руководство по платам ESP32-CAM: назначение выводов и GPIO.

Добавьте IP-адрес вашего Raspberry Pi или используйте доменное имя сервера:

String serverName = "192.168.1.XXX";   // REPLACE WITH YOUR Raspberry Pi IP ADDRESS
//String serverName = "example.com";   // OR REPLACE WITH YOUR DOMAIN NAME
String serverPath = "/upload.php";     // The default serverPath should be upload.php

Загрузка кода в ESP32-CAM

Теперь вы можете загрузить код на плату ESP32-CAM. Подключите плату ESP32-CAM к компьютеру с помощью FTDI программатора.

Следуйте следующей схеме подключения:

ESP32-CAM подключение к FTDI программатору для загрузки программы в Arduino IDE

Многие FTDI программаторы имеют перемычку, позволяющую выбрать 3.3В или 5В. Убедитесь, что перемычка установлена в правильное положение для выбора 5В.

Важно: GPIO 0 должен быть подключён к GND, чтобы можно было загрузить код.

ESP32-CAM

FTDI программатор

GND

GND

5V

VCC (5V)

U0R

TX

U0T

RX

GPIO 0

GND

Для загрузки кода выполните следующие шаги:

  1. Перейдите в Tools > Board и выберите AI-Thinker ESP32-CAM.

  2. Перейдите в Tools > Port и выберите COM-порт, к которому подключён ESP32.

  3. Затем нажмите кнопку загрузки для загрузки кода.

  4. Когда вы начнёте видеть точки в окне отладки, как показано ниже, нажмите встроенную кнопку RST на ESP32-CAM.

ESP32-CAM загрузка нового скетча Arduino IDE нажмите кнопку RESET

Через несколько секунд код должен быть успешно загружен на вашу плату.

Если у вас возникли проблемы с загрузкой кода, прочитайте наше Руководство по устранению неполадок ESP32-CAM.

Как работает код

Вот краткое объяснение того, как работает код:

  • Импортирует все библиотеки;

  • Определяет необходимые переменные;

  • Определяет выводы камеры;

  • В setup() устанавливается Wi-Fi соединение и инициализируется камера ESP32.

  • В loop() есть таймер, который вызывает функцию sendPhoto() каждые 30 секунд. Вы можете изменить это время задержки в переменной timerInterval.

Функция sendPhoto() – это часть, которая фактически делает снимок и отправляет его на ваш сервер. Вы можете использовать эту функцию в других ваших проектах, которые требуют снятия и публикации фотографии на сервер.

4. Тестирование и финальная демонстрация

После загрузки кода на плату откройте монитор последовательного порта Arduino IDE, и вы должны увидеть подобное сообщение, выводимое каждые 30 секунд:

The file esp32-cam.jpg has been uploaded.
ESP32-CAM файл esp32-cam.jpg загружен Arduino IDE Serial Monitor тестирование проекта

Если вы перейдёте по URL локального сервера http://IP-Address/uploads, или по URL облачного сервера https://example.com/uploads, вы должны увидеть папку со всеми сохранёнными фотографиями.

ESP32-CAM папка Uploads с изображениями на сервере

Вы можете открыть каждую ссылку для просмотра полноразмерного изображения на новой странице:

ESP32-CAM загрузка фото на сервер PHP Arduino IDE

Теперь, если вы перейдёте по URL локального сервера http://IP-Address/gallery.php, или по URL облачного сервера https://example.com/gallery.php, вы сможете получить доступ к странице галереи, где можно просматривать и удалять фотографии.

ESP32-CAM фото-галерея просмотр PHP файла

Чтобы удалить любую фотографию, просто нажмите на ссылку «Delete file» рядом с каждым изображением.

ESP32-CAM удаление файла из галереи php

Заключение

Вот и всё! Теперь вы можете отправлять фотографии ESP32-CAM на любой сервер с помощью HTTP POST. Модифицируйте этот проект под свои нужды. Например, можно делать фото и отправлять его на сервер при обнаружении движения.

Другие руководства по ESP32, которые могут вас заинтересовать: