From c0fa16dd711446545a968b6ef3819896c23f080d Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Wed, 8 May 2024 23:36:35 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=B7=20Adds=20camera=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/lib/models.ts | 16 +- app/src/routes/menu.svelte | 15 ++ app/src/routes/peripherals/+page.ts | 7 + .../routes/peripherals/camera/+page.svelte | 7 + app/src/routes/peripherals/camera/+page.ts | 7 + .../routes/peripherals/camera/Camera.svelte | 67 ++++++ .../peripherals/camera/CameraSetting.svelte | 70 ++++++ esp32/lib/ESP32-sveltekit/CameraService.cpp | 90 ++++++++ esp32/lib/ESP32-sveltekit/CameraService.h | 37 +++ .../ESP32-sveltekit/CameraSettingsService.h | 182 +++++++++++++++ esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp | 218 ++++++++++-------- esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h | 16 ++ esp32/lib/ESP32-sveltekit/Features.h | 5 + esp32/lib/ESP32-sveltekit/FeaturesService.cpp | 1 + esp32/lib/PsychicHttp/src/PsychicHttp.h | 13 +- 15 files changed, 646 insertions(+), 105 deletions(-) create mode 100644 app/src/routes/peripherals/+page.ts create mode 100644 app/src/routes/peripherals/camera/+page.svelte create mode 100644 app/src/routes/peripherals/camera/+page.ts create mode 100644 app/src/routes/peripherals/camera/Camera.svelte create mode 100644 app/src/routes/peripherals/camera/CameraSetting.svelte create mode 100644 esp32/lib/ESP32-sveltekit/CameraService.cpp create mode 100644 esp32/lib/ESP32-sveltekit/CameraService.h create mode 100644 esp32/lib/ESP32-sveltekit/CameraSettingsService.h diff --git a/app/src/lib/models.ts b/app/src/lib/models.ts index 2904f37..4590350 100644 --- a/app/src/lib/models.ts +++ b/app/src/lib/models.ts @@ -154,4 +154,18 @@ export type MQTTSettings = { client_id: string; keep_alive: number; clean_session: boolean; -}; \ No newline at end of file +}; + +export type CameraSettings = { + framesize: number; + quality: number; + brightness: number; + contrast: number; + saturation: number; + sharpness: number; + denoise: number; + special_effect: number; + wb_mode: number; + vflip: boolean; + hmirror: boolean; +}; diff --git a/app/src/routes/menu.svelte b/app/src/routes/menu.svelte index d7dc740..0584af6 100644 --- a/app/src/routes/menu.svelte +++ b/app/src/routes/menu.svelte @@ -4,6 +4,8 @@ import Users from '~icons/mdi/users'; import Settings from '~icons/mdi/settings'; import MdiController from '~icons/mdi/controller'; + import Devices from '~icons/mdi/devices' + import Camera from '~icons/mdi/camera-outline'; import Health from '~icons/mdi/stethoscope'; import Folder from '~icons/mdi/folder-outline'; import Update from '~icons/mdi/reload'; @@ -51,6 +53,19 @@ href: '/controller', feature: true, }, + { + title: 'Peripherals', + icon: Devices, + feature: true, + submenu: [ + { + title: 'Camera', + icon: Camera, + href: '/peripherals/camera', + feature: $page.data.features.camera, + } + ] + }, { title: 'Connections', icon: Remote, diff --git a/app/src/routes/peripherals/+page.ts b/app/src/routes/peripherals/+page.ts new file mode 100644 index 0000000..c637df9 --- /dev/null +++ b/app/src/routes/peripherals/+page.ts @@ -0,0 +1,7 @@ +import type { PageLoad } from './$types'; +import { goto } from '$app/navigation'; + +export const load = (async () => { + goto('/'); + return; +}) satisfies PageLoad; diff --git a/app/src/routes/peripherals/camera/+page.svelte b/app/src/routes/peripherals/camera/+page.svelte new file mode 100644 index 0000000..b68f9df --- /dev/null +++ b/app/src/routes/peripherals/camera/+page.svelte @@ -0,0 +1,7 @@ + + +
+ +
diff --git a/app/src/routes/peripherals/camera/+page.ts b/app/src/routes/peripherals/camera/+page.ts new file mode 100644 index 0000000..ec75227 --- /dev/null +++ b/app/src/routes/peripherals/camera/+page.ts @@ -0,0 +1,7 @@ +import type { PageLoad } from './$types'; + +export const load = (async () => { + return { + title: 'Camera' + }; +}) satisfies PageLoad; diff --git a/app/src/routes/peripherals/camera/Camera.svelte b/app/src/routes/peripherals/camera/Camera.svelte new file mode 100644 index 0000000..9c7e2fe --- /dev/null +++ b/app/src/routes/peripherals/camera/Camera.svelte @@ -0,0 +1,67 @@ + + + + + + Camera + + +
+ +
+ + +
\ No newline at end of file diff --git a/app/src/routes/peripherals/camera/CameraSetting.svelte b/app/src/routes/peripherals/camera/CameraSetting.svelte new file mode 100644 index 0000000..b365c08 --- /dev/null +++ b/app/src/routes/peripherals/camera/CameraSetting.svelte @@ -0,0 +1,70 @@ + + +{#await getCameraSettings()} + +{:then _} +
+ + + + + + + + + + + + + +
+{/await} \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/CameraService.cpp b/esp32/lib/ESP32-sveltekit/CameraService.cpp new file mode 100644 index 0000000..07fb827 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/CameraService.cpp @@ -0,0 +1,90 @@ +#include + +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"; + +SemaphoreHandle_t cameraMutex = xSemaphoreCreateMutex(); + +camera_fb_t *safe_camera_fb_get() { + camera_fb_t *fb = NULL; + if (xSemaphoreTake(cameraMutex, portMAX_DELAY) == pdTRUE) { + fb = esp_camera_fb_get(); + xSemaphoreGive(cameraMutex); + } + return fb; +} + +CameraService::CameraService(PsychicHttpServer *server, + TaskManager *taskManager, + SecurityManager *securityManager) + : _server(server), + _taskManager(taskManager), + _securityManager(securityManager), + _videoStream(_STREAM_CONTENT_TYPE) {} +void CameraService::begin() { + InitializeCamera(); + _server->on( + STILL_SERVICE_PATH, HTTP_GET, + _securityManager->wrapRequest( + std::bind(&CameraService::cameraStill, this, std::placeholders::_1), + AuthenticationPredicates::IS_AUTHENTICATED)); + + ESP_LOGV("CameraService", "Registered GET endpoint: %s", STILL_SERVICE_PATH); +} + +esp_err_t CameraService::InitializeCamera() { + camera_config_t camera_config; + camera_config.ledc_channel = LEDC_CHANNEL_0; + camera_config.ledc_timer = LEDC_TIMER_0; + camera_config.pin_d0 = Y2_GPIO_NUM; + camera_config.pin_d1 = Y3_GPIO_NUM; + camera_config.pin_d2 = Y4_GPIO_NUM; + camera_config.pin_d3 = Y5_GPIO_NUM; + camera_config.pin_d4 = Y6_GPIO_NUM; + camera_config.pin_d5 = Y7_GPIO_NUM; + camera_config.pin_d6 = Y8_GPIO_NUM; + camera_config.pin_d7 = Y9_GPIO_NUM; + camera_config.pin_xclk = XCLK_GPIO_NUM; + camera_config.pin_pclk = PCLK_GPIO_NUM; + camera_config.pin_vsync = VSYNC_GPIO_NUM; + camera_config.pin_href = HREF_GPIO_NUM; + camera_config.pin_sccb_sda = SIOD_GPIO_NUM; + camera_config.pin_sccb_scl = SIOC_GPIO_NUM; + camera_config.pin_pwdn = PWDN_GPIO_NUM; + camera_config.pin_reset = RESET_GPIO_NUM; + camera_config.xclk_freq_hz = 20000000; + camera_config.pixel_format = PIXFORMAT_JPEG; + + if (psramFound()) { + camera_config.frame_size = FRAMESIZE_SVGA; + camera_config.jpeg_quality = 10; + camera_config.fb_count = 2; + } else { + camera_config.frame_size = FRAMESIZE_SVGA; + camera_config.jpeg_quality = 12; + camera_config.fb_count = 1; + } + + log_i("Initializing camera"); + esp_err_t err = esp_camera_init(&camera_config); + if (err != ESP_OK) log_e("Camera probe failed with error 0x%x", err); + + return err; +} + +esp_err_t CameraService::cameraStill(PsychicRequest *request) { + camera_fb_t *fb = safe_camera_fb_get(); + if (!fb) { + Serial.println("Camera capture failed"); + request->reply(500, "text/plain", "Camera capture failed"); + return ESP_FAIL; + } + PsychicStreamResponse response = PsychicStreamResponse(request, "image/jpeg", "capture.jpg"); + response.beginSend(); + response.write(fb->buf, fb->len); + esp_camera_fb_return(fb); + return response.endSend(); +} diff --git a/esp32/lib/ESP32-sveltekit/CameraService.h b/esp32/lib/ESP32-sveltekit/CameraService.h new file mode 100644 index 0000000..b610eb5 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/CameraService.h @@ -0,0 +1,37 @@ +#ifndef CameraService_h +#define CameraService_h + +#define CAMERA_MODEL_AI_THINKER + +#include +#include +#include +#include +#include +#include +#include + +#define STREAM_SERVICE_PATH "/api/camera/stream" +#define STILL_SERVICE_PATH "/api/camera/still" + +#define PART_BOUNDARY "frame" + +camera_fb_t *safe_camera_fb_get(); + +class CameraService +{ + public: + CameraService(PsychicHttpServer *server, TaskManager *taskManager, SecurityManager *securityManager); + + void begin(); + + private: + PsychicHttpServer *_server; + TaskManager *_taskManager; + SecurityManager *_securityManager; + PsychicStream _videoStream; + esp_err_t cameraStill(PsychicRequest *request); + esp_err_t InitializeCamera(); +}; + +#endif // end CameraService_h diff --git a/esp32/lib/ESP32-sveltekit/CameraSettingsService.h b/esp32/lib/ESP32-sveltekit/CameraSettingsService.h new file mode 100644 index 0000000..a7c14db --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/CameraSettingsService.h @@ -0,0 +1,182 @@ +#ifndef CameraSettingsService_h +#define CameraSettingsService_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAMERA_SETTINGS_FILE "/config/cameraSettings.json" +#define EVENT_CAMERA_SETTINGS "CameraSettings" +#define CAMERA_SETTINGS_PATH "/api/camera/settings" + +class CameraSettings { + public: + pixformat_t pixformat; + framesize_t framesize; // 0 - 10 + uint8_t quality; // 0 - 63 + int8_t brightness; //-2 - 2 + int8_t contrast; //-2 - 2 + int8_t saturation; //-2 - 2 + int8_t sharpness; //-2 - 2 + uint8_t denoise; + gainceiling_t gainceiling; + uint8_t whitebal; + uint8_t special_effect; // 0 - 6 + uint8_t wb_mode; // 0 - 4 + uint8_t awb; + uint8_t exposure_ctrl; + uint8_t awb_gain; + uint8_t gain_ctrl; + uint8_t aec; + uint8_t aec2; + int8_t ae_level; //-2 - 2 + uint16_t aec_value; // 0 - 1200 + uint8_t agc; + uint8_t agc_gain; // 0 - 30 + uint8_t bpc; + uint8_t wpc; + uint8_t raw_gma; + uint8_t lenc; + uint8_t hmirror; + uint8_t vflip; + uint8_t dcw; + uint8_t colorbar; + + static void read(CameraSettings &settings, JsonObject &root) { + root["pixformat"] = settings.pixformat; + root["framesize"] = settings.framesize; + root["quality"] = settings.quality; + root["brightness"] = settings.brightness; + root["contrast"] = settings.contrast; + root["saturation"] = settings.saturation; + root["sharpness"] = settings.sharpness; + root["denoise"] = settings.denoise; + root["special_effect"] = settings.special_effect; + root["wb_mode"] = settings.wb_mode; + root["exposure_ctrl"] = settings.exposure_ctrl; + root["gain_ctrl"] = settings.gain_ctrl; + root["awb"] = settings.awb; + root["awb_gain"] = settings.awb_gain; + root["aec"] = settings.aec; + root["aec2"] = settings.aec2; + root["ae_level"] = settings.ae_level; + root["aec_value"] = settings.aec_value; + root["agc"] = settings.agc; + root["agc_gain"] = settings.agc_gain; + root["gainceiling"] = settings.gainceiling; + root["bpc"] = settings.bpc; + root["wpc"] = settings.wpc; + root["raw_gma"] = settings.raw_gma; + root["lenc"] = settings.lenc; + root["hmirror"] = settings.hmirror; + root["vflip"] = settings.vflip; + root["dcw"] = settings.dcw; + root["colorbar"] = settings.colorbar; + } + + static StateUpdateResult update(JsonObject &root, + CameraSettings &settings) { + settings.pixformat = root["pixformat"]; + settings.framesize = root["framesize"]; + settings.brightness = root["brightness"]; + settings.contrast = root["contrast"]; + settings.quality = root["quality"]; + settings.contrast = root["contrast"]; + settings.saturation = root["saturation"]; + settings.sharpness = root["sharpness"]; + settings.denoise = root["denoise"]; + settings.exposure_ctrl = root["exposure_ctrl"]; + settings.gain_ctrl = root["gain_ctrl"]; + settings.special_effect = root["special_effect"]; + settings.wb_mode = root["wb_mode"]; + settings.awb = root["awb"]; + settings.awb_gain = root["awb_gain"]; + settings.aec = root["aec"]; + settings.aec2 = root["aec2"]; + settings.ae_level = root["ae_level"]; + settings.aec_value = root["aec_value"]; + settings.agc = root["agc"]; + settings.agc_gain = root["agc_gain"]; + settings.gainceiling = root["gainceiling"]; + settings.bpc = root["bpc"]; + settings.wpc = root["wpc"]; + settings.raw_gma = root["raw_gma"]; + settings.lenc = root["lenc"]; + settings.hmirror = root["hmirror"]; + settings.vflip = root["vflip"]; + settings.dcw = root["dcw"]; + settings.colorbar = root["colorbar"]; + + return StateUpdateResult::CHANGED; + }; +}; + +class CameraSettingsService : public StatefulService { + public: + CameraSettingsService(PsychicHttpServer *server, FS *fs, + SecurityManager *securityManager, EventSocket *socket) + : _server(server), + _securityManager(securityManager), + _httpEndpoint(CameraSettings::read, CameraSettings::update, this, + server, CAMERA_SETTINGS_PATH, securityManager, + AuthenticationPredicates::IS_ADMIN), + _eventEndpoint(CameraSettings::read, CameraSettings::update, this, + socket, EVENT_CAMERA_SETTINGS), + _fsPersistence(CameraSettings::read, CameraSettings::update, this, fs, + CAMERA_SETTINGS_FILE) { + addUpdateHandler([&](const String &originId) { updateCamera(); }, + false); + } + + void begin() { + _httpEndpoint.begin(); + _eventEndpoint.begin(); + } + + void updateCamera() { + ESP_LOGI("CameraSettings", "Updating camera settings"); + sensor_t *s = esp_camera_sensor_get(); + s->set_pixformat(s, _state.pixformat); + s->set_framesize(s, _state.framesize); + s->set_brightness(s, _state.brightness); + s->set_contrast(s, _state.contrast); + s->set_saturation(s, _state.saturation); + s->set_sharpness(s, _state.sharpness); + s->set_denoise(s, _state.denoise); + s->set_gainceiling(s, _state.gainceiling); + s->set_quality(s, _state.quality); + s->set_colorbar(s, _state.colorbar); + s->set_whitebal(s, _state.whitebal); + s->set_awb_gain(s, _state.awb_gain); + s->set_wb_mode(s, _state.wb_mode); + s->set_exposure_ctrl(s, _state.exposure_ctrl); + s->set_aec2(s, _state.aec2); + s->set_ae_level(s, _state.ae_level); + s->set_aec_value(s, _state.aec_value); + s->set_gain_ctrl(s, _state.gain_ctrl); + s->set_agc_gain(s, _state.agc_gain); + s->set_bpc(s, _state.bpc); + s->set_wpc(s, _state.wpc); + s->set_special_effect(s, _state.special_effect); + s->set_raw_gma(s, _state.raw_gma); + s->set_lenc(s, _state.lenc); + s->set_hmirror(s, _state.hmirror); + s->set_vflip(s, _state.vflip); + s->set_dcw(s, _state.dcw); + } + + private: + PsychicHttpServer *_server; + SecurityManager *_securityManager; + HttpEndpoint _httpEndpoint; + EventEndpoint _eventEndpoint; + FSPersistence _fsPersistence; +}; + +#endif // end CameraSettingsService_h diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp index b1c1951..ddc4f56 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp @@ -15,15 +15,20 @@ #include -ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEndpoints) - : _server(server), _numberEndpoints(numberEndpoints), _taskManager(), - _featureService(server), _securitySettingsService(server, &ESPFS), +ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, + unsigned int numberEndpoints) + : _server(server), + _numberEndpoints(numberEndpoints), + _taskManager(), + _featureService(server), + _securitySettingsService(server, &ESPFS), _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_socket), _wifiScanner(server, &_securitySettingsService), _wifiStatus(server, &_securitySettingsService), _apSettingsService(server, &ESPFS, &_securitySettingsService), _apStatus(server, &_securitySettingsService, &_apSettingsService), - _socket(server, &_securitySettingsService, AuthenticationPredicates::IS_AUTHENTICATED), + _socket(server, &_securitySettingsService, + AuthenticationPredicates::IS_AUTHENTICATED), #if FT_ENABLED(FT_NTP) _ntpSettingsService(server, &ESPFS, &_securitySettingsService), _ntpStatus(server, &_securitySettingsService), @@ -32,7 +37,8 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _uploadFirmwareService(server, &_securitySettingsService), #endif #if FT_ENABLED(FT_DOWNLOAD_FIRMWARE) - _downloadFirmwareService(server, &_securitySettingsService, &_socket, &_taskManager), + _downloadFirmwareService(server, &_securitySettingsService, &_socket, + &_taskManager), #endif #if FT_ENABLED(FT_MQTT) _mqttSettingsService(server, &ESPFS, &_securitySettingsService), @@ -49,153 +55,169 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd #endif #if FT_ENABLED(FT_ANALYTICS) _analyticsService(&_socket, &_taskManager), +#endif +#if FT_ENABLED(FT_CAMERA) + _cameraService(server, &_taskManager, &_securitySettingsService), + _cameraSettingsService(server, &ESPFS, &_securitySettingsService, &_socket), #endif _restartService(server, &_securitySettingsService), _factoryResetService(server, &ESPFS, &_securitySettingsService), _systemStatus(server, &_securitySettingsService), _fileExplorer(server, &_securitySettingsService), - _motionService(_server, &_socket, &_securitySettingsService, &_taskManager) { + _motionService(_server, &_socket, &_securitySettingsService, + &_taskManager) { } void ESP32SvelteKit::begin() { - ESP_LOGV("ESP32SvelteKit", "Loading settings from files system"); - ESP_LOGI("Running Firmware Version: %s", APP_VERSION); - ESPFS.begin(true); + ESP_LOGV("ESP32SvelteKit", "Loading settings from files system"); + ESP_LOGI("Running Firmware Version: %s", APP_VERSION); + ESPFS.begin(true); - _wifiSettingsService.initWiFi(); + _wifiSettingsService.initWiFi(); - setupServer(); + setupServer(); - setupMDNS(); + setupMDNS(); - startServices(); + startServices(); - ESP_LOGV("ESP32SvelteKit", "Starting loop task"); - _taskManager.createTask(this->_loopImpl, "Spot main", 4096, this, - (tskIDLE_PRIORITY + 1), NULL, - ESP32SVELTEKIT_RUNNING_CORE); + ESP_LOGV("ESP32SvelteKit", "Starting loop task"); + _taskManager.createTask(this->_loopImpl, "Spot main", 4096, this, + (tskIDLE_PRIORITY + 1), NULL, + ESP32SVELTEKIT_RUNNING_CORE); } void ESP32SvelteKit::setupServer() { - _server->config.max_uri_handlers = _numberEndpoints; - _server->listen(80); + _server->config.max_uri_handlers = _numberEndpoints; + _server->listen(80); #ifdef EMBED_WWW - ESP_LOGV("ESP32SvelteKit","Registering routes from PROGMEM static resources"); - WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) { - PsychicHttpRequestCallback requestHandler = [contentType, content, len](PsychicRequest *request) { - PsychicResponse response(request); - response.setCode(200); - response.setContentType(contentType.c_str()); - response.addHeader("Content-Encoding", "gzip"); - response.addHeader("Cache-Control", "public, immutable, max-age=31536000"); - response.setContent(content, len); - return response.send(); - }; - PsychicWebHandler *handler = new PsychicWebHandler(); - handler->onRequest(requestHandler); - _server->on(uri.c_str(), HTTP_GET, handler); + ESP_LOGV("ESP32SvelteKit", + "Registering routes from PROGMEM static resources"); + WWWData::registerRoutes([&](const String &uri, const String &contentType, + const uint8_t *content, size_t len) { + PsychicHttpRequestCallback requestHandler = + [contentType, content, len](PsychicRequest *request) { + PsychicResponse response(request); + response.setCode(200); + response.setContentType(contentType.c_str()); + response.addHeader("Content-Encoding", "gzip"); + response.addHeader("Cache-Control", + "public, immutable, max-age=31536000"); + response.setContent(content, len); + return response.send(); + }; + PsychicWebHandler *handler = new PsychicWebHandler(); + handler->onRequest(requestHandler); + _server->on(uri.c_str(), HTTP_GET, handler); - // Set default end-point for all non matching requests - // this is easier than using webServer.onNotFound() - if (uri.equals("/index.html")) { - _server->defaultEndpoint->setHandler(handler); - } - }); + // Set default end-point for all non matching requests + // this is easier than using webServer.onNotFound() + if (uri.equals("/index.html")) { + _server->defaultEndpoint->setHandler(handler); + } + }); #else - // Serve static resources from /www/ - ESP_LOGV("ESP32SvelteKit", - "Registering routes from FS /www/ static resources"); - _server->serveStatic("/_app/", ESPFS, "/www/_app/"); - _server->serveStatic("/favicon.png", ESPFS, "/www/favicon.png"); - // Serving all other get requests with "/www/index.htm" - _server->onNotFound([](PsychicRequest *request) { - if (request->method() == HTTP_GET) { - PsychicFileResponse response(request, ESPFS, "/www/index.html", - "text/html"); - return response.send(); - // String url = "http://" + request->host() + "/index.html"; - // request->redirect(url.c_str()); - } - }); + // Serve static resources from /www/ + ESP_LOGV("ESP32SvelteKit", + "Registering routes from FS /www/ static resources"); + _server->serveStatic("/_app/", ESPFS, "/www/_app/"); + _server->serveStatic("/favicon.png", ESPFS, "/www/favicon.png"); + // Serving all other get requests with "/www/index.htm" + _server->onNotFound([](PsychicRequest *request) { + if (request->method() == HTTP_GET) { + PsychicFileResponse response(request, ESPFS, "/www/index.html", + "text/html"); + return response.send(); + // String url = "http://" + request->host() + "/index.html"; + // request->redirect(url.c_str()); + } + }); #endif #ifdef SERVE_CONFIG_FILES - _server->serveStatic("/config/", ESPFS, "/config/"); + _server->serveStatic("/config/", ESPFS, "/config/"); #endif #if defined(ENABLE_CORS) - ESP_LOGV("ESP32SvelteKit", "Enabling CORS headers"); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization"); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true"); + ESP_LOGV("ESP32SvelteKit", "Enabling CORS headers"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", + CORS_ORIGIN); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", + "Accept, Content-Type, Authorization"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", + "true"); #endif - DefaultHeaders::Instance().addHeader("Server", _appName); + DefaultHeaders::Instance().addHeader("Server", _appName); } void ESP32SvelteKit::setupMDNS() { - ESP_LOGV("ESP32SvelteKit", "Starting MDNS"); - MDNS.begin(_wifiSettingsService.getHostname().c_str()); - MDNS.setInstanceName(_appName); - MDNS.addService("http", "tcp", 80); - MDNS.addService("ws", "tcp", 80); - MDNS.addServiceTxt("http", "tcp", "Firmware Version", APP_VERSION); + ESP_LOGV("ESP32SvelteKit", "Starting MDNS"); + MDNS.begin(_wifiSettingsService.getHostname().c_str()); + MDNS.setInstanceName(_appName); + MDNS.addService("http", "tcp", 80); + MDNS.addService("ws", "tcp", 80); + MDNS.addServiceTxt("http", "tcp", "Firmware Version", APP_VERSION); } void ESP32SvelteKit::startServices() { - _apStatus.begin(); - _socket.begin(); - _apSettingsService.begin(); - _factoryResetService.begin(); - _featureService.begin(); - _restartService.begin(); - _systemStatus.begin(); - _wifiSettingsService.begin(); - _wifiScanner.begin(); - _wifiStatus.begin(); + _apStatus.begin(); + _socket.begin(); + _apSettingsService.begin(); + _factoryResetService.begin(); + _featureService.begin(); + _restartService.begin(); + _systemStatus.begin(); + _wifiSettingsService.begin(); + _wifiScanner.begin(); + _wifiStatus.begin(); #if FT_ENABLED(FT_UPLOAD_FIRMWARE) - _uploadFirmwareService.begin(); + _uploadFirmwareService.begin(); #endif #if FT_ENABLED(FT_DOWNLOAD_FIRMWARE) - _downloadFirmwareService.begin(); + _downloadFirmwareService.begin(); #endif #if FT_ENABLED(FT_NTP) - _ntpSettingsService.begin(); - _ntpStatus.begin(); + _ntpSettingsService.begin(); + _ntpStatus.begin(); #endif #if FT_ENABLED(FT_MQTT) - _mqttSettingsService.begin(); - _mqttStatus.begin(); + _mqttSettingsService.begin(); + _mqttStatus.begin(); #endif #if FT_ENABLED(FT_SECURITY) - _authenticationService.begin(); - _securitySettingsService.begin(); + _authenticationService.begin(); + _securitySettingsService.begin(); #endif #if FT_ENABLED(FT_ANALYTICS) - _analyticsService.begin(); + _analyticsService.begin(); #endif #if FT_ENABLED(FT_SLEEP) - _sleepService.begin(); + _sleepService.begin(); #endif #if FT_ENABLED(FT_BATTERY) - _batteryService.begin(); + _batteryService.begin(); +#endif + _taskManager.begin(); + _fileExplorer.begin(); + _motionService.begin(); +#if FT_ENABLED(FT_CAMERA) + _cameraService.begin(); + _cameraSettingsService.begin(); #endif - _taskManager.begin(); - _fileExplorer.begin(); - _motionService.begin(); } void IRAM_ATTR ESP32SvelteKit::_loop() { - while (1) { - _wifiSettingsService.loop(); - _apSettingsService.loop(); + while (1) { + _wifiSettingsService.loop(); + _apSettingsService.loop(); #if FT_ENABLED(FT_MQTT) - _mqttSettingsService.loop(); + _mqttSettingsService.loop(); #endif #if FT_ENABLED(FT_ANALYTICS) - _analyticsService.loop(); + _analyticsService.loop(); #endif - _motionService.loop(); - vTaskDelay(20 / portTICK_PERIOD_MS); - } + _motionService.loop(); + vTaskDelay(20 / portTICK_PERIOD_MS); + } } diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h index 795fdce..5316368 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include #include @@ -164,6 +166,16 @@ public: { return &_motionService; } +#if FT_ENABLED(FT_CAMERA) + CameraService *getCameraService() + { + return &_cameraService; + } + CameraSettingsService *getCameraSettingsService() + { + return &_cameraSettingsService; + } +#endif void factoryReset() { @@ -223,6 +235,10 @@ private: TaskManager _taskManager; FileExplorer _fileExplorer; MotionService _motionService; +#if FT_ENABLED(FT_CAMERA) + CameraService _cameraService; + CameraSettingsService _cameraSettingsService; +#endif String _appName = APP_NAME; diff --git a/esp32/lib/ESP32-sveltekit/Features.h b/esp32/lib/ESP32-sveltekit/Features.h index 60957cf..4a12410 100644 --- a/esp32/lib/ESP32-sveltekit/Features.h +++ b/esp32/lib/ESP32-sveltekit/Features.h @@ -57,4 +57,9 @@ #define FT_ANALYTICS 1 #endif +// ESP32 camera off by default +#ifndef FT_CAMERA +#define FT_CAMERA 0 +#endif + #endif diff --git a/esp32/lib/ESP32-sveltekit/FeaturesService.cpp b/esp32/lib/ESP32-sveltekit/FeaturesService.cpp index 62953c5..75a06c3 100644 --- a/esp32/lib/ESP32-sveltekit/FeaturesService.cpp +++ b/esp32/lib/ESP32-sveltekit/FeaturesService.cpp @@ -30,6 +30,7 @@ void FeaturesService::begin() { root["sleep"] = FT_SLEEP; root["battery"] = FT_BATTERY; root["analytics"] = FT_ANALYTICS; + root["camera"] = FT_CAMERA; root["firmware_version"] = APP_VERSION; root["firmware_name"] = APP_NAME; root["firmware_built_target"] = BUILD_TARGET; diff --git a/esp32/lib/PsychicHttp/src/PsychicHttp.h b/esp32/lib/PsychicHttp/src/PsychicHttp.h index 3e9da55..eacc8c3 100644 --- a/esp32/lib/PsychicHttp/src/PsychicHttp.h +++ b/esp32/lib/PsychicHttp/src/PsychicHttp.h @@ -3,19 +3,20 @@ //#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread -#include +#include "PsychicEndpoint.h" +#include "PsychicEventSource.h" +#include "PsychicFileResponse.h" +#include "PsychicHandler.h" #include "PsychicHttpServer.h" +#include "PsychicJson.h" #include "PsychicRequest.h" #include "PsychicResponse.h" -#include "PsychicEndpoint.h" -#include "PsychicHandler.h" #include "PsychicStaticFileHandler.h" -#include "PsychicFileResponse.h" +#include "PsychicStream.h" #include "PsychicStreamResponse.h" #include "PsychicUploadHandler.h" #include "PsychicWebSocket.h" -#include "PsychicEventSource.h" -#include "PsychicJson.h" +#include #ifdef ENABLE_ASYNC #include "async_worker.h"