Приєднуйся до нашої Telegram-групи — там я особисто відповідаю на запитання, ділюсь порадами, схемами, кодом і лайфхаками зі збирання та налаштування.
Перейти в Telegram

NEWS

Додаток Esp32 cam (перегляд та керування)

Ця стаття є частиною проєкту «Гусенична платформа з камерою на ESP32-CAM»

Презентую вам унікальний застосунок для ESP32-CAM, який відкриває нові можливості відеоспостереження та керування. Після довгих пошуків ідеального рішення для трансляції відео з камери ESP32-CAM AI-Thinker на смартфон та одночасного керування гусеничною або колісною платформою — я нарешті створив такий застосунок і з радістю ділюся ним з вами.

Завантажити застосунок ESP32 CAM

Скетч для ESP32-CAM створює точку доступу WiFi з ім’ям carv. Щоб почати користуватися, просто запустіть застосунок на смартфоні, натисніть кнопку меню (вгорі праворуч) і підключіться до мережі мікроконтролера через налаштування телефону.

Інтерфейс інтуїтивний: після натискання кнопки "Відео" під елементами керування з’являється живе відео з камери. Керування здійснюється через прості команди, які надсилаються на сервер:


Команда Опис Посилання

LeftКнопка правого джойстикаhttp://host/left

RightКнопка правого джойстикаhttp://host/right

UpКнопка лівого джойстикаhttp://host/up

DownКнопка лівого джойстикаhttp://host/down

FaraКерування фарамиhttp://host/fara

AvarАварійна сигналізаціяhttp://host/avar

SignЗвуковий сигналhttp://host/sign

Dop1Додаткова функція 1http://host/dop1

Dop2Додаткова функція 2http://host/dop2

Reset1Центральна кнопка лівого джойстикаhttp://host/reset1

Reset2Центральна кнопка правого джойстикаhttp://host/reset2

 Потокове відео в реальному часі

Призначення Посилання
Відеопотікhttp://host:81/stream

Мікроконтролер ESP32-CAM запускає два сервери: відеопотік на порту 81 і сервер команд на стандартному порту 80. Наведений нижче тестовий скетч дозволяє:

  • Відображати відео на екрані Android-смартфона
  • Виводити команди в монітор порту
  • Керувати світлодіодами на платі
#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"

// ==== Пины камеры для 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

#define LED1_GPIO         33
#define LED2_GPIO         4

WebServer commandServer(80);   // Сервер команд
WiFiServer videoServer(81);    // Сервер видеопотока
TaskHandle_t videoTaskHandle;  // Задача видеопотока

bool led1_state = false;
bool led2_state = false;

// === Универсальный обработчик команд ===
void handleCommandGeneric(const String& name) {
  Serial.println("Получена команда: " + name);
  commandServer.send(200, "text/plain", "OK " + name);
}

// === Спец. команды с управлением светодиодами ===
void handleCommand1() {
  led1_state = !led1_state;
  digitalWrite(LED1_GPIO, led1_state);
  Serial.println("Команда dop1: переключили GPIO 33");
  commandServer.send(200, "text/plain", "OK dop1");
}

void handleCommand2() {
  led2_state = !led2_state;
  digitalWrite(LED2_GPIO, led2_state);
  Serial.println("Команда dop2: переключили GPIO 4");
  commandServer.send(200, "text/plain", "OK dop2");
}

// === Задача видеопотока на ядре 0 ===
void videoStreamTask(void *parameter) {
  videoServer.begin();
  Serial.println("Сервер видеопотока запущен на порту 81");

  while (true) {
    WiFiClient client = videoServer.available();
    if (client) {
      Serial.println("Клиент подключен к видеопотоку");

      String response = "HTTP/1.1 200 OK\r\n";
      response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
      client.print(response);

      while (client.connected()) {
        camera_fb_t * fb = esp_camera_fb_get();
        if (!fb) {
          Serial.println("Ошибка получения кадра");
          break;
        }

        client.print("--frame\r\n");
        client.print("Content-Type: image/jpeg\r\n\r\n");
        client.write(fb->buf, fb->len);
        client.print("\r\n");

        esp_camera_fb_return(fb);
        delay(50);
        yield();
      }

      client.stop();
      Serial.println("Клиент отключился от видеопотока");
    }

    delay(10);
  }
}

// === Инициализация камеры ===
void setupCamera() {
  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.frame_size = FRAMESIZE_VGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Ошибка камеры: 0x%x\n", err);
  }
}

// === Настройка ===
void setup() {
  Serial.begin(115200);

  pinMode(LED1_GPIO, OUTPUT);
  pinMode(LED2_GPIO, OUTPUT);
  digitalWrite(LED1_GPIO, LOW);
  digitalWrite(LED2_GPIO, LOW);

  IPAddress ip(192, 168, 4, 1);
  IPAddress gateway(192, 168, 4, 1);
  IPAddress subnet(255, 255, 255, 0);
  WiFi.softAPConfig(ip, gateway, subnet);
  WiFi.softAP("carv");

  Serial.println("Точка доступа: carv");
  Serial.print("IP: ");
  Serial.println(WiFi.softAPIP());

  setupCamera();

  // Главная страница управления
  commandServer.on("/", HTTP_GET, []() {
    String html = "<html><body><h2>Управление ESP32-CAM</h2>";
    html += "<p><a href='http://192.168.4.1:81/stream' target='_blank'>▶ Смотреть видео</a></p>";
    const char* commands[] = {
      "left", "right", "up", "down", "fara", "avar", "sign", "dop1", "dop2", "reset1", "reset2"
    };
    for (auto& cmd : commands) {
      html += "<p><a href='/" + String(cmd) + "'>" + String(cmd) + "</a></p>";
    }
    html += "</body></html>";
    commandServer.send(200, "text/html", html);
  });

  // Обработка команд
  commandServer.on("/dop1", HTTP_GET, handleCommand1);
  commandServer.on("/dop2", HTTP_GET, handleCommand2);

  commandServer.on("/left",    HTTP_GET, []() { handleCommandGeneric("left"); });
  commandServer.on("/right",   HTTP_GET, []() { handleCommandGeneric("right"); });
  commandServer.on("/up",      HTTP_GET, []() { handleCommandGeneric("up"); });
  commandServer.on("/down",    HTTP_GET, []() { handleCommandGeneric("down"); });
  commandServer.on("/fara",    HTTP_GET, []() { handleCommandGeneric("fara"); });
  commandServer.on("/avar",    HTTP_GET, []() { handleCommandGeneric("avar"); });
  commandServer.on("/sign",    HTTP_GET, []() { handleCommandGeneric("sign"); });
  commandServer.on("/reset1",  HTTP_GET, []() { handleCommandGeneric("reset1"); });
  commandServer.on("/reset2",  HTTP_GET, []() { handleCommandGeneric("reset2"); });

  commandServer.begin();
  Serial.println("Командный сервер запущен (порт 80)");

  // Запуск видеопотока в отдельной задаче
  xTaskCreatePinnedToCore(
    videoStreamTask,
    "VideoStream",
    8192,
    NULL,
    1,
    &videoTaskHandle,
    0
  );
}

// === Главный цикл ===
void loop() {
  commandServer.handleClient();
}

 Це базова реалізація, яку ви можете адаптувати під власні потреби. У найближчий час я представлю вдосконалену версію для керування гусеничною платформою, включно зі STL-моделями та електричною схемою. Слідкуйте за оновленнями!