📹 Adds camera services
This commit is contained in:
@@ -2,37 +2,16 @@
|
|||||||
import { user } from '$lib/stores/user';
|
import { user } from '$lib/stores/user';
|
||||||
import SettingsCard from "$lib/components/SettingsCard.svelte";
|
import SettingsCard from "$lib/components/SettingsCard.svelte";
|
||||||
import Camera from '~icons/mdi/camera-outline'
|
import Camera from '~icons/mdi/camera-outline'
|
||||||
import VideoCamera from '~icons/mdi/videocam-outline'
|
|
||||||
import Reload from '~icons/mdi/reload'
|
|
||||||
import Record from '~icons/mdi/radio-button-unchecked'
|
import Record from '~icons/mdi/radio-button-unchecked'
|
||||||
import Recording from '~icons/mdi/radio-button-checked'
|
|
||||||
|
|
||||||
import Spinner from '$lib/components/Spinner.svelte';
|
|
||||||
import CameraSetting from './CameraSetting.svelte';
|
import CameraSetting from './CameraSetting.svelte';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
const ws_token = `?access_token=${$user.bearer_token}`
|
const ws_token = `?access_token=${$user.bearer_token}`
|
||||||
|
|
||||||
let stillId = 0
|
let source = "/api/camera/stream"+ ws_token;
|
||||||
|
|
||||||
let recording = false
|
|
||||||
|
|
||||||
let videoMode = false
|
|
||||||
|
|
||||||
const takeStill = () => stillId += 1
|
|
||||||
|
|
||||||
const toggleMode = () => {
|
|
||||||
videoMode = !videoMode
|
|
||||||
}
|
|
||||||
|
|
||||||
let timer:number
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
timer = setInterval(takeStill, 1000)
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
clearInterval(timer)
|
source = ""
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -40,28 +19,6 @@
|
|||||||
<SettingsCard collapsible={false}>
|
<SettingsCard collapsible={false}>
|
||||||
<Camera slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
<Camera slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||||
<span slot="title">Camera</span>
|
<span slot="title">Camera</span>
|
||||||
<img src={"/api/camera/still"+ ws_token + "key=" + stillId} alt="Live-stream" class="w-full rounded-lg shadow-lg" />
|
<img src={source} alt="Live-stream" class="w-full rounded-lg shadow-lg" />
|
||||||
<CameraSetting />
|
<CameraSetting />
|
||||||
<div>
|
|
||||||
<!-- <div class="relative">
|
|
||||||
<div class="-top-12 absolute flex justify-center w-full">
|
|
||||||
<label class="swap">
|
|
||||||
<input type="checkbox" bind:value={recording} />
|
|
||||||
<div class="swap-on"><Recording class="h-10 w-10" /></div>
|
|
||||||
<div class="swap-off"><Record class="h-10 w-10" /></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="flex justify-center gap-4 p-2 rounded-xl bg-opacity-50 bg-slate-600 mt-2">
|
|
||||||
<button class="btn-outline" class:btn-primary={!videoMode} on:click={toggleMode}><Camera class="h-5 w-5" /></button>
|
|
||||||
<button class="btn-outline" class:btn-primary={ videoMode} on:click={toggleMode}><VideoCamera class="h-5 w-5" /></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <button class="btn btn-primary inline-flex items-center" on:click={takeStill}>
|
|
||||||
<Reload class="mr-2 h-5 w-5" />
|
|
||||||
<span>Reload</span>
|
|
||||||
</button> -->
|
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
@@ -17,6 +17,16 @@ camera_fb_t *safe_camera_fb_get() {
|
|||||||
return fb;
|
return fb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sensor_t *safe_sensor_get() {
|
||||||
|
sensor_t *s = NULL;
|
||||||
|
if (xSemaphoreTake(cameraMutex, portMAX_DELAY) == pdTRUE) {
|
||||||
|
s = esp_camera_sensor_get();
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void safe_sensor_return() { xSemaphoreGive(cameraMutex); }
|
||||||
|
|
||||||
CameraService::CameraService(PsychicHttpServer *server,
|
CameraService::CameraService(PsychicHttpServer *server,
|
||||||
TaskManager *taskManager,
|
TaskManager *taskManager,
|
||||||
SecurityManager *securityManager)
|
SecurityManager *securityManager)
|
||||||
@@ -31,8 +41,14 @@ void CameraService::begin() {
|
|||||||
_securityManager->wrapRequest(
|
_securityManager->wrapRequest(
|
||||||
std::bind(&CameraService::cameraStill, this, std::placeholders::_1),
|
std::bind(&CameraService::cameraStill, this, std::placeholders::_1),
|
||||||
AuthenticationPredicates::IS_AUTHENTICATED));
|
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||||
|
_server->on(STREAM_SERVICE_PATH, HTTP_GET,
|
||||||
|
_securityManager->wrapRequest(
|
||||||
|
std::bind(&CameraService::cameraStream, this,
|
||||||
|
std::placeholders::_1),
|
||||||
|
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||||
|
|
||||||
ESP_LOGV("CameraService", "Registered GET endpoint: %s", STILL_SERVICE_PATH);
|
ESP_LOGV("CameraService", "Registered GET endpoint: %s", STILL_SERVICE_PATH);
|
||||||
|
ESP_LOGV("CameraService", "Registered GET endpoint: %s", STREAM_SERVICE_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t CameraService::InitializeCamera() {
|
esp_err_t CameraService::InitializeCamera() {
|
||||||
@@ -82,9 +98,69 @@ esp_err_t CameraService::cameraStill(PsychicRequest *request) {
|
|||||||
request->reply(500, "text/plain", "Camera capture failed");
|
request->reply(500, "text/plain", "Camera capture failed");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
PsychicStreamResponse response = PsychicStreamResponse(request, "image/jpeg", "capture.jpg");
|
PsychicStreamResponse response =
|
||||||
|
PsychicStreamResponse(request, "image/jpeg", "capture.jpg");
|
||||||
response.beginSend();
|
response.beginSend();
|
||||||
response.write(fb->buf, fb->len);
|
response.write(fb->buf, fb->len);
|
||||||
esp_camera_fb_return(fb);
|
esp_camera_fb_return(fb);
|
||||||
return response.endSend();
|
return response.endSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void streamTask(void *pv) {
|
||||||
|
esp_err_t res = ESP_OK;
|
||||||
|
|
||||||
|
PsychicRequest *request = static_cast<PsychicRequest*>(pv);
|
||||||
|
|
||||||
|
httpd_req_t *copy = nullptr;
|
||||||
|
res = httpd_req_async_handler_begin(request->request(), ©);
|
||||||
|
if (res != ESP_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PsychicHttpServer *server = request->server();
|
||||||
|
PsychicRequest new_request = PsychicRequest(server, copy);
|
||||||
|
request = &new_request;
|
||||||
|
|
||||||
|
PsychicStreamResponse response = PsychicStreamResponse(request, _STREAM_CONTENT_TYPE);
|
||||||
|
camera_fb_t *fb = NULL;
|
||||||
|
|
||||||
|
char *part_buf[64];
|
||||||
|
size_t buf_len = 0;
|
||||||
|
uint8_t *buf = NULL;
|
||||||
|
int64_t fr_start = esp_timer_get_time();
|
||||||
|
|
||||||
|
response.beginSend();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
fb = safe_camera_fb_get();
|
||||||
|
if (!fb) {
|
||||||
|
ESP_LOGE("Stream", "Camera capture failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(fb->format != PIXFORMAT_JPEG){
|
||||||
|
if(!frame2jpg(fb, 80, &buf, &buf_len)) break;
|
||||||
|
} else {
|
||||||
|
buf_len = fb->len;
|
||||||
|
buf = fb->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, buf_len);
|
||||||
|
size_t w = response.write((const char *)part_buf, hlen);
|
||||||
|
w += response.write((const char *)buf, buf_len);
|
||||||
|
w += response.write((char *)_STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
|
||||||
|
if (w == 62) break;
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
buf = NULL;
|
||||||
|
taskYIELD();
|
||||||
|
int64_t delay = 30000ll - esp_timer_get_time() - fr_start;
|
||||||
|
if (delay > 0) vTaskDelay(pdMS_TO_TICKS(delay));
|
||||||
|
}
|
||||||
|
response.endSend();
|
||||||
|
httpd_req_async_handler_complete(copy);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t CameraService::cameraStream(PsychicRequest *request) {
|
||||||
|
_taskManager->createTask(streamTask, "Stream client task", 4096, request, 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
#ifndef CameraService_h
|
#ifndef CameraService_h
|
||||||
#define CameraService_h
|
#define CameraService_h
|
||||||
|
|
||||||
#define CAMERA_MODEL_AI_THINKER
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <CameraPins.h>
|
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
#include <SecurityManager.h>
|
#include <SecurityManager.h>
|
||||||
#include <TaskManager.h>
|
#include <TaskManager.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_camera.h>
|
#include <esp_camera.h>
|
||||||
|
#include <async_worker.h>
|
||||||
|
|
||||||
|
#define CAMERA_MODEL_AI_THINKER
|
||||||
|
#include <CameraPins.h>
|
||||||
|
|
||||||
#define STREAM_SERVICE_PATH "/api/camera/stream"
|
#define STREAM_SERVICE_PATH "/api/camera/stream"
|
||||||
#define STILL_SERVICE_PATH "/api/camera/still"
|
#define STILL_SERVICE_PATH "/api/camera/still"
|
||||||
@@ -17,6 +18,8 @@
|
|||||||
#define PART_BOUNDARY "frame"
|
#define PART_BOUNDARY "frame"
|
||||||
|
|
||||||
camera_fb_t *safe_camera_fb_get();
|
camera_fb_t *safe_camera_fb_get();
|
||||||
|
sensor_t* safe_sensor_get();
|
||||||
|
void safe_sensor_return();
|
||||||
|
|
||||||
class CameraService
|
class CameraService
|
||||||
{
|
{
|
||||||
@@ -31,6 +34,7 @@ class CameraService
|
|||||||
SecurityManager *_securityManager;
|
SecurityManager *_securityManager;
|
||||||
PsychicStream _videoStream;
|
PsychicStream _videoStream;
|
||||||
esp_err_t cameraStill(PsychicRequest *request);
|
esp_err_t cameraStill(PsychicRequest *request);
|
||||||
|
esp_err_t cameraStream(PsychicRequest *request);
|
||||||
esp_err_t InitializeCamera();
|
esp_err_t InitializeCamera();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef CameraSettingsService_h
|
#ifndef CameraSettingsService_h
|
||||||
#define CameraSettingsService_h
|
#define CameraSettingsService_h
|
||||||
|
|
||||||
|
#include <CameraService.h>
|
||||||
#include <EventEndpoint.h>
|
#include <EventEndpoint.h>
|
||||||
#include <FSPersistence.h>
|
#include <FSPersistence.h>
|
||||||
#include <HttpEndpoint.h>
|
#include <HttpEndpoint.h>
|
||||||
@@ -137,11 +138,42 @@ class CameraSettingsService : public StatefulService<CameraSettings> {
|
|||||||
void begin() {
|
void begin() {
|
||||||
_httpEndpoint.begin();
|
_httpEndpoint.begin();
|
||||||
_eventEndpoint.begin();
|
_eventEndpoint.begin();
|
||||||
|
sensor_t *s = safe_sensor_get();
|
||||||
|
_state.pixformat = s->pixformat;
|
||||||
|
_state.framesize = s->status.framesize;
|
||||||
|
_state.brightness = s->status.brightness;
|
||||||
|
_state.contrast = s->status.contrast;
|
||||||
|
_state.saturation = s->status.saturation;
|
||||||
|
_state.sharpness = s->status.sharpness;
|
||||||
|
_state.denoise = s->status.denoise;
|
||||||
|
_state.gainceiling = (gainceiling_t)s->status.gainceiling;
|
||||||
|
_state.quality = s->status.quality;
|
||||||
|
_state.colorbar = s->status.colorbar;
|
||||||
|
_state.awb_gain = s->status.awb_gain;
|
||||||
|
_state.wb_mode = s->status.wb_mode;
|
||||||
|
_state.aec2 = s->status.aec2;
|
||||||
|
_state.ae_level = s->status.ae_level;
|
||||||
|
_state.aec_value = s->status.aec_value;
|
||||||
|
_state.agc_gain = s->status.agc_gain;
|
||||||
|
_state.bpc = s->status.bpc;
|
||||||
|
_state.wpc = s->status.wpc;
|
||||||
|
_state.special_effect = s->status.special_effect;
|
||||||
|
_state.raw_gma = s->status.raw_gma;
|
||||||
|
_state.lenc = s->status.lenc;
|
||||||
|
_state.hmirror = s->status.hmirror;
|
||||||
|
_state.vflip = s->status.vflip;
|
||||||
|
_state.dcw = s->status.dcw;
|
||||||
|
safe_sensor_return();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateCamera() {
|
void updateCamera() {
|
||||||
ESP_LOGI("CameraSettings", "Updating camera settings");
|
ESP_LOGI("CameraSettings", "Updating camera settings");
|
||||||
sensor_t *s = esp_camera_sensor_get();
|
sensor_t *s = safe_sensor_get();
|
||||||
|
if (!s) {
|
||||||
|
ESP_LOGE("CameraSettings", "Failed to update camera settings");
|
||||||
|
safe_sensor_return();
|
||||||
|
return;
|
||||||
|
}
|
||||||
s->set_pixformat(s, _state.pixformat);
|
s->set_pixformat(s, _state.pixformat);
|
||||||
s->set_framesize(s, _state.framesize);
|
s->set_framesize(s, _state.framesize);
|
||||||
s->set_brightness(s, _state.brightness);
|
s->set_brightness(s, _state.brightness);
|
||||||
@@ -152,14 +184,11 @@ class CameraSettingsService : public StatefulService<CameraSettings> {
|
|||||||
s->set_gainceiling(s, _state.gainceiling);
|
s->set_gainceiling(s, _state.gainceiling);
|
||||||
s->set_quality(s, _state.quality);
|
s->set_quality(s, _state.quality);
|
||||||
s->set_colorbar(s, _state.colorbar);
|
s->set_colorbar(s, _state.colorbar);
|
||||||
s->set_whitebal(s, _state.whitebal);
|
|
||||||
s->set_awb_gain(s, _state.awb_gain);
|
s->set_awb_gain(s, _state.awb_gain);
|
||||||
s->set_wb_mode(s, _state.wb_mode);
|
s->set_wb_mode(s, _state.wb_mode);
|
||||||
s->set_exposure_ctrl(s, _state.exposure_ctrl);
|
|
||||||
s->set_aec2(s, _state.aec2);
|
s->set_aec2(s, _state.aec2);
|
||||||
s->set_ae_level(s, _state.ae_level);
|
s->set_ae_level(s, _state.ae_level);
|
||||||
s->set_aec_value(s, _state.aec_value);
|
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_agc_gain(s, _state.agc_gain);
|
||||||
s->set_bpc(s, _state.bpc);
|
s->set_bpc(s, _state.bpc);
|
||||||
s->set_wpc(s, _state.wpc);
|
s->set_wpc(s, _state.wpc);
|
||||||
@@ -169,6 +198,7 @@ class CameraSettingsService : public StatefulService<CameraSettings> {
|
|||||||
s->set_hmirror(s, _state.hmirror);
|
s->set_hmirror(s, _state.hmirror);
|
||||||
s->set_vflip(s, _state.vflip);
|
s->set_vflip(s, _state.vflip);
|
||||||
s->set_dcw(s, _state.dcw);
|
s->set_dcw(s, _state.dcw);
|
||||||
|
safe_sensor_return();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Reference in New Issue
Block a user