commit 186ec816ea4c1cbe3c54b41a108e2244e8d191f1 Author: drjones Date: Wed May 20 10:05:24 2026 -0700 chore: import local project into Gitea diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93065f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Dependency directories +node_modules/ +bower_components/ +jspm_packages/ + +# Logs and databases +*.log +*.sql +*.sqlite + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production + +# Build output +dist/ +build/ +out/ +.next/ + +# Debug files +*.pdb + +# Local development +*.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..321a1fd --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# ESP32-CAM Video Streaming for Seeed XIAO ESP32S3 Sense + +This project implements a live MJPEG video streaming server on the Seeed XIAO ESP32S3 Sense board with OV3660/OV5640 camera module. + +## Features +- Automatic WiFi connection to "thetempleofdoom" network +- Live MJPEG video streaming web server +- Serial debug output at 115200 baud +- Optimized for PSRAM usage + +## Hardware Requirements +- Seeed XIAO ESP32S3 Sense board +- OV3660 or OV5640 camera module + +## Setup +1. Install required libraries: + - esp_camera + - WiFi +2. Configure Arduino IDE: + - Select "XIAO_ESP32S3" board + - Enable PSRAM in board settings +3. Upload the sketch + +## Usage +1. Power on the device +2. Monitor serial output for connection status and IP address +3. Access the stream at `http://[device-ip]/` + +## Configuration +- WiFi credentials can be changed in firmware.ino +- Camera settings can be adjusted in firmware.ino + +## Known Issues +- None currently identified + +## Future Improvements +- Add WiFi connection timeout +- Implement OTA updates +- Add configuration web interface diff --git a/camera_pins.h b/camera_pins.h new file mode 100644 index 0000000..094f2ed --- /dev/null +++ b/camera_pins.h @@ -0,0 +1,23 @@ +// camera_pins.h +#if defined(CAMERA_MODEL_XIAO_ESP32S3) + #define PWDN_GPIO_NUM -1 + #define RESET_GPIO_NUM -1 + #define XCLK_GPIO_NUM 10 + #define SIOD_GPIO_NUM 40 + #define SIOC_GPIO_NUM 39 + + #define Y9_GPIO_NUM 48 + #define Y8_GPIO_NUM 11 + #define Y7_GPIO_NUM 12 + #define Y6_GPIO_NUM 14 + #define Y5_GPIO_NUM 16 + #define Y4_GPIO_NUM 18 + #define Y3_GPIO_NUM 17 + #define Y2_GPIO_NUM 15 + + #define VSYNC_GPIO_NUM 38 + #define HREF_GPIO_NUM 47 + #define PCLK_GPIO_NUM 13 +#else + #error "Camera model not defined" +#endif diff --git a/s3 cam.ino b/s3 cam.ino new file mode 100644 index 0000000..6859e0d --- /dev/null +++ b/s3 cam.ino @@ -0,0 +1,172 @@ +#define CAMERA_MODEL_XIAO_ESP32S3 // PSRAM-enabled camera model + +#include "esp_camera.h" +#include +#include "camera_pins.h" + +// Your Wi-Fi credentials +const char* ssid = "thetempleofdoom"; +const char* password = "myhandsonmyself"; + +#include "esp_http_server.h" +#include "esp_camera.h" + +#define PART_BOUNDARY "123456789000000000000987654321" +static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + +// Frame buffer conversion function from ESP32 CameraWebServer example +bool frame2jpg(camera_fb_t * fb, int quality, uint8_t ** out_buf, size_t * out_len) { + if(!fb || !out_buf || !out_len) return false; + + *out_buf = NULL; + *out_len = 0; + + bool ret = true; + if(fb->format == PIXFORMAT_JPEG) { + *out_buf = fb->buf; + *out_len = fb->len; + } else { + if(!fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out_buf, out_len)) { + ret = false; + } + } + return ret; +} + +// Implementation of startCameraServer from ESP32 CameraWebServer example +extern "C" void startCameraServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = 80; + config.ctrl_port = 80; + config.max_open_sockets = 3; + + httpd_handle_t stream_httpd = NULL; + httpd_handle_t camera_httpd = NULL; + + if (httpd_start(&stream_httpd, &config) == ESP_OK) { + httpd_uri_t stream_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = [](httpd_req_t *req){ + camera_fb_t *fb = NULL; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len = 0; + uint8_t *_jpg_buf = NULL; + char *part_buf[64]; + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if(res != ESP_OK) return res; + + while(true) { + fb = esp_camera_fb_get(); + if (!fb) { + Serial.println("Camera capture failed"); + res = ESP_FAIL; + } else { + if(fb->format != PIXFORMAT_JPEG) { + bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + if(!jpeg_converted) { + Serial.println("JPEG compression failed"); + esp_camera_fb_return(fb); + res = ESP_FAIL; + } + } else { + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; + } + } + if(res == ESP_OK) { + res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); + } + if(fb) { + esp_camera_fb_return(fb); + } + if(res != ESP_OK) break; + } + return res; + }, + .user_ctx = NULL + }; + httpd_register_uri_handler(stream_httpd, &stream_uri); + } +} + +void setup() { + Serial.begin(115200); + while (!Serial); + + // Camera configuration + 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; // for streaming + config.frame_size = FRAMESIZE_UXGA; // max resolution + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + config.fb_location = CAMERA_FB_IN_PSRAM; // use PSRAM + config.jpeg_quality = 12; + config.fb_count = 1; + + // Optimize if PSRAM is found + if (psramFound()) { + config.jpeg_quality = 10; + config.fb_count = 2; + config.grab_mode = CAMERA_GRAB_LATEST; + } + + // Initialize the camera + esp_err_t err = esp_camera_init(&config); + if (err != ESP_OK) { + Serial.printf("Camera init failed: 0x%x\n", err); + return; + } + + // Optional sensor settings + sensor_t* s = esp_camera_sensor_get(); + if (s->id.PID == OV3660_PID) { + s->set_vflip(s, 1); + s->set_brightness(s, 1); + s->set_saturation(s, -2); + s->set_framesize(s, FRAMESIZE_QVGA); + } + + // Connect to Wi-Fi + WiFi.begin(ssid, password); + WiFi.setSleep(false); + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + Serial.println("WiFi connected"); + + // Start the web server + startCameraServer(); + + Serial.print("Camera Ready! Access at: http://"); + Serial.println(WiFi.localIP()); +} + +void loop() { + // All handled in server task + delay(10000); +}