commit 61ee8e94232c5c6d064db4633ef6a0ac276bd33d Author: cheykrym Date: Sun Jan 25 22:50:05 2026 +0300 init diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5172429 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..da0a616 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32-s3-n16r8] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +monitor_speed = 115200 + +board_build.flash_size = 16MB +board_build.partitions = default.csv diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..aea8fad --- /dev/null +++ b/src/config.h @@ -0,0 +1,29 @@ +#pragma once + +// ===== Wi-Fi (copied from main board) ===== +#define WIFI_SSID "Capybara" +#define WIFI_PASS "qq1234567890" + +// ===== Camera pins (ESP32-S3-CAM, OV2640) ===== +// Pinout from your table. +#define CAM_PIN_PWDN -1 +#define CAM_PIN_RESET -1 +#define CAM_PIN_XCLK 15 +#define CAM_PIN_SIOD 4 +#define CAM_PIN_SIOC 5 + +#define CAM_PIN_Y9 16 +#define CAM_PIN_Y8 17 +#define CAM_PIN_Y7 18 +#define CAM_PIN_Y6 12 +#define CAM_PIN_Y5 10 +#define CAM_PIN_Y4 8 +#define CAM_PIN_Y3 9 +#define CAM_PIN_Y2 11 + +#define CAM_PIN_VSYNC 6 +#define CAM_PIN_HREF 7 +#define CAM_PIN_PCLK 13 + +// If probe fails, try PWDN on GPIO2: +// #define CAM_PIN_PWDN 2 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1577032 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +#include "config.h" + +static const char *STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=frame"; +static const char *STREAM_BOUNDARY = "\r\n--frame\r\n"; +static const char *STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + +static httpd_handle_t httpd = nullptr; + +static bool cameraPinsOk() { + return CAM_PIN_XCLK != -1 && + CAM_PIN_SIOD != -1 && + CAM_PIN_SIOC != -1 && + CAM_PIN_Y2 != -1 && + CAM_PIN_Y3 != -1 && + CAM_PIN_Y4 != -1 && + CAM_PIN_Y5 != -1 && + CAM_PIN_Y6 != -1 && + CAM_PIN_Y7 != -1 && + CAM_PIN_Y8 != -1 && + CAM_PIN_Y9 != -1 && + CAM_PIN_VSYNC != -1 && + CAM_PIN_HREF != -1 && + CAM_PIN_PCLK != -1; +} + +static String jsonSensorStatus(sensor_t *s) { + String json = "{"; + json += "\"framesize\":" + String(s->status.framesize) + ","; + json += "\"quality\":" + String(s->status.quality) + ","; + json += "\"brightness\":" + String(s->status.brightness) + ","; + json += "\"contrast\":" + String(s->status.contrast) + ","; + json += "\"saturation\":" + String(s->status.saturation); + json += "}"; + return json; +} + +static esp_err_t handleRoot(httpd_req_t *req) { + const char *html = + "" + "" + "" + "ESP32 Cam" + "" + "

ESP32 Camera

