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"