Files
2026-07-20 16:02:42 -05:00

179 lines
5.6 KiB
Arduino

/*
Waveshare ESP32-S3-ETH + OV5640 RTSP/MJPEG camera
Install first in Arduino IDE Library Manager:
ESP32-RTSPServer by Richard Sachse (version 1.3.5 or newer)
Open in VLC: rtsp://<Ethernet-IP>:8554/
This is MJPEG carried over RTSP, not H.264. The stream is 720p (1280x720).
*/
#include <Arduino.h>
#include <SPI.h>
#include <ETH.h>
#include <ESPmDNS.h>
#include <NetworkUdp.h>
#include <ArduinoOTA.h>
#include "esp_camera.h"
#include <ESP32-RTSPServer.h>
constexpr int ETH_MOSI = 11, ETH_MISO = 12, ETH_SCK = 13;
constexpr int ETH_CS = 14, ETH_RST = 9, ETH_INT = 10, ETH_ADDR = 1;
constexpr int CAMERA_POWER = 8; // Active-low power enable on ESP32-S3-ETH.
const char *otaHostname = "esp32s3-camera-node";
const char *otaPassword = "1gYh0Y";
uint32_t last_ota_time = 0;
SPIClass ethSpi(FSPI);
static bool ethConnected = false;
RTSPServer rtspServer;
TaskHandle_t videoTaskHandle = nullptr;
int cameraQuality = 20;
static bool startCamera() {
camera_config_t config = {};
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = 41; config.pin_d1 = 45; config.pin_d2 = 46; config.pin_d3 = 42;
config.pin_d4 = 40; config.pin_d5 = 38; config.pin_d6 = 15; config.pin_d7 = 18;
config.pin_xclk = 3; config.pin_pclk = 39; config.pin_vsync = 1; config.pin_href = 2;
config.pin_sccb_sda = 48; config.pin_sccb_scl = 47;
config.pin_pwdn = -1; config.pin_reset = -1;
config.xclk_freq_hz = 10000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_HD; // 1280x720 (720p). Requires PSRAM.
config.jpeg_quality = cameraQuality; // Higher number = smaller/lower-quality JPEG.
config.fb_count = 1;
config.fb_location = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
esp_err_t error = esp_camera_init(&config);
if (error != ESP_OK) {
Serial.printf("Camera init failed: 0x%x\n", error);
return false;
}
return true;
}
static void sendVideo(void *) {
while (true) {
if (rtspServer.readyToSendFrame()) {
camera_fb_t *frame = esp_camera_fb_get();
if (frame) {
rtspServer.sendRTSPFrame(frame->buf, frame->len, cameraQuality, frame->width, frame->height);
esp_camera_fb_return(frame);
}
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
static void startOTA() {
// Explicit mDNS binding before ArduinoOTA prevents unreliable Ethernet OTA discovery.
if (!MDNS.begin(otaHostname)) {
Serial.println("mDNS responder setup failed!");
}
ArduinoOTA.setHostname(otaHostname);
ArduinoOTA.setPassword(otaPassword);
ArduinoOTA
.onStart([]() {
String type = ArduinoOTA.getCommand() == U_FLASH ? "sketch" : "filesystem";
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
if (millis() - last_ota_time > 500) {
Serial.printf("Progress: %u%%\n", progress / (total / 100));
last_ota_time = millis();
}
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(ETH.localIP());
}
static void onEthEvent(arduino_event_id_t event, arduino_event_info_t info) {
switch (event) {
case ARDUINO_EVENT_ETH_START:
Serial.println("ETH: Started, setting hostname.");
ETH.setHostname(otaHostname);
break;
case ARDUINO_EVENT_ETH_CONNECTED:
Serial.println("ETH: Link up.");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
Serial.print("ETH: Got IP: ");
Serial.println(ETH.localIP());
ethConnected = true;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
Serial.println("ETH: Link down.");
ethConnected = false;
break;
case ARDUINO_EVENT_ETH_STOP:
Serial.println("ETH: Stopped.");
ethConnected = false;
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\nESP32-S3 Ethernet RTSP camera starting");
// Required for camera power on this Waveshare board.
pinMode(CAMERA_POWER, OUTPUT);
digitalWrite(CAMERA_POWER, LOW);
delay(200);
Network.onEvent(onEthEvent);
ethSpi.begin(ETH_SCK, ETH_MISO, ETH_MOSI);
Serial.println("Network: Initializing W5500 over SPI...");
if (!ETH.begin(ETH_PHY_W5500, ETH_ADDR, ETH_CS, ETH_INT, ETH_RST, ethSpi)) {
Serial.println("Network: Fatal error - W5500 initialization failed.");
while (true) delay(1000);
}
Serial.println("Network: Waiting for DHCP lease...");
unsigned long dhcpWaitStart = millis();
while (!ethConnected && millis() - dhcpWaitStart < 15000) {
delay(100);
}
if (!ethConnected) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
startOTA();
if (!startCamera()) return;
rtspServer.transport = RTSPServer::VIDEO_ONLY;
rtspServer.rtspPort = 8554;
if (!rtspServer.init(RTSPServer::VIDEO_ONLY, 8554)) {
Serial.println("RTSP server failed to start");
return;
}
xTaskCreate(sendVideo, "RTSP video", 5 * 1024, nullptr, 5, &videoTaskHandle);
Serial.printf("RTSP ready: rtsp://%s:8554/\n", ETH.localIP().toString().c_str());
}
void loop() {
ArduinoOTA.handle();
delay(2);
}