A regression was added in commit 01154e949, which prepends
clutter to the status0 JSON status message.
Example:
```
$ curl 'http://webcam/cm?cmnd=Status0' -u admin:password ; echo
{s}Webcam Frame rate{m}0 FPS{e}{"Status":{"Module":0,"DeviceName":"...
```
The response is not properly formatted JSON and breaks client software.
The problem occurs because a new case statement was added for the
purpose of injecting webcam stats into the JSON status message, but
a break statement is missing and execution falls through to the
following case, which prepends garbage to the output buffer.
With this one-line fix in place, the output is properly formatted:
```
$ curl 'http://tahoe-front-door-cam/cm?cmnd=Status0' -u admin:password ; echo
{"Status":{"Module":0,"DeviceName":"
```
Note that the prior case for FUNC_EVERY_SECOND is also missing a break
statement. That looks wrong to me but it's unrelated to fixing this
issue and I'm inclined to punt that concern to others who are more
familiar with this code.
1604 lines
52 KiB
C++
1604 lines
52 KiB
C++
/*
|
|
xdrv_81_esp32_webcam.ino - ESP32 webcam support for Tasmota
|
|
|
|
Copyright (C) 2021 Gerhard Mutz and Theo Arends
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef ESP32
|
|
#ifdef USE_WEBCAM
|
|
// defining USE_WEBCAM_V2 will use xdrv_81_esp32_webcam_task.ino instead.
|
|
#ifndef USE_WEBCAM_V2
|
|
|
|
/*********************************************************************************************\
|
|
* ESP32 webcam based on example in Arduino-ESP32 library
|
|
*
|
|
* Template as used on ESP32-CAM WiFi + bluetooth Camera Module Development Board ESP32 With Camera Module OV2640 Geekcreit for Arduino
|
|
* {"NAME":"AITHINKER CAM","GPIO":[4992,1,672,1,416,5088,1,1,1,6720,736,704,1,1,5089,5090,0,5091,5184,5152,0,5120,5024,5056,0,0,0,0,4928,1,5094,5095,5092,0,0,5093],"FLAG":0,"BASE":2}
|
|
*
|
|
* Supported commands:
|
|
* WcInterrupt = Control streaming, 0 = stop, 1 = start
|
|
* WcResolution = Set resolution
|
|
**** Pre v14.4.0 ****
|
|
0 = FRAMESIZE_96X96, // 96x96
|
|
1 = FRAMESIZE_QQVGA, // 160x120
|
|
2 = FRAMESIZE_QCIF, // 176x144
|
|
3 = FRAMESIZE_HQVGA, // 240x176
|
|
4 = FRAMESIZE_240X240, // 240x240
|
|
5 = FRAMESIZE_QVGA, // 320x240
|
|
6 = FRAMESIZE_CIF, // 400x296
|
|
7 = FRAMESIZE_HVGA, // 480x320
|
|
8 = FRAMESIZE_VGA, // 640x480
|
|
9 = FRAMESIZE_SVGA, // 800x600
|
|
10 = FRAMESIZE_XGA, // 1024x768
|
|
11 = FRAMESIZE_HD, // 1280x720
|
|
12 = FRAMESIZE_SXGA, // 1280x1024
|
|
13 = FRAMESIZE_UXGA, // 1600x1200
|
|
// 3MP Sensors above this no yet supported with this driver
|
|
14 = FRAMESIZE_FHD, // 1920x1080
|
|
15 = FRAMESIZE_P_HD, // 720x1280
|
|
16 = FRAMESIZE_P_3MP, // 864x1536
|
|
17 = FRAMESIZE_QXGA, // 2048x1536
|
|
// 5MP Sensors
|
|
18 = FRAMESIZE_QHD, // 2560x1440
|
|
19 = FRAMESIZE_WQXGA, // 2560x1600
|
|
20 = FRAMESIZE_P_FHD, // 1080x1920
|
|
21 = FRAMESIZE_QSXGA, // 2560x1920
|
|
22 = FRAMESIZE_INVALID
|
|
|
|
**** Post v14.4.0 ****
|
|
0 = FRAMESIZE_96X96, // 96x96
|
|
1 = FRAMESIZE_QQVGA, // 160x120
|
|
2 = FRAMESIZE_128X128, // 128x128
|
|
3 = FRAMESIZE_QCIF, // 176x144
|
|
4 = FRAMESIZE_HQVGA, // 240x176
|
|
5 = FRAMESIZE_240X240, // 240x240
|
|
6 = FRAMESIZE_QVGA, // 320x240
|
|
7 = FRAMESIZE_320X320, // 320x320 (Known not to work. See https://github.com/espressif/arduino-esp32/pull/10814)
|
|
8 = FRAMESIZE_CIF, // 400x296
|
|
9 = FRAMESIZE_HVGA, // 480x320
|
|
10 = FRAMESIZE_VGA, // 640x480
|
|
11 = FRAMESIZE_SVGA, // 800x600
|
|
12 = FRAMESIZE_XGA, // 1024x768
|
|
13 = FRAMESIZE_HD, // 1280x720
|
|
14 = FRAMESIZE_SXGA, // 1280x1024
|
|
15 = FRAMESIZE_UXGA, // 1600x1200
|
|
// 3MP Sensors above this no yet supported with this driver
|
|
16 = FRAMESIZE_FHD, // 1920x1080
|
|
17 = FRAMESIZE_P_HD, // 720x1280
|
|
18 = FRAMESIZE_P_3MP, // 864x1536
|
|
19 = FRAMESIZE_QXGA, // 2048x1536
|
|
// 5MP Sensors
|
|
20 = FRAMESIZE_QHD, // 2560x1440
|
|
21 = FRAMESIZE_WQXGA, // 2560x1600
|
|
22 = FRAMESIZE_P_FHD, // 1080x1920
|
|
23 = FRAMESIZE_QSXGA, // 2560x1920
|
|
24 = FRAMESIZE_5MP, // 2592x1944
|
|
25 = FRAMESIZE_INVALID
|
|
|
|
* WcMirror = Mirror picture, 0 = no, 1 = yes
|
|
* WcFlip = Flip picture, 0 = no, 1 = yes
|
|
* WcSaturation = Set picture Saturation -2 ... +2
|
|
* WcBrightness = Set picture Brightness -2 ... +2
|
|
* WcContrast = Set picture Contrast -2 ... +2
|
|
* WcSpecialEffekt = Set Special Picture Effect: 0 = off, 1 = , 2 = , 3 = , 4 = , 5 = , 6 =
|
|
* WcAWB = Auto White Balance, 0 = no, 1 = yes
|
|
* WcWBMode = White Balance Mode, 0 = auto, 1 =
|
|
* WcAWBGain = Auto White Balance Gain, 0 = no, 1 = yes
|
|
* WcAEC = Auto exposure control (Sensor), 0 = no, 1 = yes
|
|
* WcAECDSP = Auto exposure control (DSP), 0 = no, 1 = yes
|
|
* WcAECValue = Auto exposure control value, 0 ... 1024
|
|
* WcAECLevel = Auto exposure control level, -2 ... +2
|
|
* WcAGC = Auto gain control, 0 = no, 1 = yes
|
|
* WcAGCGain = Auto gain control gain, 0 .. 30
|
|
* WcGainCeiling = Gain ceiling, 0 .. 6 (0 = x2, 1 = x4, 2 = x8, 3 = x16, 4 = x32, 5 = x64, 6 = x128)
|
|
* WcGammaCorrect = Auto Gamma Correct, 0 = no, 1 = yes
|
|
* WcLensCorrect = Auto Lens Correct, 0 = no, 1 = yes
|
|
* WcWPC = White Pixel Correct, 0 = no, 1 = yes
|
|
* WcDCW = Downscale, 0 = no, 1 = yes
|
|
* WcBPC = Black Pixel Correct, 0 = no, 1 = yes
|
|
* WcColorbar = Show Colorbar, 0 = no, 1 = yes
|
|
* WcFeature = Set extended Feature, 0 = off, 1 = reduce FPS, 2 = Nightmode
|
|
* WcStats = Show Statistics
|
|
* WcInit = Init Camera Interface
|
|
* WcRtsp = Control RTSP Server, 0=disable, 1=enable (forces restart) (if defined ENABLE_RTSPSERVER)
|
|
*
|
|
* WcFeature Explanation
|
|
* See https://hobbylad.files.wordpress.com/2020/02/ov2640-camera-module-software-application-notes.pdf
|
|
* for more Information
|
|
* 0: Normal Operation
|
|
* 1: Reduced FPS: Set XCLK Divisor to 2. Reduces Framerate and also increases exposure time. This causes
|
|
* better low light performance. See above document, page 6
|
|
* 2: Night Mode: Further increase exposure time and lower the Framerate depending on available light.
|
|
* See above Document, Page 8
|
|
*
|
|
* Only boards with PSRAM should be used.
|
|
* To speed up cam processing cpu frequency should be better set to 240Mhz
|
|
*
|
|
* remarks for AI-THINKER
|
|
* GPIO0 zero must be disconnected from any wire after programming because this pin drives the cam clock and does
|
|
* not tolerate any capictive load
|
|
* the AITHINKER module does not have CAM_RESET - so if you get the camera into a bad state, power off restart is the only way out.
|
|
* flash led = gpio 4
|
|
* red led = gpio 33
|
|
* optional rtsp url: rtsp://xxx.xxx.xxx.xxx:8554/mjpeg/1
|
|
*
|
|
* SH 2023-05-14 - added mutex for many webcam functions - this is to prevent multi-threaded access to the camera functions, which
|
|
* can case error 0x105 upon re-init.
|
|
* Errors 0x103 and 0xffffffff could indicate CAM_PWDN incorrect.
|
|
*
|
|
* I2C use: if USE_I2C is enabled, you can set GPIO26 to I2c_SDA/2 and GPIO27 to I2C_SCL/2, and then use the shared I2C bus 2.
|
|
* Then you can use cmd i2cscan2 to check for camera presence.
|
|
*/
|
|
|
|
/*********************************************************************************************/
|
|
|
|
#define XDRV_81 81
|
|
|
|
#include "cam_hal.h"
|
|
#include "esp_camera.h"
|
|
#include "sensor.h"
|
|
#include "fb_gfx.h"
|
|
#include "camera_pins.h"
|
|
|
|
SemaphoreHandle_t WebcamMutex = nullptr;
|
|
|
|
#ifndef USE_WEBCAM_SETUP_ONLY
|
|
bool HttpCheckPriviledgedAccess(bool);
|
|
extern ESP8266WebServer *Webserver;
|
|
|
|
// use mutex like:
|
|
// TasAutoMutex localmutex(&WebcamMutex, "somename");
|
|
// in any function. Will wait for mutex to be clear, and auto-release when the function exits.
|
|
|
|
#define BOUNDARY "e8b8c539-047d-4777-a985-fbba6edff11e"
|
|
|
|
#ifndef MAX_PICSTORE
|
|
#define MAX_PICSTORE 4
|
|
#endif
|
|
struct PICSTORE {
|
|
uint8_t *buff;
|
|
uint32_t len;
|
|
};
|
|
|
|
#ifdef ENABLE_RTSPSERVER
|
|
#include <OV2640.h>
|
|
#include <SimStreamer.h>
|
|
#include <OV2640Streamer.h>
|
|
#include <CRtspSession.h>
|
|
#ifndef RTSP_FRAME_TIME
|
|
#define RTSP_FRAME_TIME 100
|
|
#endif // RTSP_FRAME_TIME
|
|
#endif // ENABLE_RTSPSERVER
|
|
|
|
#endif //USE_WEBCAM_SETUP_ONLY
|
|
|
|
struct {
|
|
uint8_t up = 0;
|
|
uint16_t width;
|
|
uint16_t height;
|
|
uint8_t stream_active;
|
|
#ifndef USE_WEBCAM_SETUP_ONLY
|
|
WiFiClient client;
|
|
ESP8266WebServer *CamServer;
|
|
struct PICSTORE picstore[MAX_PICSTORE];
|
|
#ifdef ENABLE_RTSPSERVER
|
|
WiFiServer *rtspp;
|
|
CStreamer *rtsp_streamer;
|
|
CRtspSession *rtsp_session;
|
|
WiFiClient rtsp_client;
|
|
uint8_t rtsp_start;
|
|
OV2640 cam;
|
|
uint32_t rtsp_lastframe_time;
|
|
#endif // ENABLE_RTSPSERVER
|
|
#endif // USE_WEBCAM_SETUP_ONLY
|
|
} Wc;
|
|
|
|
struct {
|
|
uint32_t camcnt = 0;
|
|
uint32_t camfps = 0;
|
|
uint32_t camfail = 0;
|
|
uint32_t jpegfail = 0;
|
|
uint32_t clientfail = 0;
|
|
char name[7] = "Webcam";
|
|
} WcStats;
|
|
|
|
/*********************************************************************************************/
|
|
|
|
void WcInterrupt(uint32_t state) {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcInterrupt");
|
|
// Stop camera ISR if active to fix TG1WDT_SYS_RESET
|
|
if (!Wc.up) { return; }
|
|
|
|
if (state) {
|
|
// Re-enable interrupts
|
|
cam_start();
|
|
} else {
|
|
// Stop interrupts
|
|
cam_stop();
|
|
}
|
|
}
|
|
|
|
bool WcPinUsed(void) {
|
|
bool pin_used = true;
|
|
for (uint32_t i = 0; i < MAX_WEBCAM_DATA; i++) {
|
|
if (!PinUsed(GPIO_WEBCAM_DATA, i)) {
|
|
pin_used = false;
|
|
}
|
|
// if (i < MAX_WEBCAM_HSD) {
|
|
// if (!PinUsed(GPIO_WEBCAM_HSD, i)) {
|
|
// pin_used = false;
|
|
// }
|
|
// }
|
|
}
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: i2c_enabled_2: %d"), TasmotaGlobal.i2c_enabled[1]);
|
|
|
|
if (!PinUsed(GPIO_WEBCAM_XCLK) || !PinUsed(GPIO_WEBCAM_PCLK) ||
|
|
!PinUsed(GPIO_WEBCAM_VSYNC) || !PinUsed(GPIO_WEBCAM_HREF) ||
|
|
((!PinUsed(GPIO_WEBCAM_SIOD) || !PinUsed(GPIO_WEBCAM_SIOC)) && !TasmotaGlobal.i2c_enabled[1]) // preferred option is to reuse and share I2Cbus 2
|
|
) {
|
|
pin_used = false;
|
|
}
|
|
return pin_used;
|
|
}
|
|
|
|
|
|
void WcFeature(int32_t value) {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcFeature");
|
|
sensor_t * wc_s = esp_camera_sensor_get();
|
|
if (!wc_s) { return; }
|
|
|
|
if (value != 1) {
|
|
// CLKRC: Set Clock Divider to 0 = fullspeed
|
|
wc_s->set_reg(wc_s, 0x111, 0x3f, 0x00);
|
|
vTaskDelay(200 / portTICK_PERIOD_MS);
|
|
}
|
|
if (value != 2) {
|
|
// Stop Nightmode
|
|
wc_s->set_reg(wc_s, 0x103, 0xff, 0x0a); // COM1: Reset dummy frames
|
|
wc_s->set_reg(wc_s, 0x10f, 0xff, 0x43); // Reserved Reg
|
|
wc_s->set_reg(wc_s, 0x10f, 0xff, 0x4b); // Reserved Reg
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
wc_s->set_reg(wc_s, 0x10f, 0xff, 0x43); // Reserved Reg
|
|
}
|
|
|
|
switch(value) {
|
|
case 1:
|
|
// Reduce FPS
|
|
// CLKRC: Set Clock Divider to 2
|
|
wc_s->set_reg(wc_s, 0x111, 0x3f, 0x02);
|
|
break;
|
|
case 2:
|
|
// Start Nightmode
|
|
wc_s->set_reg(wc_s, 0x10f, 0xff, 0x4b); // Reserved Reg
|
|
wc_s->set_reg(wc_s, 0x103, 0xff, 0xcf); // COM1: Allow 7 dummy frames
|
|
break;
|
|
}
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Feature: %d"), value);
|
|
}
|
|
|
|
|
|
void WcApplySettings() {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcApplySettings");
|
|
sensor_t * wc_s = esp_camera_sensor_get();
|
|
if (!wc_s) { return; }
|
|
|
|
wc_s->set_vflip(wc_s, Settings->webcam_config.flip);
|
|
wc_s->set_hmirror(wc_s, Settings->webcam_config.mirror);
|
|
|
|
wc_s->set_brightness(wc_s, Settings->webcam_config.brightness - 2);
|
|
wc_s->set_saturation(wc_s, Settings->webcam_config.saturation - 2);
|
|
wc_s->set_contrast(wc_s, Settings->webcam_config.contrast - 2);
|
|
|
|
wc_s->set_special_effect(wc_s, Settings->webcam_config2.special_effect);
|
|
|
|
wc_s->set_whitebal(wc_s, Settings->webcam_config.awb);
|
|
wc_s->set_wb_mode(wc_s, Settings->webcam_config2.wb_mode);
|
|
wc_s->set_awb_gain(wc_s, Settings->webcam_config.awb_gain);
|
|
|
|
wc_s->set_exposure_ctrl(wc_s, Settings->webcam_config.aec);
|
|
wc_s->set_aec_value(wc_s, Settings->webcam_config2.aec_value - 2);
|
|
wc_s->set_ae_level(wc_s, Settings->webcam_config2.ae_level);
|
|
wc_s->set_aec2(wc_s, Settings->webcam_config.aec2);
|
|
|
|
wc_s->set_gain_ctrl(wc_s, Settings->webcam_config.agc);
|
|
wc_s->set_agc_gain(wc_s, Settings->webcam_config2.agc_gain);
|
|
wc_s->set_gainceiling(wc_s, (gainceiling_t)Settings->webcam_config2.gainceiling);
|
|
|
|
wc_s->set_raw_gma(wc_s, Settings->webcam_config.raw_gma);
|
|
wc_s->set_lenc(wc_s, Settings->webcam_config.lenc);
|
|
wc_s->set_wpc(wc_s, Settings->webcam_config.wpc);
|
|
wc_s->set_dcw(wc_s, Settings->webcam_config.dcw);
|
|
wc_s->set_bpc(wc_s, Settings->webcam_config.bpc);
|
|
|
|
WcFeature(Settings->webcam_config.feature);
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Settings updated"));
|
|
}
|
|
|
|
void WcSetDefaults(uint32_t upgrade) {
|
|
if (!upgrade) {
|
|
Settings->webcam_config.flip = 0;
|
|
Settings->webcam_config.mirror = 0;
|
|
|
|
Settings->webcam_config.saturation = 2; // = 0
|
|
Settings->webcam_config.brightness = 2; // = 0
|
|
Settings->webcam_config.contrast = 2; // = 0
|
|
}
|
|
|
|
Settings->webcam_config2.special_effect = 0;
|
|
Settings->webcam_config.colorbar = 0;
|
|
|
|
Settings->webcam_config.awb = 1; // white balance
|
|
Settings->webcam_config2.wb_mode = 0; // white balance mode
|
|
Settings->webcam_config.awb_gain = 1; // white blance gain
|
|
|
|
Settings->webcam_config.aec = 1; // autoexposure (sensor)
|
|
Settings->webcam_config.aec2 = 1; // autoexposure (dsp)
|
|
Settings->webcam_config2.ae_level = 2; // autoexposure level (-2 - +2, default 0)
|
|
Settings->webcam_config2.aec_value = 204; // manual exposure value
|
|
|
|
Settings->webcam_config.agc = 1; // auto gain control
|
|
Settings->webcam_config2.agc_gain = 5; // manual gain control
|
|
Settings->webcam_config2.gainceiling = 0; // auto gain ceiling
|
|
|
|
Settings->webcam_config.raw_gma = 1; // gamma correct
|
|
Settings->webcam_config.lenc = 1; // lens correction
|
|
Settings->webcam_config.wpc = 1; // white pixel correct
|
|
Settings->webcam_config.dcw = 1; // downsize en
|
|
Settings->webcam_config.bpc = 0; // black pixel correct?
|
|
|
|
Settings->webcam_config.feature = 0;
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Defaults set"));
|
|
|
|
if (Wc.up) { WcApplySettings(); }
|
|
}
|
|
|
|
uint32_t WcSetup(int32_t fsiz) {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcSetup");
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: WcSetup"));
|
|
if (fsiz >= FRAMESIZE_FHD) { fsiz = FRAMESIZE_FHD - 1; }
|
|
|
|
int stream_active = Wc.stream_active;
|
|
Wc.stream_active = 0;
|
|
|
|
if (fsiz < 0) {
|
|
if (Wc.up){
|
|
esp_camera_deinit();
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Deinit fsiz %d"), fsiz);
|
|
Wc.up = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (Wc.up) {
|
|
esp_camera_deinit();
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Deinit"));
|
|
//return Wc.up;
|
|
}
|
|
Wc.up = 0;
|
|
|
|
//esp_log_level_set("*", ESP_LOG_VERBOSE);
|
|
|
|
camera_config_t config;
|
|
|
|
memset(&config, 0, sizeof(config));
|
|
|
|
if (WcPinUsed()) {
|
|
config.pin_d0 = Pin(GPIO_WEBCAM_DATA); // Y2_GPIO_NUM;
|
|
config.pin_d1 = Pin(GPIO_WEBCAM_DATA, 1); // Y3_GPIO_NUM;
|
|
config.pin_d2 = Pin(GPIO_WEBCAM_DATA, 2); // Y4_GPIO_NUM;
|
|
config.pin_d3 = Pin(GPIO_WEBCAM_DATA, 3); // Y5_GPIO_NUM;
|
|
config.pin_d4 = Pin(GPIO_WEBCAM_DATA, 4); // Y6_GPIO_NUM;
|
|
config.pin_d5 = Pin(GPIO_WEBCAM_DATA, 5); // Y7_GPIO_NUM;
|
|
config.pin_d6 = Pin(GPIO_WEBCAM_DATA, 6); // Y8_GPIO_NUM;
|
|
config.pin_d7 = Pin(GPIO_WEBCAM_DATA, 7); // Y9_GPIO_NUM;
|
|
config.pin_xclk = Pin(GPIO_WEBCAM_XCLK); // XCLK_GPIO_NUM;
|
|
config.pin_pclk = Pin(GPIO_WEBCAM_PCLK); // PCLK_GPIO_NUM;
|
|
config.pin_vsync = Pin(GPIO_WEBCAM_VSYNC); // VSYNC_GPIO_NUM;
|
|
config.pin_href = Pin(GPIO_WEBCAM_HREF); // HREF_GPIO_NUM;
|
|
config.pin_sccb_sda = Pin(GPIO_WEBCAM_SIOD); // SIOD_GPIO_NUM; - unset to use shared I2C bus 2
|
|
config.pin_sccb_scl = Pin(GPIO_WEBCAM_SIOC); // SIOC_GPIO_NUM;
|
|
if(TasmotaGlobal.i2c_enabled[1]){ // configure SIOD and SIOC as SDA,2 and SCL,2
|
|
config.sccb_i2c_port = 1; // reuse initialized bus 2, can be shared now
|
|
if(config.pin_sccb_sda < 0){ // GPIO_WEBCAM_SIOD must not be set to really make it happen
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: Use I2C bus2"));
|
|
}
|
|
}
|
|
config.pin_pwdn = Pin(GPIO_WEBCAM_PWDN); // PWDN_GPIO_NUM;
|
|
config.pin_reset = Pin(GPIO_WEBCAM_RESET); // RESET_GPIO_NUM;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Template pin config"));
|
|
} else if (Y2_GPIO_NUM != -1) {
|
|
// Modell is set in camera_pins.h
|
|
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_sscb_sda = SIOD_GPIO_NUM;
|
|
config.pin_sscb_scl = SIOC_GPIO_NUM;
|
|
config.pin_pwdn = PWDN_GPIO_NUM;
|
|
config.pin_reset = RESET_GPIO_NUM;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Compile flag pin config"));
|
|
} else {
|
|
// no valid config found -> abort
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: No pin config"));
|
|
return 0;
|
|
}
|
|
|
|
int32_t ledc_channel = analogAttach(config.pin_xclk);
|
|
if (ledc_channel < 0) {
|
|
AddLog(LOG_LEVEL_ERROR, "CAM: cannot allocated ledc channel, remove a PWM GPIO");
|
|
}
|
|
config.ledc_channel = (ledc_channel_t) ledc_channel;
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, "CAM: XCLK on GPIO %i using ledc channel %i", config.pin_xclk, config.ledc_channel);
|
|
config.ledc_timer = LEDC_TIMER_0;
|
|
// config.xclk_freq_hz = 20000000;
|
|
config.xclk_freq_hz = Settings->webcam_clk * 1000000;
|
|
config.pixel_format = PIXFORMAT_JPEG;
|
|
|
|
//esp_log_level_set("*", ESP_LOG_INFO);
|
|
|
|
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
|
|
// for larger pre-allocated frame buffer.
|
|
|
|
bool psram = UsePSRAM();
|
|
if (psram) {
|
|
config.frame_size = FRAMESIZE_UXGA;
|
|
config.jpeg_quality = 10;
|
|
config.fb_count = 2;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: PSRAM found"));
|
|
} else {
|
|
config.frame_size = FRAMESIZE_VGA;
|
|
config.jpeg_quality = 12;
|
|
config.fb_count = 1;
|
|
config.fb_location = CAMERA_FB_IN_DRAM;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: PSRAM not found"));
|
|
}
|
|
|
|
esp_err_t err;
|
|
// cannot hurt to retry...
|
|
for (int i = 0; i < 3; i++){
|
|
err = esp_camera_init(&config);
|
|
|
|
if (err != ESP_OK) {
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: InitErr 0x%x try %d"), err, (i+1));
|
|
esp_camera_deinit();
|
|
} else {
|
|
if (i){
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: InitOK try %d"), (i+1));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (err != ESP_OK) {
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: InitErr 0x%x"), err);
|
|
return 0;
|
|
}
|
|
|
|
// AddLog(LOG_LEVEL_INFO, PSTR("CAM: heap check 2: %d"),ESP_getFreeHeap());
|
|
|
|
sensor_t * wc_s = esp_camera_sensor_get();
|
|
// drop down frame size for higher initial frame rate
|
|
wc_s->set_framesize(wc_s, (framesize_t)fsiz);
|
|
|
|
camera_fb_t *wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) {
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: Init failed to get the frame on time"));
|
|
return 0;
|
|
}
|
|
Wc.width = wc_fb->width;
|
|
Wc.height = wc_fb->height;
|
|
esp_camera_fb_return(wc_fb);
|
|
|
|
WcApplySettings();
|
|
|
|
camera_sensor_info_t *info = esp_camera_sensor_get_info(&wc_s->id);
|
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: %s Initialized"), info->name);
|
|
TasmotaGlobal.camera_initialized = true;
|
|
|
|
Wc.up = 1;
|
|
if (psram) { Wc.up = 2; }
|
|
|
|
// restore stream_active if we setup ok.
|
|
Wc.stream_active = stream_active;
|
|
|
|
return Wc.up;
|
|
}
|
|
|
|
/*********************************************************************************************/
|
|
#ifndef USE_WEBCAM_SETUP_ONLY
|
|
int32_t WcSetOptions(uint32_t sel, int32_t value) {
|
|
int32_t res = 0;
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcSetOptions");
|
|
|
|
sensor_t *s = esp_camera_sensor_get();
|
|
if (!s) { return -99; }
|
|
|
|
switch (sel) {
|
|
case 0:
|
|
if (value >= 0) { s->set_framesize(s, (framesize_t)value); }
|
|
res = s->status.framesize;
|
|
|
|
// WcFeature is lost on resolution change
|
|
WcApplySettings();
|
|
break;
|
|
case 1:
|
|
if (value >= 0) { s->set_special_effect(s, value); }
|
|
res = s->status.special_effect;
|
|
break;
|
|
case 2:
|
|
if (value >= 0) { s->set_vflip(s, value); }
|
|
res = s->status.vflip;
|
|
break;
|
|
case 3:
|
|
if (value >= 0) { s->set_hmirror(s, value); }
|
|
res = s->status.hmirror;
|
|
break;
|
|
case 4:
|
|
if (value >= -4) { s->set_contrast(s, value); }
|
|
res = s->status.contrast;
|
|
break;
|
|
case 5:
|
|
if (value >= -4) { s->set_brightness(s, value); }
|
|
res = s->status.brightness;
|
|
break;
|
|
case 6:
|
|
if (value >= -4) { s->set_saturation(s,value); }
|
|
res = s->status.saturation;
|
|
break;
|
|
case 7:
|
|
if (value >= 0) { s->set_whitebal(s, value); }
|
|
res = s->status.awb;
|
|
break;
|
|
case 8:
|
|
if (value >= 0) { s->set_wb_mode(s, value); }
|
|
res = s->status.wb_mode;
|
|
break;
|
|
case 9:
|
|
if (value >= 0) { s->set_awb_gain(s, value); }
|
|
res = s->status.awb_gain;
|
|
break;
|
|
case 10:
|
|
if (value >= 0) { s->set_exposure_ctrl(s, value); }
|
|
res = s->status.aec;
|
|
break;
|
|
case 11:
|
|
if (value >= 0) { s->set_aec_value(s, value); }
|
|
res = s->status.aec_value;
|
|
break;
|
|
case 12:
|
|
if (value >= 0) { s->set_ae_level(s, value); }
|
|
res = s->status.ae_level;
|
|
break;
|
|
case 13:
|
|
if (value >= 0) { s->set_aec2(s, value); }
|
|
res = s->status.aec2;
|
|
break;
|
|
case 14:
|
|
if (value >= 0) { s->set_gain_ctrl(s, value); }
|
|
res = s->status.agc;
|
|
break;
|
|
case 15:
|
|
if (value >= 0) { s->set_agc_gain(s, value); }
|
|
res = s->status.agc_gain;
|
|
break;
|
|
case 16:
|
|
if (value >= 0) { s->set_gainceiling(s, (gainceiling_t)value); }
|
|
res = s->status.gainceiling;
|
|
break;
|
|
case 17:
|
|
if (value >= 0) { s->set_raw_gma(s, value); }
|
|
res = s->status.raw_gma;
|
|
break;
|
|
case 18:
|
|
if (value >= 0) { s->set_lenc(s, value); }
|
|
res = s->status.lenc;
|
|
break;
|
|
case 19:
|
|
if (value >= 0) { s->set_wpc(s, value); }
|
|
res = s->status.wpc;
|
|
break;
|
|
case 20:
|
|
if (value >= 0) { s->set_dcw(s, value); }
|
|
res = s->status.dcw;
|
|
break;
|
|
case 21:
|
|
// blackpixelcontrol
|
|
if (value >= 0) { s->set_bpc(s, value); }
|
|
res = s->status.bpc;
|
|
break;
|
|
case 22:
|
|
if (value >= 0) { s->set_colorbar(s, value); }
|
|
res = s->status.colorbar;
|
|
break;
|
|
case 23:
|
|
if (value >= 0) { WcFeature(value); }
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
uint32_t WcGetWidth(void) {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcGetWidth");
|
|
|
|
camera_fb_t *wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) { return 0; }
|
|
Wc.width = wc_fb->width;
|
|
esp_camera_fb_return(wc_fb);
|
|
return Wc.width;
|
|
}
|
|
|
|
uint32_t WcGetHeight(void) {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcGetWidth");
|
|
camera_fb_t *wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) { return 0; }
|
|
Wc.height = wc_fb->height;
|
|
esp_camera_fb_return(wc_fb);
|
|
return Wc.height;
|
|
}
|
|
|
|
/*********************************************************************************************/
|
|
|
|
struct WC_Motion {
|
|
uint16_t motion_detect;
|
|
uint32_t motion_ltime;
|
|
uint32_t motion_trigger;
|
|
uint32_t motion_brightness;
|
|
uint8_t *last_motion_buffer;
|
|
} wc_motion;
|
|
|
|
|
|
uint32_t WcSetMotionDetect(int32_t value) {
|
|
if (value >= 0) { wc_motion.motion_detect = value; }
|
|
if (-1 == value) {
|
|
return wc_motion.motion_trigger;
|
|
} else {
|
|
return wc_motion.motion_brightness;
|
|
}
|
|
}
|
|
|
|
// optional motion detector
|
|
void WcDetectMotion(void) {
|
|
camera_fb_t *wc_fb;
|
|
uint8_t *out_buf = 0;
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcDetectMotion");
|
|
|
|
if ((millis()-wc_motion.motion_ltime) > wc_motion.motion_detect) {
|
|
wc_motion.motion_ltime = millis();
|
|
wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) { return; }
|
|
|
|
if (!wc_motion.last_motion_buffer) {
|
|
wc_motion.last_motion_buffer = (uint8_t *)heap_caps_malloc((wc_fb->width*wc_fb->height) + 4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
}
|
|
if (wc_motion.last_motion_buffer) {
|
|
if (PIXFORMAT_JPEG == wc_fb->format) {
|
|
out_buf = (uint8_t *)heap_caps_malloc((wc_fb->width*wc_fb->height*3)+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (out_buf) {
|
|
fmt2rgb888(wc_fb->buf, wc_fb->len, wc_fb->format, out_buf);
|
|
uint32_t x, y;
|
|
uint8_t *pxi = out_buf;
|
|
uint8_t *pxr = wc_motion.last_motion_buffer;
|
|
// convert to bw
|
|
uint64_t accu = 0;
|
|
uint64_t bright = 0;
|
|
for (y = 0; y < wc_fb->height; y++) {
|
|
for (x = 0; x < wc_fb->width; x++) {
|
|
int32_t gray = (pxi[0] + pxi[1] + pxi[2]) / 3;
|
|
int32_t lgray = pxr[0];
|
|
pxr[0] = gray;
|
|
pxi += 3;
|
|
pxr++;
|
|
accu += abs(gray - lgray);
|
|
bright += gray;
|
|
}
|
|
}
|
|
wc_motion.motion_trigger = accu / ((wc_fb->height * wc_fb->width) / 100);
|
|
wc_motion.motion_brightness = bright / ((wc_fb->height * wc_fb->width) / 100);
|
|
free(out_buf);
|
|
}
|
|
}
|
|
}
|
|
esp_camera_fb_return(wc_fb);
|
|
}
|
|
}
|
|
|
|
#ifdef COPYFRAME
|
|
struct PICSTORE tmp_picstore;
|
|
#endif
|
|
|
|
uint32_t WcGetPicstore(int32_t num, uint8_t **buff) {
|
|
if (num<0) { return MAX_PICSTORE; }
|
|
*buff = Wc.picstore[num].buff;
|
|
return Wc.picstore[num].len;
|
|
}
|
|
|
|
uint32_t WcGetFrame(int32_t bnum) {
|
|
size_t _jpg_buf_len = 0;
|
|
uint8_t * _jpg_buf = NULL;
|
|
camera_fb_t *wc_fb = 0;
|
|
bool jpeg_converted = false;
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcGetFrame");
|
|
|
|
if (bnum < 0) {
|
|
if (bnum < -MAX_PICSTORE) { bnum=-1; }
|
|
bnum = -bnum;
|
|
bnum--;
|
|
if (Wc.picstore[bnum].buff) { free(Wc.picstore[bnum].buff); }
|
|
Wc.picstore[bnum].len = 0;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef COPYFRAME
|
|
if (bnum & 0x10) {
|
|
bnum &= 0xf;
|
|
_jpg_buf = tmp_picstore.buff;
|
|
_jpg_buf_len = tmp_picstore.len;
|
|
if (!_jpg_buf_len) { return 0; }
|
|
goto pcopy;
|
|
}
|
|
#endif
|
|
|
|
wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Can't get frame"));
|
|
return 0;
|
|
}
|
|
if (!bnum) {
|
|
Wc.width = wc_fb->width;
|
|
Wc.height = wc_fb->height;
|
|
esp_camera_fb_return(wc_fb);
|
|
return 0;
|
|
}
|
|
|
|
if (wc_fb->format != PIXFORMAT_JPEG) {
|
|
jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
|
|
if (!jpeg_converted){
|
|
//Serial.println("JPEG compression failed");
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
} else {
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
|
|
pcopy:
|
|
if ((bnum < 1) || (bnum > MAX_PICSTORE)) { bnum = 1; }
|
|
bnum--;
|
|
if (Wc.picstore[bnum].buff) { free(Wc.picstore[bnum].buff); }
|
|
Wc.picstore[bnum].buff = (uint8_t *)heap_caps_malloc(_jpg_buf_len+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (Wc.picstore[bnum].buff) {
|
|
memcpy(Wc.picstore[bnum].buff, _jpg_buf, _jpg_buf_len);
|
|
Wc.picstore[bnum].len = _jpg_buf_len;
|
|
} else {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Can't allocate picstore"));
|
|
Wc.picstore[bnum].len = 0;
|
|
}
|
|
if (wc_fb) { esp_camera_fb_return(wc_fb); }
|
|
if (jpeg_converted) { free(_jpg_buf); }
|
|
if (!Wc.picstore[bnum].buff) { return 0; }
|
|
|
|
return _jpg_buf_len;
|
|
}
|
|
|
|
//////////////// Handle authentication /////////////////
|
|
|
|
bool WebcamAuthenticate(void)
|
|
{
|
|
if (strlen(SettingsText(SET_WEBPWD)) && (HTTP_MANAGER_RESET_ONLY != Web.state)) {
|
|
return Wc.CamServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD));
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool WebcamCheckPriviledgedAccess(bool autorequestauth = true)
|
|
{
|
|
|
|
if(Settings->webcam_config2.auth == 0){
|
|
return true;
|
|
}
|
|
|
|
if (autorequestauth && !WebcamAuthenticate()) {
|
|
Wc.CamServer->requestAuthentication();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
void HandleImage(void) {
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
uint32_t bnum = Webserver->arg(F("p")).toInt();
|
|
if ((bnum < 0) || (bnum > MAX_PICSTORE)) { bnum= 1; }
|
|
WiFiClient client = Webserver->client();
|
|
String response = "HTTP/1.1 200 OK\r\n";
|
|
response += "Content-disposition: inline; filename=cap.jpg\r\n";
|
|
response += "Content-type: image/jpeg\r\n\r\n";
|
|
Webserver->sendContent(response);
|
|
|
|
TasAutoMutex localmutex(&WebcamMutex, "HandleImage");
|
|
|
|
if (!bnum) {
|
|
size_t _jpg_buf_len = 0;
|
|
uint8_t * _jpg_buf = NULL;
|
|
camera_fb_t *wc_fb = 0;
|
|
wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) { return; }
|
|
if (Wc.stream_active < 2) {
|
|
// fetch some more frames
|
|
esp_camera_fb_return(wc_fb);
|
|
wc_fb = esp_camera_fb_get();
|
|
esp_camera_fb_return(wc_fb);
|
|
wc_fb = esp_camera_fb_get();
|
|
}
|
|
if (wc_fb->format != PIXFORMAT_JPEG) {
|
|
bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
|
|
if (!jpeg_converted) {
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
} else {
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
if (_jpg_buf_len) {
|
|
client.write((char *)_jpg_buf, _jpg_buf_len);
|
|
}
|
|
if (wc_fb) { esp_camera_fb_return(wc_fb); }
|
|
} else {
|
|
bnum--;
|
|
if (!Wc.picstore[bnum].len) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: No image #: %d"), bnum);
|
|
return;
|
|
}
|
|
client.write((char *)Wc.picstore[bnum].buff, Wc.picstore[bnum].len);
|
|
}
|
|
client.stop();
|
|
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("CAM: Sending image #: %d"), bnum+1);
|
|
}
|
|
|
|
void HandleImageBasic(void) {
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP "Capture image"));
|
|
|
|
if (Settings->webcam_config.stream) {
|
|
if (!Wc.CamServer) {
|
|
WcInterruptControl();
|
|
}
|
|
}
|
|
|
|
TasAutoMutex localmutex(&WebcamMutex, "HandleImage");
|
|
camera_fb_t *wc_fb;
|
|
wc_fb = esp_camera_fb_get(); // Acquire frame
|
|
if (!wc_fb) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Frame buffer could not be acquired"));
|
|
return;
|
|
}
|
|
|
|
size_t _jpg_buf_len = 0;
|
|
uint8_t * _jpg_buf = NULL;
|
|
if (wc_fb->format != PIXFORMAT_JPEG) {
|
|
bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
|
|
if (!jpeg_converted) {
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
} else {
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
|
|
if (_jpg_buf_len) {
|
|
Webserver->client().flush();
|
|
WSHeaderSend();
|
|
Webserver->sendHeader(F("Content-disposition"), F("inline; filename=snapshot.jpg"));
|
|
Webserver->send_P(200, "image/jpeg", (char *)_jpg_buf, _jpg_buf_len);
|
|
Webserver->client().stop();
|
|
}
|
|
|
|
esp_camera_fb_return(wc_fb); // Free frame buffer
|
|
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("CAM: Image sent"));
|
|
}
|
|
|
|
void HandleWebcamMjpeg(void) {
|
|
if(!WebcamCheckPriviledgedAccess()){
|
|
Wc.CamServer->send(403,"","");
|
|
return;
|
|
}
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Handle camserver"));
|
|
// if (!Wc.stream_active) {
|
|
// always restart stream
|
|
Wc.stream_active = 1;
|
|
Wc.client = Wc.CamServer->client();
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Create client"));
|
|
// }
|
|
}
|
|
|
|
void HandleWebcamMjpegTask(void) {
|
|
camera_fb_t *wc_fb;
|
|
size_t _jpg_buf_len = 0;
|
|
uint8_t * _jpg_buf = NULL;
|
|
|
|
//WiFiClient client = CamServer->client();
|
|
uint32_t tlen;
|
|
bool jpeg_converted = false;
|
|
|
|
if (!Wc.client.connected()) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Client fail"));
|
|
Wc.stream_active = 0;
|
|
WcStats.clientfail++;
|
|
}
|
|
if (1 == Wc.stream_active) {
|
|
Wc.client.flush();
|
|
Wc.client.setTimeout(3);
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Start stream"));
|
|
Wc.client.print("HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
|
|
"\r\n");
|
|
Wc.stream_active = 2;
|
|
}
|
|
|
|
TasAutoMutex localmutex(&WebcamMutex, "HandleWebcamMjpegTask");
|
|
|
|
if (2 == Wc.stream_active) {
|
|
wc_fb = esp_camera_fb_get();
|
|
if (!wc_fb) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Frame fail"));
|
|
Wc.stream_active = 0;
|
|
WcStats.camfail++;
|
|
}
|
|
WcStats.camcnt++;
|
|
}
|
|
if (2 == Wc.stream_active) {
|
|
if (wc_fb->format != PIXFORMAT_JPEG) {
|
|
jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
|
|
if (!jpeg_converted){
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: JPEG compression failed"));
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
WcStats.jpegfail++;
|
|
}
|
|
} else {
|
|
_jpg_buf_len = wc_fb->len;
|
|
_jpg_buf = wc_fb->buf;
|
|
}
|
|
|
|
Wc.client.print("--" BOUNDARY "\r\n");
|
|
Wc.client.printf("Content-Type: image/jpeg\r\n"
|
|
"Content-Length: %d\r\n"
|
|
"\r\n", static_cast<int>(_jpg_buf_len));
|
|
tlen = Wc.client.write(_jpg_buf, _jpg_buf_len);
|
|
/*
|
|
if (tlen!=_jpg_buf_len) {
|
|
esp_camera_fb_return(wc_fb);
|
|
Wc.stream_active=0;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Send fail"));
|
|
}*/
|
|
// Wc.client.print("\r\n--" BOUNDARY "\r\n");
|
|
Wc.client.print("\r\n");
|
|
|
|
#ifdef COPYFRAME
|
|
if (tmp_picstore.buff) { free(tmp_picstore.buff); }
|
|
tmp_picstore.buff = (uint8_t *)heap_caps_malloc(_jpg_buf_len+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (tmp_picstore.buff) {
|
|
memcpy(tmp_picstore.buff, _jpg_buf, _jpg_buf_len);
|
|
tmp_picstore.len = _jpg_buf_len;
|
|
} else {
|
|
tmp_picstore.len = 0;
|
|
}
|
|
#endif
|
|
|
|
if (jpeg_converted) { free(_jpg_buf); }
|
|
esp_camera_fb_return(wc_fb);
|
|
//AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: send frame"));
|
|
}
|
|
if (0 == Wc.stream_active) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Stream exit"));
|
|
Wc.client.flush();
|
|
Wc.client.stop();
|
|
}
|
|
}
|
|
|
|
void HandleWebcamRoot(void) {
|
|
if(!WebcamCheckPriviledgedAccess()){
|
|
Wc.CamServer->send(403,"","");
|
|
return;
|
|
}
|
|
//CamServer->redirect("http://" + String(ip) + ":81/cam.mjpeg");
|
|
Wc.CamServer->sendHeader("Location", "/cam.mjpeg");
|
|
Wc.CamServer->send(302, "", "");
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Root called"));
|
|
}
|
|
|
|
/*********************************************************************************************/
|
|
|
|
uint32_t WcSetStreamserver(uint32_t flag) {
|
|
if (TasmotaGlobal.global_state.network_down) {
|
|
Wc.stream_active = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (flag) {
|
|
if (!Wc.CamServer) {
|
|
Wc.stream_active = 0;
|
|
Wc.CamServer = new ESP8266WebServer(81);
|
|
Wc.CamServer->on("/", HandleWebcamRoot);
|
|
Wc.CamServer->on("/cam.mjpeg", HandleWebcamMjpeg);
|
|
Wc.CamServer->on("/cam.jpg", HandleWebcamMjpeg);
|
|
Wc.CamServer->on("/stream", HandleWebcamMjpeg);
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Stream init"));
|
|
Wc.CamServer->begin();
|
|
}
|
|
} else {
|
|
if (Wc.CamServer) {
|
|
Wc.stream_active = 0;
|
|
Wc.CamServer->stop();
|
|
delete Wc.CamServer;
|
|
Wc.CamServer = NULL;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Stream exit"));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void WcInterruptControl() {
|
|
TasAutoMutex localmutex(&WebcamMutex, "WcInterruptControl");
|
|
|
|
WcSetStreamserver(Settings->webcam_config.stream);
|
|
if(Wc.up == 0) {
|
|
WcSetup(Settings->webcam_config.resolution);
|
|
}
|
|
|
|
}
|
|
|
|
/*********************************************************************************************/
|
|
|
|
|
|
void WcLoop(void) {
|
|
// if (4 == Wc.stream_active) { return; }
|
|
|
|
if (Wc.CamServer) {
|
|
Wc.CamServer->handleClient();
|
|
if (Wc.stream_active) { HandleWebcamMjpegTask(); }
|
|
}
|
|
if (wc_motion.motion_detect) { WcDetectMotion(); }
|
|
|
|
#ifdef ENABLE_RTSPSERVER
|
|
if (Settings->webcam_config.rtsp && !TasmotaGlobal.global_state.wifi_down && Wc.up) {
|
|
if (!Wc.rtsp_start) {
|
|
Wc.rtspp = new WiFiServer(8554);
|
|
Wc.rtspp->begin();
|
|
Wc.rtsp_start = 1;
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: RTSP init"));
|
|
Wc.rtsp_lastframe_time = millis();
|
|
}
|
|
|
|
// If we have an active client connection, just service that until gone
|
|
if (Wc.rtsp_session) {
|
|
Wc.rtsp_session->handleRequests(0); // we don't use a timeout here,
|
|
// instead we send only if we have new enough frames
|
|
|
|
uint32_t now = millis();
|
|
if ((now-Wc.rtsp_lastframe_time) > RTSP_FRAME_TIME) {
|
|
Wc.rtsp_session->broadcastCurrentFrame(now);
|
|
Wc.rtsp_lastframe_time = now;
|
|
// AddLog(LOG_LEVEL_INFO, PSTR("CAM: RTSP session frame"));
|
|
}
|
|
|
|
if (Wc.rtsp_session->m_stopped) {
|
|
delete Wc.rtsp_session;
|
|
delete Wc.rtsp_streamer;
|
|
Wc.rtsp_session = NULL;
|
|
Wc.rtsp_streamer = NULL;
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: RTSP stopped"));
|
|
}
|
|
}
|
|
else {
|
|
Wc.rtsp_client = Wc.rtspp->accept();
|
|
if (Wc.rtsp_client) {
|
|
Wc.rtsp_streamer = new OV2640Streamer(&Wc.rtsp_client, Wc.cam); // our streamer for UDP/TCP based RTP transport
|
|
Wc.rtsp_session = new CRtspSession(&Wc.rtsp_client, Wc.rtsp_streamer); // our threads RTSP session and state
|
|
AddLog(LOG_LEVEL_INFO, PSTR("CAM: RTSP stream created"));
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_RTSPSERVER
|
|
}
|
|
|
|
void WcPicSetup(void) {
|
|
WebServer_on(PSTR("/wc.jpg"), HandleImage);
|
|
WebServer_on(PSTR("/wc.mjpeg"), HandleImage);
|
|
WebServer_on(PSTR("/snapshot.jpg"), HandleImage);
|
|
}
|
|
|
|
void WcShowStream(void) {
|
|
if (Settings->webcam_config.stream) {
|
|
// if (!Wc.CamServer || !Wc.up) {
|
|
if (!Wc.CamServer) {
|
|
WcInterruptControl();
|
|
}
|
|
if (Wc.CamServer && Wc.up!=0) {
|
|
// Give the webcam webserver some time to prepare the stream - catch error in JS
|
|
WSContentSend_P(PSTR("<p></p><center><img onerror='setTimeout(()=>{this.src=this.src;},1000)' src='http://%_I:81/stream' alt='Webcam stream' style='width:99%%;'></center><p></p>"),(uint32_t)WiFi.localIP());
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // USE_WEBCAM_SETUP_ONLY
|
|
|
|
void WcInit(void) {
|
|
if (!Settings->webcam_config.data) {
|
|
Settings->webcam_config.stream = 1;
|
|
Settings->webcam_config.resolution = FRAMESIZE_QVGA;
|
|
WcSetDefaults(0);
|
|
}
|
|
// previous webcam driver had only a small subset of possible config vars
|
|
// in this case we have to only set the new variables to default values
|
|
if(!Settings->webcam_config2.upgraded) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Upgrade settings"));
|
|
WcSetDefaults(1);
|
|
Settings->webcam_config2.upgraded = 1;
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Commands
|
|
\*********************************************************************************************/
|
|
#ifndef USE_WEBCAM_SETUP_ONLY
|
|
|
|
#define D_PRFX_WEBCAM "WC"
|
|
#define D_CMND_WC_STREAM "Stream"
|
|
#define D_CMND_WC_RESOLUTION "Resolution"
|
|
#define D_CMND_WC_MIRROR "Mirror"
|
|
#define D_CMND_WC_FLIP "Flip"
|
|
#define D_CMND_WC_SATURATION "Saturation"
|
|
#define D_CMND_WC_BRIGHTNESS "Brightness"
|
|
#define D_CMND_WC_CONTRAST "Contrast"
|
|
#define D_CMND_WC_SPECIALEFFECT "SpecialEffect"
|
|
|
|
#define D_CMND_WC_AWB "AWB"
|
|
#define D_CMND_WC_WB_MODE "WBMode"
|
|
#define D_CMND_WC_AWB_GAIN "AWBGain"
|
|
|
|
#define D_CMND_WC_AEC "AEC"
|
|
#define D_CMND_WC_AEC_VALUE "AECValue"
|
|
#define D_CMND_WC_AE_LEVEL "AELevel"
|
|
#define D_CMND_WC_AEC2 "AECDSP"
|
|
|
|
#define D_CMND_WC_AGC "AGC"
|
|
#define D_CMND_WC_AGC_GAIN "AGCGain"
|
|
#define D_CMND_WC_GAINCEILING "GainCeiling"
|
|
|
|
#define D_CMND_WC_RAW_GMA "GammaCorrect"
|
|
#define D_CMND_WC_LENC "LensCorrect"
|
|
|
|
#define D_CMND_WC_WPC "WPC"
|
|
#define D_CMND_WC_DCW "DCW"
|
|
#define D_CMND_WC_BPC "BPC"
|
|
|
|
#define D_CMND_WC_COLORBAR "Colorbar"
|
|
|
|
#define D_CMND_WC_FEATURE "Feature"
|
|
#define D_CMND_WC_SETDEFAULTS "SetDefaults"
|
|
#define D_CMND_WC_STATS "Stats"
|
|
|
|
#define D_CMND_WC_INIT "Init"
|
|
#define D_CMND_RTSP "Rtsp"
|
|
|
|
#define D_CMND_WC_AUTH "Auth"
|
|
#define D_CMND_WC_CLK "Clock"
|
|
|
|
const char kWCCommands[] PROGMEM = D_PRFX_WEBCAM "|" // Prefix
|
|
"|" D_CMND_WC_STREAM "|" D_CMND_WC_RESOLUTION "|" D_CMND_WC_MIRROR "|" D_CMND_WC_FLIP "|"
|
|
D_CMND_WC_SATURATION "|" D_CMND_WC_BRIGHTNESS "|" D_CMND_WC_CONTRAST "|" D_CMND_WC_SPECIALEFFECT "|"
|
|
D_CMND_WC_AWB "|" D_CMND_WC_WB_MODE "|" D_CMND_WC_AWB_GAIN "|" D_CMND_WC_AEC "|"
|
|
D_CMND_WC_AEC_VALUE "|" D_CMND_WC_AE_LEVEL "|" D_CMND_WC_AEC2 "|" D_CMND_WC_AGC "|"
|
|
D_CMND_WC_AGC_GAIN "|" D_CMND_WC_GAINCEILING "|" D_CMND_WC_RAW_GMA "|" D_CMND_WC_LENC "|"
|
|
D_CMND_WC_WPC "|" D_CMND_WC_DCW "|" D_CMND_WC_BPC "|" D_CMND_WC_COLORBAR "|" D_CMND_WC_FEATURE "|"
|
|
D_CMND_WC_SETDEFAULTS "|" D_CMND_WC_STATS "|" D_CMND_WC_INIT "|" D_CMND_WC_AUTH "|" D_CMND_WC_CLK
|
|
#ifdef ENABLE_RTSPSERVER
|
|
"|" D_CMND_RTSP
|
|
#endif // ENABLE_RTSPSERVER
|
|
;
|
|
|
|
void (* const WCCommand[])(void) PROGMEM = {
|
|
&CmndWebcam, &CmndWebcamStream, &CmndWebcamResolution, &CmndWebcamMirror, &CmndWebcamFlip,
|
|
&CmndWebcamSaturation, &CmndWebcamBrightness, &CmndWebcamContrast, &CmndWebcamSpecialEffect,
|
|
&CmndWebcamAWB, &CmndWebcamWBMode, &CmndWebcamAWBGain, &CmndWebcamAEC, &CmndWebcamAECValue,
|
|
&CmndWebcamAELevel, &CmndWebcamAEC2, &CmndWebcamAGC, &CmndWebcamAGCGain, &CmndWebcamGainCeiling,
|
|
&CmndWebcamGammaCorrect, &CmndWebcamLensCorrect, &CmndWebcamWPC, &CmndWebcamDCW, &CmndWebcamBPC,
|
|
&CmndWebcamColorbar, &CmndWebcamFeature, &CmndWebcamSetDefaults,
|
|
&CmndWebcamStats, &CmndWebcamInit, &CmndWebcamAuth, &CmndWebcamClock
|
|
#ifdef ENABLE_RTSPSERVER
|
|
, &CmndWebRtsp
|
|
#endif // ENABLE_RTSPSERVER
|
|
};
|
|
|
|
void CmndWebcam(void) {
|
|
Response_P(PSTR("{\"" D_PRFX_WEBCAM "\":{\"" D_CMND_WC_STREAM "\":%d,\"" D_CMND_WC_RESOLUTION "\":%d,\"" D_CMND_WC_MIRROR "\":%d,\""
|
|
D_CMND_WC_FLIP "\":%d,\""
|
|
D_CMND_WC_SATURATION "\":%d,\"" D_CMND_WC_BRIGHTNESS "\":%d,\"" D_CMND_WC_CONTRAST "\":%d,\""
|
|
D_CMND_WC_SPECIALEFFECT "\":%d,\"" D_CMND_WC_AWB "\":%d,\"" D_CMND_WC_WB_MODE "\":%d,\""
|
|
D_CMND_WC_AWB_GAIN "\":%d,\"" D_CMND_WC_AEC "\":%d,\"" D_CMND_WC_AEC_VALUE "\":%d,\""
|
|
D_CMND_WC_AE_LEVEL "\":%d,\"" D_CMND_WC_AEC2 "\":%d,\"" D_CMND_WC_AGC "\":%d,\""
|
|
D_CMND_WC_AGC_GAIN "\":%d,\"" D_CMND_WC_GAINCEILING "\":%d,\"" D_CMND_WC_RAW_GMA "\":%d,\""
|
|
D_CMND_WC_LENC "\":%d,\"" D_CMND_WC_WPC "\":%d,\"" D_CMND_WC_DCW "\":%d,\"" D_CMND_WC_BPC "\":%d,\""
|
|
D_CMND_WC_COLORBAR "\":%d,\"" D_CMND_WC_FEATURE "\":%d,\"" D_CMND_WC_AUTH "\":%d"
|
|
#ifdef ENABLE_RTSPSERVER
|
|
",\"" D_CMND_RTSP "\":%d"
|
|
#endif // ENABLE_RTSPSERVER
|
|
"}}"),
|
|
Settings->webcam_config.stream, Settings->webcam_config.resolution, Settings->webcam_config.mirror,
|
|
Settings->webcam_config.flip,
|
|
Settings->webcam_config.saturation -2, Settings->webcam_config.brightness -2, Settings->webcam_config.contrast -2,
|
|
Settings->webcam_config2.special_effect, Settings->webcam_config.awb, Settings->webcam_config2.wb_mode,
|
|
Settings->webcam_config.awb_gain, Settings->webcam_config.aec, Settings->webcam_config2.aec_value,
|
|
Settings->webcam_config2.ae_level -2, Settings->webcam_config.aec2, Settings->webcam_config.agc,
|
|
Settings->webcam_config2.agc_gain, Settings->webcam_config2.gainceiling, Settings->webcam_config.raw_gma,
|
|
Settings->webcam_config.lenc, Settings->webcam_config.wpc, Settings->webcam_config.dcw, Settings->webcam_config.bpc,
|
|
Settings->webcam_config.colorbar, Settings->webcam_config.feature, Settings->webcam_config2.auth
|
|
#ifdef ENABLE_RTSPSERVER
|
|
, Settings->webcam_config.rtsp
|
|
#endif // ENABLE_RTSPSERVER
|
|
);
|
|
}
|
|
|
|
void CmndWebcamStream(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.stream = XdrvMailbox.payload;
|
|
if (!Settings->webcam_config.stream) {
|
|
WcInterruptControl(); // Stop stream
|
|
} else {
|
|
WcSetStreamserver(Settings->webcam_config.stream); // Ensure server is running
|
|
}
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.stream);
|
|
}
|
|
|
|
void CmndWebcamResolution(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < FRAMESIZE_FHD)) {
|
|
Settings->webcam_config.resolution = XdrvMailbox.payload;
|
|
WcSetOptions(0, Settings->webcam_config.resolution);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config.resolution);
|
|
}
|
|
|
|
void CmndWebcamMirror(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.mirror = XdrvMailbox.payload;
|
|
WcSetOptions(3, Settings->webcam_config.mirror);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.mirror);
|
|
}
|
|
|
|
void CmndWebcamFlip(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.flip = XdrvMailbox.payload;
|
|
WcSetOptions(2, Settings->webcam_config.flip);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.flip);
|
|
}
|
|
|
|
void CmndWebcamSaturation(void) {
|
|
if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) {
|
|
Settings->webcam_config.saturation = XdrvMailbox.payload +2;
|
|
WcSetOptions(6, Settings->webcam_config.saturation -2);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config.saturation -2);
|
|
}
|
|
|
|
void CmndWebcamBrightness(void) {
|
|
if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) {
|
|
Settings->webcam_config.brightness = XdrvMailbox.payload +2;
|
|
WcSetOptions(5, Settings->webcam_config.brightness -2);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config.brightness -2);
|
|
}
|
|
|
|
void CmndWebcamContrast(void) {
|
|
if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) {
|
|
Settings->webcam_config.contrast = XdrvMailbox.payload +2;
|
|
WcSetOptions(4, Settings->webcam_config.contrast -2);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config.contrast -2);
|
|
}
|
|
|
|
void CmndWebcamSpecialEffect(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6)) {
|
|
Settings->webcam_config2.special_effect = XdrvMailbox.payload;
|
|
WcSetOptions(1, Settings->webcam_config2.special_effect);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.special_effect);
|
|
}
|
|
|
|
void CmndWebcamAWB(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.awb = XdrvMailbox.payload;
|
|
WcSetOptions(7, Settings->webcam_config.awb);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.awb);
|
|
}
|
|
|
|
void CmndWebcamWBMode(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) {
|
|
Settings->webcam_config2.wb_mode = XdrvMailbox.payload;
|
|
WcSetOptions(8, Settings->webcam_config2.wb_mode);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.wb_mode);
|
|
}
|
|
|
|
void CmndWebcamAWBGain(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.awb_gain = XdrvMailbox.payload;
|
|
WcSetOptions(9, Settings->webcam_config.awb_gain);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.awb_gain);
|
|
}
|
|
|
|
void CmndWebcamAEC(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.aec = XdrvMailbox.payload;
|
|
WcSetOptions(10, Settings->webcam_config.aec);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.aec);
|
|
}
|
|
|
|
void CmndWebcamAECValue(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1200)) {
|
|
Settings->webcam_config2.aec_value = XdrvMailbox.payload;
|
|
WcSetOptions(11, Settings->webcam_config2.aec_value);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.aec_value);
|
|
}
|
|
|
|
void CmndWebcamAELevel(void) {
|
|
if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) {
|
|
Settings->webcam_config2.ae_level = XdrvMailbox.payload + 2;
|
|
WcSetOptions(12, Settings->webcam_config2.ae_level - 2);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.ae_level - 2);
|
|
}
|
|
|
|
void CmndWebcamAEC2(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.aec2 = XdrvMailbox.payload;
|
|
WcSetOptions(13, Settings->webcam_config.aec2);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.aec2);
|
|
}
|
|
|
|
void CmndWebcamAGC(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.agc = XdrvMailbox.payload;
|
|
WcSetOptions(14, Settings->webcam_config.agc);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.agc);
|
|
}
|
|
|
|
void CmndWebcamAGCGain(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 30)) {
|
|
Settings->webcam_config2.agc_gain = XdrvMailbox.payload;
|
|
WcSetOptions(15, Settings->webcam_config2.agc_gain);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.agc_gain);
|
|
}
|
|
|
|
void CmndWebcamGainCeiling(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6)) {
|
|
Settings->webcam_config2.gainceiling = XdrvMailbox.payload;
|
|
WcSetOptions(16, Settings->webcam_config2.gainceiling);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.gainceiling);
|
|
}
|
|
|
|
void CmndWebcamGammaCorrect(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.raw_gma = XdrvMailbox.payload;
|
|
WcSetOptions(17, Settings->webcam_config.raw_gma);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.raw_gma);
|
|
}
|
|
|
|
void CmndWebcamLensCorrect(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.lenc = XdrvMailbox.payload;
|
|
WcSetOptions(18, Settings->webcam_config.lenc);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.lenc);
|
|
}
|
|
|
|
void CmndWebcamWPC(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.wpc = XdrvMailbox.payload;
|
|
WcSetOptions(19, Settings->webcam_config.wpc);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.wpc);
|
|
}
|
|
|
|
void CmndWebcamDCW(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.dcw = XdrvMailbox.payload;
|
|
WcSetOptions(20, Settings->webcam_config.dcw);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.dcw);
|
|
}
|
|
|
|
void CmndWebcamBPC(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.bpc = XdrvMailbox.payload;
|
|
WcSetOptions(21, Settings->webcam_config.bpc);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.bpc);
|
|
}
|
|
|
|
void CmndWebcamColorbar(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.colorbar = XdrvMailbox.payload;
|
|
WcSetOptions(22, Settings->webcam_config.colorbar);
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.colorbar);
|
|
}
|
|
|
|
void CmndWebcamFeature(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
Settings->webcam_config.feature = XdrvMailbox.payload;
|
|
WcSetOptions(23, Settings->webcam_config.feature);
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config.feature);
|
|
}
|
|
|
|
void CmndWebcamAuth(void){
|
|
if((XdrvMailbox.payload >=0) && (XdrvMailbox.payload <= 1)){
|
|
Settings->webcam_config2.auth = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_config2.auth);
|
|
}
|
|
|
|
void CmndWebcamClock(void){
|
|
if((XdrvMailbox.payload >= 10) && (XdrvMailbox.payload <= 200)){
|
|
Settings->webcam_clk = XdrvMailbox.payload;
|
|
WcInterruptControl();
|
|
}
|
|
ResponseCmndNumber(Settings->webcam_clk);
|
|
}
|
|
|
|
void CmndWebcamInit(void) {
|
|
WcSetup(Settings->webcam_config.resolution);
|
|
WcInterruptControl();
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndWebcamSetDefaults(void) {
|
|
WcSetDefaults(0);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
#define D_WEBCAM_STATS_FPS "FPS"
|
|
#define D_WEBCAM_STATS_CAMFAIL "CamFail"
|
|
#define D_WEBCAM_STATS_JPEGFAIL "JpegFail"
|
|
#define D_WEBCAM_STATS_CLIENTFAIL "ClientFail"
|
|
|
|
void CmndWebcamStats(void) {
|
|
Response_P(PSTR("{\"" D_PRFX_WEBCAM D_CMND_WC_STATS "\":{\"" D_WEBCAM_STATS_FPS "\":%d,\""
|
|
D_WEBCAM_STATS_CAMFAIL "\":%d,\"" D_WEBCAM_STATS_JPEGFAIL "\":%d,\"" D_WEBCAM_STATS_CLIENTFAIL "\":%d"
|
|
"}}"),
|
|
WcStats.camfps, WcStats.camfail, WcStats.jpegfail, WcStats.clientfail
|
|
);
|
|
}
|
|
|
|
#ifdef ENABLE_RTSPSERVER
|
|
void CmndWebRtsp(void) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->webcam_config.rtsp = XdrvMailbox.payload;
|
|
TasmotaGlobal.restart_flag = 2;
|
|
}
|
|
ResponseCmndStateText(Settings->webcam_config.rtsp);
|
|
}
|
|
#endif // ENABLE_RTSPSERVER
|
|
|
|
void WcUpdateStats(void) {
|
|
WcStats.camfps = WcStats.camcnt;
|
|
WcStats.camcnt = 0;
|
|
}
|
|
|
|
void WcSensorStats(void) {
|
|
if (!Wc.up) { return; }
|
|
|
|
ResponseAppend_P(PSTR(",\"CAMERA\":{"
|
|
"\"" D_WEBCAM_STATS_FPS "\":%d,"
|
|
"\"" D_WEBCAM_STATS_CAMFAIL "\":%d,"
|
|
"\"" D_WEBCAM_STATS_JPEGFAIL "\":%d,"
|
|
"\"" D_WEBCAM_STATS_CLIENTFAIL "\":%d}"),
|
|
WcStats.camfps, WcStats.camfail,
|
|
WcStats.jpegfail, WcStats.clientfail);
|
|
}
|
|
|
|
const char HTTP_WEBCAM_FPS[] PROGMEM = "{s}%s " D_FRAME_RATE "{m}%d " D_UNIT_FPS "{e}";
|
|
|
|
void WcStatsShow(void) {
|
|
#ifdef USE_WEBSERVER
|
|
WSContentSend_PD(HTTP_WEBCAM_FPS, WcStats.name, WcStats.camfps);
|
|
#endif // USE_WEBSERVER
|
|
}
|
|
|
|
#endif //USE_WEBCAM_SETUP_ONLY
|
|
|
|
/*********************************************************************************************\
|
|
* Interface
|
|
\*********************************************************************************************/
|
|
|
|
bool Xdrv81(uint32_t function) {
|
|
bool result = false;
|
|
#ifndef USE_WEBCAM_SETUP_ONLY
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
WcLoop();
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WcPicSetup();
|
|
break;
|
|
case FUNC_WEB_ADD_MAIN_BUTTON:
|
|
WcShowStream();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
WcUpdateStats();
|
|
case FUNC_JSON_APPEND:
|
|
WcSensorStats();
|
|
break;
|
|
case FUNC_WEB_SENSOR:
|
|
WcStatsShow();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kWCCommands, WCCommand);
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
WcInit();
|
|
break;
|
|
case FUNC_INIT:
|
|
if(Wc.up == 0) WcSetup(Settings->webcam_config.resolution);
|
|
break;
|
|
case FUNC_ACTIVE:
|
|
result = true;
|
|
break;
|
|
}
|
|
#endif // USE_WEBCAM_SETUP_ONLY
|
|
return result;
|
|
}
|
|
|
|
#endif // USE_WEBCAM_LEGACY
|
|
#endif // USE_WEBCAM
|
|
#endif // ESP32
|