mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-16 20:47:55 +00:00
feat: implement WebRTC signaling and UDP-based video streaming and control service
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
#include "esp_camera.h"
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ESP32Servo.h>
|
||||
|
||||
// === НАСТРОЙКА РЕЖИМА ЗАГЛУШКИ ===
|
||||
const bool USE_MOCK_IF_NO_CAMERA = true; // true - слать черный экран, false - ничего не слать
|
||||
|
||||
Servo myServo;
|
||||
const int servoPin = 13; // ПРИОРИТЕТ: Пин жестко закреплен за сервоприводом
|
||||
|
||||
// !!! УНИКАЛЬНЫЙ ID ДЛЯ КАЖДОЙ ПЛАТЫ
|
||||
const uint8_t DEVICE_ID = 1;
|
||||
|
||||
// Конфигурация пинов (пример для Seeed Studio XIAO ESP32S3 Sense)
|
||||
#define PWDN_GPIO_NUM -1
|
||||
@@ -10,7 +20,7 @@
|
||||
#define SIOC_GPIO_NUM 39
|
||||
#define Y9_GPIO_NUM 48
|
||||
#define Y8_GPIO_NUM 47
|
||||
#define Y7_GPIO_NUM 13
|
||||
#define Y7_GPIO_NUM 18 // ИЗМЕНЕНО: Отключаем этот пин для камеры, отдаем под Серво
|
||||
#define Y6_GPIO_NUM 14
|
||||
#define Y5_GPIO_NUM 12
|
||||
#define Y4_GPIO_NUM 11
|
||||
@@ -20,24 +30,45 @@
|
||||
#define HREF_GPIO_NUM 38
|
||||
#define PCLK_GPIO_NUM 37
|
||||
|
||||
|
||||
const char* ssid = "RT-WiFi-1712";
|
||||
const char* password = "yt3xihY2Ci";
|
||||
|
||||
const char* serverIP = "192.168.0.11";
|
||||
const char* serverIP = "192.168.0.10";
|
||||
const int serverPort = 9999;
|
||||
const int localPort = 8888;
|
||||
|
||||
// !!! УНИКАЛЬНЫЙ ID ДЛЯ КАЖДОЙ ПЛАТЫ (например, 1 для первой, 2 для второй и т.д.)
|
||||
const uint8_t DEVICE_ID = 1;
|
||||
|
||||
|
||||
WiFiUDP udpVideo;
|
||||
WiFiUDP udpCmd;
|
||||
camera_config_t config;
|
||||
|
||||
// Флаг успешной инициализации камеры
|
||||
bool isCameraInitialized = false;
|
||||
|
||||
// Моковый минимальный валидный JPEG черного цвета
|
||||
const uint8_t mockBlackJpeg[] = {
|
||||
0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60,
|
||||
0x00, 0x60, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x08,
|
||||
0x00, 0x08, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xC4, 0x00, 0x14,
|
||||
0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0xFF, 0xD9
|
||||
};
|
||||
const size_t mockSize = sizeof(mockBlackJpeg);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
// Настройка камеры (QVGA для стабильности)
|
||||
|
||||
// Инициализируем сервопривод в первую очередь
|
||||
myServo.attach(servoPin, 500, 2500);
|
||||
Serial.println("Сервопривод инициализирован на пине 13");
|
||||
|
||||
// Настройка конфигурации камеры
|
||||
config.ledc_channel = LEDC_CHANNEL_0;
|
||||
config.ledc_timer = LEDC_TIMER_0;
|
||||
config.pin_d0 = Y2_GPIO_NUM;
|
||||
@@ -45,7 +76,7 @@ void setup() {
|
||||
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_d5 = Y7_GPIO_NUM; // Передаст -1, библиотека не тронет 13 пин
|
||||
config.pin_d6 = Y8_GPIO_NUM;
|
||||
config.pin_d7 = Y9_GPIO_NUM;
|
||||
config.pin_xclk = XCLK_GPIO_NUM;
|
||||
@@ -62,7 +93,15 @@ void setup() {
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
config.fb_count = 2;
|
||||
|
||||
esp_camera_init(&config);
|
||||
// Проверяем статус инициализации камеры
|
||||
esp_err_t err = esp_camera_init(&config);
|
||||
if (err == ESP_OK) {
|
||||
isCameraInitialized = true;
|
||||
Serial.println("Камера успешно инициализирована!");
|
||||
} else {
|
||||
isCameraInitialized = false;
|
||||
Serial.printf("Камера не найдена (код 0x%x). Режим мок-данных: %s\n", err, USE_MOCK_IF_NO_CAMERA ? "ВКЛ" : "ВЫКЛ");
|
||||
}
|
||||
|
||||
// Подключение к Wi-Fi
|
||||
WiFi.begin(ssid, password);
|
||||
@@ -79,43 +118,58 @@ void setup() {
|
||||
|
||||
void checkIncomingCommands() {
|
||||
int packetSize = udpCmd.parsePacket();
|
||||
if (packetSize > 0) {
|
||||
char packetBuffer[255];
|
||||
int len = udpCmd.read(packetBuffer, 255);
|
||||
if (len > 0) { packetBuffer[len] = 0; }
|
||||
|
||||
String command = String(packetBuffer);
|
||||
command.trim();
|
||||
if (packetSize == 5) {
|
||||
uint8_t packetBuffer[5];
|
||||
int len = udpCmd.read(packetBuffer, 5);
|
||||
if (len == 5) {
|
||||
uint8_t steer = packetBuffer[0];
|
||||
uint8_t throttle = packetBuffer[1];
|
||||
uint8_t brake = packetBuffer[2];
|
||||
uint8_t gear = packetBuffer[3];
|
||||
uint8_t flags = packetBuffer[4];
|
||||
|
||||
Serial.print("Получена команда: ");
|
||||
Serial.println(command);
|
||||
// ИСПРАВЛЕНО: Инвертирован диапазон (180, 0 вместо 0, 180) для исправления направления
|
||||
int servoAngle = map(steer, 0, 200, 180, 0);
|
||||
myServo.write(servoAngle);
|
||||
|
||||
Serial.print("Получена команда: ");
|
||||
Serial.println(command);
|
||||
|
||||
// Логика управления
|
||||
if (command == "left") {
|
||||
// Ваш код поворота налево
|
||||
} else if (command == "right") {
|
||||
// Ваш код поворота направо
|
||||
} else if (command == "start") {
|
||||
// Ваш код запуска моторов / логики
|
||||
} else if (command == "stop") {
|
||||
// Ваш код остановки
|
||||
Serial.printf("Got control packet: Steer=%d (Angle=%d), Throttle=%d%%, Brake=%d%%, Gear=%d, Flags=0x%02X\n",
|
||||
steer, servoAngle, throttle, brake, gear, flags);
|
||||
}
|
||||
} else if (packetSize > 0) {
|
||||
char garbage[255];
|
||||
udpCmd.read(garbage, sizeof(garbage));
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
checkIncomingCommands();
|
||||
|
||||
camera_fb_t * fb = esp_camera_fb_get();
|
||||
if (!fb) return;
|
||||
uint8_t * buf = nullptr;
|
||||
size_t bufferSize = 0;
|
||||
camera_fb_t * fb = nullptr;
|
||||
|
||||
size_t bufferSize = fb->len;
|
||||
uint8_t * buf = fb->buf;
|
||||
// Пытаемся получить кадр, если камера запущена
|
||||
if (isCameraInitialized) {
|
||||
fb = esp_camera_fb_get();
|
||||
if (fb) {
|
||||
bufferSize = fb->len;
|
||||
buf = fb->buf;
|
||||
}
|
||||
}
|
||||
|
||||
// Если кадра с камеры нет
|
||||
if (!buf) {
|
||||
// Если отправка заглушки отключена — выходим из цикла, продолжая слушать команды
|
||||
if (!USE_MOCK_IF_NO_CAMERA) {
|
||||
delay(10);
|
||||
return;
|
||||
}
|
||||
// Если включена — подставляем моковый черный кадр
|
||||
bufferSize = mockSize;
|
||||
buf = (uint8_t *)mockBlackJpeg;
|
||||
}
|
||||
|
||||
// Размер куска данных уменьшаем до 1023, чтобы вместе с 1 байтом ID пакет был 1024 байта
|
||||
// Нарезка и отправка по UDP (оригинальная логика)
|
||||
size_t chunkSize = 1023;
|
||||
uint8_t packetBuffer[1024];
|
||||
|
||||
@@ -125,15 +179,18 @@ void loop() {
|
||||
sendSize = bufferSize - i;
|
||||
}
|
||||
|
||||
// Формируем пакет: первый байт — ID, дальше — видеоданные
|
||||
packetBuffer[0] = DEVICE_ID;
|
||||
memcpy(packetBuffer + 1, buf + i, sendSize);
|
||||
|
||||
udpVideo.beginPacket(serverIP, serverPort);
|
||||
udpVideo.write(packetBuffer, sendSize + 1); // Отправляем данные вместе с ID
|
||||
udpVideo.write(packetBuffer, sendSize + 1);
|
||||
udpVideo.endPacket();
|
||||
}
|
||||
|
||||
esp_camera_fb_return(fb);
|
||||
// Возвращаем буфер только если он реальный
|
||||
if (fb) {
|
||||
esp_camera_fb_return(fb);
|
||||
}
|
||||
|
||||
delay(10);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user