" + "" + "" + "" + "" + "" + "" + ""; + + httpd_resp_set_type(req, "text/html; charset=utf-8"); + return httpd_resp_send(req, html, HTTPD_RESP_USE_STRLEN); +} + +static esp_err_t handleStatus(httpd_req_t *req) { + sensor_t *s = esp_camera_sensor_get(); + if (!s) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "sensor"); + return ESP_FAIL; + } + String json = jsonSensorStatus(s); + httpd_resp_set_type(req, "application/json"); + return httpd_resp_send(req, json.c_str(), json.length()); +} + +static esp_err_t handleControl(httpd_req_t *req) { + char buf[32]; + char var[16]; + int val = 0; + + if (httpd_req_get_url_query_str(req, buf, sizeof(buf)) != ESP_OK || + httpd_query_key_value(buf, "var", var, sizeof(var)) != ESP_OK || + httpd_query_key_value(buf, "val", buf, sizeof(buf)) != ESP_OK) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "bad query"); + return ESP_FAIL; + } + + val = atoi(buf); + sensor_t *s = esp_camera_sensor_get(); + if (!s) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "sensor"); + return ESP_FAIL; + } + + int res = 0; + if (!strcmp(var, "framesize")) res = s->set_framesize(s, (framesize_t)val); + else if (!strcmp(var, "quality")) res = s->set_quality(s, val); + else if (!strcmp(var, "brightness")) res = s->set_brightness(s, val); + else if (!strcmp(var, "contrast")) res = s->set_contrast(s, val); + else if (!strcmp(var, "saturation")) res = s->set_saturation(s, val); + else { + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "unknown var"); + return ESP_FAIL; + } + + if (res != 0) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "set failed"); + return ESP_FAIL; + } + + httpd_resp_send(req, "OK", 2); + return ESP_OK; +} + +static esp_err_t handleCapture(httpd_req_t *req) { + camera_fb_t *fb = esp_camera_fb_get(); + if (!fb) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "capture failed"); + return ESP_FAIL; + } + + httpd_resp_set_type(req, "image/jpeg"); + esp_err_t res = httpd_resp_send(req, (const char *)fb->buf, fb->len); + esp_camera_fb_return(fb); + return res; +} + +static esp_err_t handleStream(httpd_req_t *req) { + httpd_resp_set_type(req, STREAM_CONTENT_TYPE); + + while (true) { + camera_fb_t *fb = esp_camera_fb_get(); + if (!fb) { + return ESP_FAIL; + } + + uint8_t *jpeg_buf = fb->buf; + size_t jpeg_len = fb->len; + bool free_jpeg = false; + + if (fb->format != PIXFORMAT_JPEG) { + if (!frame2jpg(fb, 80, &jpeg_buf, &jpeg_len)) { + esp_camera_fb_return(fb); + return ESP_FAIL; + } + free_jpeg = true; + } + + char part[64]; + int len = snprintf(part, sizeof(part), STREAM_PART, (unsigned)jpeg_len); + + if (httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)) != ESP_OK || + httpd_resp_send_chunk(req, part, len) != ESP_OK || + httpd_resp_send_chunk(req, (const char *)jpeg_buf, jpeg_len) != ESP_OK) { + if (free_jpeg) free(jpeg_buf); + esp_camera_fb_return(fb); + return ESP_FAIL; + } + + if (free_jpeg) free(jpeg_buf); + esp_camera_fb_return(fb); + } + + return ESP_OK; +} + +static void startWebServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.max_uri_handlers = 8; + + if (httpd_start(&httpd, &config) != ESP_OK) { + Serial.println("HTTP server start failed"); + return; + } + + httpd_uri_t root = { .uri = "/", .method = HTTP_GET, .handler = handleRoot, .user_ctx = nullptr }; + httpd_uri_t status = { .uri = "/status", .method = HTTP_GET, .handler = handleStatus, .user_ctx = nullptr }; + httpd_uri_t control = { .uri = "/control", .method = HTTP_GET, .handler = handleControl, .user_ctx = nullptr }; + httpd_uri_t capture = { .uri = "/capture", .method = HTTP_GET, .handler = handleCapture, .user_ctx = nullptr }; + httpd_uri_t stream = { .uri = "/stream", .method = HTTP_GET, .handler = handleStream, .user_ctx = nullptr }; + + httpd_register_uri_handler(httpd, &root); + httpd_register_uri_handler(httpd, &status); + httpd_register_uri_handler(httpd, &control); + httpd_register_uri_handler(httpd, &capture); + httpd_register_uri_handler(httpd, &stream); +} + +void setup() { + Serial.begin(115200); + delay(200); + + if (!cameraPinsOk()) { + Serial.println("Camera pins are not set. Update src/config.h."); + while (true) delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASS); + Serial.print("Connecting to Wi-Fi"); + + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(300); + } + + Serial.println(); + Serial.print("Connected, IP: "); + Serial.println(WiFi.localIP()); + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_0; + config.ledc_timer = LEDC_TIMER_0; + config.pin_d0 = CAM_PIN_Y2; + config.pin_d1 = CAM_PIN_Y3; + config.pin_d2 = CAM_PIN_Y4; + config.pin_d3 = CAM_PIN_Y5; + config.pin_d4 = CAM_PIN_Y6; + config.pin_d5 = CAM_PIN_Y7; + config.pin_d6 = CAM_PIN_Y8; + config.pin_d7 = CAM_PIN_Y9; + config.pin_xclk = CAM_PIN_XCLK; + config.pin_pclk = CAM_PIN_PCLK; + config.pin_vsync = CAM_PIN_VSYNC; + config.pin_href = CAM_PIN_HREF; + config.pin_sccb_sda = CAM_PIN_SIOD; + config.pin_sccb_scl = CAM_PIN_SIOC; + config.pin_pwdn = CAM_PIN_PWDN; + config.pin_reset = CAM_PIN_RESET; + config.xclk_freq_hz = 20000000; + config.pixel_format = PIXFORMAT_JPEG; + config.grab_mode = CAMERA_GRAB_LATEST; + + // FPS preset: smaller frame + higher JPEG quality value (more compression). + // Note: lower "quality" number = better quality but slower. + if (psramFound()) { + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 20; + config.fb_count = 2; + config.fb_location = CAMERA_FB_IN_PSRAM; + } else { + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 20; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_DRAM; + } + + if (esp_camera_init(&config) != ESP_OK) { + Serial.println("Camera init failed"); + while (true) delay(1000); + } + + startWebServer(); + Serial.println("Camera server ready: http:///"); +} + +void loop() { + delay(1000); +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html