Tasmota/tasmota/xdrv_39_webcam.ino

729 lines
20 KiB
Arduino
Raw Normal View History

2020-05-03 17:37:12 +01:00
/*
xdrv_39_webcam.ino - ESP32 webcam support for Tasmota
Copyright (C) 2020 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.
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
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
/*********************************************************************************************\
* 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 No SPI","GPIO":[4992,65504,65504,65504,5472,5312,65504,65504,5504,5536,65504,65504,5568,5440,5280,5248,0,5216,5408,5376,0,5344,5024,5056,0,0,0,0,4928,65504,5120,5088,5184,0,0,5152],"FLAG":0,"BASE":1}
* Template with SPI configured. This needs define USE_SPI
* {"NAME":"AITHINKER CAM","GPIO":[4992,65504,672,65504,5472,5312,65504,65504,5504,5536,736,704,5568,5440,5280,5248,0,5216,5408,5376,0,5344,5024,5056,0,0,0,0,4928,65504,5120,5088,5184,0,0,5152],"FLAG":0,"BASE":1}
*
* Command: Webcam <number>
* 0 = Stop streaming
* 1 = FRAMESIZE_QQVGA2 (128x160)
* 2 = FRAMESIZE_QCIF (176x144)
* 3 = FRAMESIZE_HQVGA (240x176)
* 4 = FRAMESIZE_QVGA (320x240)
* 5 = FRAMESIZE_CIF (400x296)
* 6 = FRAMESIZE_VGA (640x480)
* 7 = FRAMESIZE_SVGA (800x600)
* 8 = FRAMESIZE_XGA (1024x768)
* 9 = FRAMESIZE_SXGA (1280x1024)
* 10 = FRAMESIZE_UXGA (1600x1200)
2020-05-04 11:48:42 +01:00
*
* Only boards with PSRAM should be used. To enable PSRAM board should be se set to esp32cam in common32 of platform_override.ini
* board = esp32cam
* To speed up cam processing cpu frequency should be better set to 240Mhz in common32 of platform_override.ini
* board_build.f_cpu = 240000000L
2020-05-03 17:37:12 +01:00
\*********************************************************************************************/
2020-05-02 07:10:23 +01:00
#define XDRV_39 39
#define CAMERA_MODEL_AI_THINKER
//#define USE_TEMPLATE
#define WC_LOGLEVEL LOG_LEVEL_INFO
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#include "esp_camera.h"
#include "sensor.h"
uint8_t wc_up;
uint16_t wc_width;
uint16_t wc_height;
uint8_t wc_stream_active;
uint32_t wc_setup(int32_t fsiz) {
2020-05-03 17:37:12 +01:00
if (fsiz > 10) { fsiz = 10; }
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
wc_stream_active = 0;
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
if (fsiz < 0) {
2020-05-02 07:10:23 +01:00
esp_camera_deinit();
return 0;
}
if (wc_up) {
esp_camera_deinit();
//return wc_up;
}
//esp_log_level_set("*", ESP_LOG_VERBOSE);
2020-05-03 17:37:12 +01:00
camera_config_t config;
2020-05-02 07:10:23 +01:00
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
// config.pixel_format = PIXFORMAT_GRAYSCALE;
// config.pixel_format = PIXFORMAT_RGB565;
#ifndef USE_TEMPLATE
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;
2020-05-03 17:37:12 +01:00
#else
2020-05-04 11:48:42 +01:00
if (PinUsed(GPIO_WEBCAM_Y2) && PinUsed(GPIO_WEBCAM_Y3) && PinUsed(GPIO_WEBCAM_Y4) && PinUsed(GPIO_WEBCAM_Y5)\
&& PinUsed(GPIO_WEBCAM_Y6) && PinUsed(GPIO_WEBCAM_Y7) && PinUsed(GPIO_WEBCAM_Y8) && PinUsed(GPIO_WEBCAM_Y9)\
&& PinUsed(GPIO_WEBCAM_XCLK) && PinUsed(GPIO_WEBCAM_PCLK) && PinUsed(GPIO_WEBCAM_VSYNC) && PinUsed(GPIO_WEBCAM_HREF)\
&& PinUsed(GPIO_WEBCAM_SIOD) && PinUsed(GPIO_WEBCAM_SIOC)) {
config.pin_d0 = Pin(GPIO_WEBCAM_Y2); //Y2_GPIO_NUM;
config.pin_d1 = Pin(GPIO_WEBCAM_Y3); //Y3_GPIO_NUM;
config.pin_d2 = Pin(GPIO_WEBCAM_Y4); //Y4_GPIO_NUM;
config.pin_d3 = Pin(GPIO_WEBCAM_Y5); //Y5_GPIO_NUM;
config.pin_d4 = Pin(GPIO_WEBCAM_Y6); //Y6_GPIO_NUM;
config.pin_d5 = Pin(GPIO_WEBCAM_Y7); //Y7_GPIO_NUM;
config.pin_d6 = Pin(GPIO_WEBCAM_Y8); //Y8_GPIO_NUM;
config.pin_d7 = Pin(GPIO_WEBCAM_Y9); //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_sscb_sda = Pin(GPIO_WEBCAM_SIOD); //SIOD_GPIO_NUM;
config.pin_sscb_scl = Pin(GPIO_WEBCAM_SIOC); //SIOC_GPIO_NUM;
2020-05-03 17:37:12 +01:00
int16_t xpin;
2020-05-04 11:48:42 +01:00
xpin = Pin(GPIO_WEBCAM_PWDN);
2020-05-03 17:37:12 +01:00
if (99 == xpin) { xpin = -1; }
config.pin_pwdn = xpin; //PWDN_GPIO_NUM;
2020-05-04 11:48:42 +01:00
xpin = Pin(GPIO_WEBCAM_RESET);
2020-05-03 17:37:12 +01:00
if (99 == xpin) { xpin=-1; }
config.pin_reset = xpin; //RESET_GPIO_NUM;
} else {
// defaults to AI THINKER
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;
}
2020-05-02 07:10:23 +01:00
#endif
//ESP.getPsramSize()
//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.
2020-05-03 17:37:12 +01:00
bool psram = psramFound();
2020-05-02 07:10:23 +01:00
if (psram) {
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: PSRAM found"));
2020-05-02 07:10:23 +01:00
} else {
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 12;
config.fb_count = 1;
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: PSRAM not found"));
2020-05-02 07:10:23 +01:00
}
// stupid workaround camera diver eats up static ram should prefer PSRAM
// so we steal static ram to force driver to alloc PSRAM
//ESP.getMaxAllocHeap()
// void *x=malloc(70000);
2020-05-03 17:37:12 +01:00
void *x = 0;
2020-05-02 07:10:23 +01:00
esp_err_t err = esp_camera_init(&config);
2020-05-03 17:37:12 +01:00
if (x) { free(x); }
2020-05-02 07:10:23 +01:00
if (err != ESP_OK) {
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Init failed with error 0x%x"), err);
2020-05-02 07:10:23 +01:00
return 0;
}
sensor_t * wc_s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated
2020-05-03 17:37:12 +01:00
if (OV3660_PID == wc_s->id.PID) {
wc_s->set_vflip(wc_s, 1); // flip it back
wc_s->set_brightness(wc_s, 1); // up the brightness just a bit
wc_s->set_saturation(wc_s, -2); // lower the saturation
2020-05-02 07:10:23 +01:00
}
// 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();
2020-05-03 17:37:12 +01:00
wc_width = wc_fb->width;
wc_height = wc_fb->height;
2020-05-02 07:10:23 +01:00
esp_camera_fb_return(wc_fb);
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Initialized"));
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
wc_up = 1;
if (psram) { wc_up=2; }
2020-05-02 07:10:23 +01:00
return wc_up;
}
2020-05-03 17:37:12 +01:00
int32_t wc_set_options(uint32_t sel, int32_t value) {
int32_t res = 0;
2020-05-02 07:10:23 +01:00
sensor_t *s = esp_camera_sensor_get();
2020-05-03 17:37:12 +01:00
if (!s) { return -99; }
2020-05-02 07:10:23 +01:00
switch (sel) {
case 0:
2020-05-03 17:37:12 +01:00
if (value >= 0) { s->set_framesize(s, (framesize_t)value); }
2020-05-02 07:10:23 +01:00
res = s->status.framesize;
break;
case 1:
2020-05-03 17:37:12 +01:00
if (value >= 0) { s->set_special_effect(s, value); }
2020-05-02 07:10:23 +01:00
res = s->status.special_effect;
break;
case 2:
2020-05-03 17:37:12 +01:00
if (value >= 0) { s->set_vflip(s, value); }
2020-05-02 07:10:23 +01:00
res = s->status.vflip;
break;
case 3:
2020-05-03 17:37:12 +01:00
if (value >= 0) { s->set_hmirror(s, value); }
2020-05-02 07:10:23 +01:00
res = s->status.hmirror;
break;
case 4:
2020-05-03 17:37:12 +01:00
if (value >= -4) { s->set_contrast(s, value); }
2020-05-02 07:10:23 +01:00
res = s->status.contrast;
break;
case 5:
2020-05-03 17:37:12 +01:00
if (value >= -4) { s->set_brightness(s, value); }
2020-05-02 07:10:23 +01:00
res = s->status.brightness;
break;
case 6:
2020-05-03 17:37:12 +01:00
if (value >= -4) { s->set_saturation(s,value); }
2020-05-02 07:10:23 +01:00
res = s->status.saturation;
break;
}
return res;
}
uint32_t wc_get_width(void) {
camera_fb_t *wc_fb = esp_camera_fb_get();
2020-05-03 17:37:12 +01:00
if (!wc_fb) { return 0; }
wc_width = wc_fb->width;
2020-05-02 07:10:23 +01:00
esp_camera_fb_return(wc_fb);
return wc_width;
}
uint32_t wc_get_height(void) {
camera_fb_t *wc_fb = esp_camera_fb_get();
2020-05-03 17:37:12 +01:00
if (!wc_fb) { return 0; }
wc_height = wc_fb->height;
2020-05-02 07:10:23 +01:00
esp_camera_fb_return(wc_fb);
return wc_height;
}
#ifndef MAX_PICSTORE
#define MAX_PICSTORE 4
#endif
struct PICSTORE {
uint8_t *buff;
uint32_t len;
};
struct PICSTORE picstore[MAX_PICSTORE];
#ifdef COPYFRAME
struct PICSTORE tmp_picstore;
#endif
uint32_t get_picstore(int32_t num, uint8_t **buff) {
2020-05-03 17:37:12 +01:00
if (num<0) { return MAX_PICSTORE; }
*buff = picstore[num].buff;
2020-05-02 07:10:23 +01:00
return picstore[num].len;
}
uint32_t wc_get_jpeg(uint8_t **buff) {
2020-05-03 17:37:12 +01:00
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
camera_fb_t *wc_fb;
2020-05-02 07:10:23 +01:00
wc_fb = esp_camera_fb_get();
2020-05-03 17:37:12 +01:00
if (!wc_fb) { return 0; }
if (wc_fb->format != PIXFORMAT_JPEG) {
2020-05-02 07:10:23 +01:00
bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
2020-05-03 17:37:12 +01:00
if (!jpeg_converted) {
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
2020-05-02 07:10:23 +01:00
}
} else {
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
}
esp_camera_fb_return(wc_fb);
2020-05-03 17:37:12 +01:00
*buff = _jpg_buf;
2020-05-02 07:10:23 +01:00
return _jpg_buf_len;
}
uint32_t wc_get_frame(int32_t bnum) {
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
2020-05-03 17:37:12 +01:00
camera_fb_t *wc_fb = 0;
bool jpeg_converted = false;
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
if (bnum < 0) {
if (bnum < -MAX_PICSTORE) { bnum=-1; }
bnum = -bnum;
2020-05-02 07:10:23 +01:00
bnum--;
2020-05-03 17:37:12 +01:00
if (picstore[bnum].buff) { free(picstore[bnum].buff); }
picstore[bnum].len = 0;
2020-05-02 07:10:23 +01:00
return 0;
}
#ifdef COPYFRAME
2020-05-03 17:37:12 +01:00
if (bnum & 0x10) {
bnum &= 0xf;
_jpg_buf = tmp_picstore.buff;
_jpg_buf_len = tmp_picstore.len;
if (!_jpg_buf_len) { return 0; }
2020-05-02 07:10:23 +01:00
goto pcopy;
}
#endif
wc_fb = esp_camera_fb_get();
if (!wc_fb) {
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Can't get frame"));
2020-05-02 07:10:23 +01:00
return 0;
}
if (!bnum) {
wc_width = wc_fb->width;
wc_height = wc_fb->height;
esp_camera_fb_return(wc_fb);
return 0;
}
2020-05-03 17:37:12 +01:00
if (wc_fb->format != PIXFORMAT_JPEG) {
2020-05-02 07:10:23 +01:00
jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
if (!jpeg_converted){
2020-05-03 17:37:12 +01:00
//Serial.println("JPEG compression failed");
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
2020-05-02 07:10:23 +01:00
}
} else {
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
}
pcopy:
2020-05-03 17:37:12 +01:00
if ((bnum < 1) || (bnum > MAX_PICSTORE)) { bnum = 1; }
2020-05-02 07:10:23 +01:00
bnum--;
2020-05-03 17:37:12 +01:00
if (picstore[bnum].buff) { free(picstore[bnum].buff); }
picstore[bnum].buff = (uint8_t *)heap_caps_malloc(_jpg_buf_len+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
2020-05-02 07:10:23 +01:00
if (picstore[bnum].buff) {
2020-05-03 17:37:12 +01:00
memcpy(picstore[bnum].buff, _jpg_buf, _jpg_buf_len);
picstore[bnum].len = _jpg_buf_len;
2020-05-02 07:10:23 +01:00
} else {
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Can't allocate picstore"));
picstore[bnum].len = 0;
2020-05-02 07:10:23 +01:00
}
2020-05-03 17:37:12 +01:00
if (wc_fb) { esp_camera_fb_return(wc_fb); }
if (jpeg_converted) { free(_jpg_buf); }
if (!picstore[bnum].buff) { return 0; }
2020-05-02 07:10:23 +01:00
return _jpg_buf_len;
}
bool HttpCheckPriviledgedAccess(bool);
extern ESP8266WebServer *Webserver;
void HandleImage(void) {
if (!HttpCheckPriviledgedAccess(true)) { return; }
uint32_t bnum = Webserver->arg(F("p")).toInt();
2020-05-03 17:37:12 +01:00
if ((bnum < 0) || (bnum > MAX_PICSTORE)) { bnum= 1; }
2020-05-02 07:10:23 +01:00
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);
if (!bnum) {
uint8_t *buff;
uint32_t len;
2020-05-03 17:37:12 +01:00
len = wc_get_jpeg(&buff);
2020-05-02 07:10:23 +01:00
if (len) {
client.write(buff,len);
free(buff);
}
} else {
bnum--;
if (!picstore[bnum].len) {
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: No image #: %d"), bnum);
2020-05-02 07:10:23 +01:00
return;
}
client.write((char *)picstore[bnum].buff, picstore[bnum].len);
}
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Sending image #: %d"), bnum+1);
2020-05-02 07:10:23 +01:00
}
ESP8266WebServer *CamServer;
#define BOUNDARY "e8b8c539-047d-4777-a985-fbba6edff11e"
WiFiClient client;
void handleMjpeg(void) {
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Handle camserver"));
2020-05-02 07:10:23 +01:00
//if (!wc_stream_active) {
2020-05-03 17:37:12 +01:00
wc_stream_active = 1;
2020-05-02 07:10:23 +01:00
client = CamServer->client();
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Create client"));
2020-05-02 07:10:23 +01:00
//}
}
void handleMjpeg_task(void) {
camera_fb_t *wc_fb;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
//WiFiClient client = CamServer->client();
uint32_t tlen;
2020-05-03 17:37:12 +01:00
bool jpeg_converted = false;
2020-05-02 07:10:23 +01:00
if (!client.connected()) {
2020-05-03 17:37:12 +01:00
wc_stream_active = 0;
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Client fail"));
2020-05-02 07:10:23 +01:00
goto exit;
}
2020-05-03 17:37:12 +01:00
if (1 == wc_stream_active) {
2020-05-02 07:10:23 +01:00
client.flush();
client.setTimeout(3);
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Start stream"));
2020-05-02 07:10:23 +01:00
client.print("HTTP/1.1 200 OK\r\n"
2020-05-03 17:37:12 +01:00
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
"\r\n");
wc_stream_active = 2;
2020-05-02 07:10:23 +01:00
} else {
wc_fb = esp_camera_fb_get();
if (!wc_fb) {
2020-05-03 17:37:12 +01:00
wc_stream_active = 0;
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame fail"));
2020-05-02 07:10:23 +01:00
goto exit;
}
2020-05-03 17:37:12 +01:00
if (wc_fb->format != PIXFORMAT_JPEG) {
2020-05-02 07:10:23 +01:00
jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
if (!jpeg_converted){
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: JPEG compression failed"));
2020-05-02 07:10:23 +01:00
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
}
} else {
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
}
client.printf("Content-Type: image/jpeg\r\n"
"Content-Length: %d\r\n"
2020-05-03 17:37:12 +01:00
"\r\n", static_cast<int>(_jpg_buf_len));
tlen = client.write(_jpg_buf, _jpg_buf_len);
2020-05-02 07:10:23 +01:00
/*
if (tlen!=_jpg_buf_len) {
esp_camera_fb_return(wc_fb);
wc_stream_active=0;
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Send fail"));
2020-05-02 07:10:23 +01:00
}*/
client.print("\r\n--" BOUNDARY "\r\n");
#ifdef COPYFRAME
2020-05-03 17:37:12 +01:00
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);
2020-05-02 07:10:23 +01:00
if (tmp_picstore.buff) {
2020-05-03 17:37:12 +01:00
memcpy(tmp_picstore.buff, _jpg_buf, _jpg_buf_len);
tmp_picstore.len = _jpg_buf_len;
2020-05-02 07:10:23 +01:00
} else {
2020-05-03 17:37:12 +01:00
tmp_picstore.len = 0;
2020-05-02 07:10:23 +01:00
}
#endif
2020-05-03 17:37:12 +01:00
if (jpeg_converted) { free(_jpg_buf); }
2020-05-02 07:10:23 +01:00
esp_camera_fb_return(wc_fb);
2020-05-03 17:37:12 +01:00
//AddLog_P2(WC_LOGLEVEL, PSTR("CAM: send frame"));
2020-05-02 07:10:23 +01:00
exit:
if (!wc_stream_active) {
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit"));
2020-05-02 07:10:23 +01:00
client.flush();
client.stop();
}
}
}
void CamHandleRoot(void) {
//CamServer->redirect("http://" + String(ip) + ":81/cam.mjpeg");
CamServer->sendHeader("Location", WiFi.localIP().toString() + ":81/cam.mjpeg");
CamServer->send(302, "", "");
2020-05-04 05:54:24 +01:00
//Serial.printf("WC root called");
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: root called"));
2020-05-02 07:10:23 +01:00
}
uint16_t motion_detect;
uint32_t motion_ltime;
uint32_t motion_trigger;
uint32_t motion_brightness;
uint8_t *last_motion_buffer;
uint32_t wc_set_motion_detect(int32_t value) {
2020-05-03 17:37:12 +01:00
if (value >= 0) { motion_detect=value; }
if (-1 == value) {
2020-05-02 07:10:23 +01:00
return motion_trigger;
} else {
return motion_brightness;
}
}
// optional motion detector
void detect_motion(void) {
2020-05-03 17:37:12 +01:00
camera_fb_t *wc_fb;
uint8_t *out_buf=0;
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
if ((millis()-motion_ltime) > motion_detect) {
motion_ltime = millis();
2020-05-02 07:10:23 +01:00
wc_fb = esp_camera_fb_get();
2020-05-03 17:37:12 +01:00
if (!wc_fb) { return; }
2020-05-02 07:10:23 +01:00
if (!last_motion_buffer) {
2020-05-03 17:37:12 +01:00
last_motion_buffer=(uint8_t *)heap_caps_malloc((wc_fb->width*wc_fb->height)+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
2020-05-02 07:10:23 +01:00
}
if (last_motion_buffer) {
2020-05-03 17:37:12 +01:00
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);
2020-05-02 07:10:23 +01:00
if (out_buf) {
fmt2rgb888(wc_fb->buf, wc_fb->len, wc_fb->format, out_buf);
2020-05-03 17:37:12 +01:00
uint32_t x, y;
uint8_t *pxi = out_buf;
uint8_t *pxr = last_motion_buffer;
2020-05-02 07:10:23 +01:00
// convert to bw
2020-05-03 17:37:12 +01:00
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;
2020-05-02 07:10:23 +01:00
pxr++;
2020-05-03 17:37:12 +01:00
accu += abs(gray - lgray);
bright += gray;
2020-05-02 07:10:23 +01:00
}
}
2020-05-03 17:37:12 +01:00
motion_trigger = accu / ((wc_fb->height * wc_fb->width) / 100);
motion_brightness = bright / ((wc_fb->height * wc_fb->width) / 100);
2020-05-02 07:10:23 +01:00
free(out_buf);
}
}
}
esp_camera_fb_return(wc_fb);
}
}
void wc_show_stream(void) {
#ifndef USE_SCRIPT
2020-05-03 17:37:12 +01:00
if (CamServer) {
2020-05-04 16:13:14 +01:00
WSContentSend_P(PSTR("<p></p><center><img src='http://%s:81/stream' alt='Webcam stream' style='width:99%%;'></center><p></p>"),
2020-05-03 17:37:12 +01:00
WiFi.localIP().toString().c_str());
}
#endif
}
2020-05-02 07:10:23 +01:00
uint32_t wc_set_streamserver(uint32_t flag) {
2020-05-03 17:37:12 +01:00
if (global_state.wifi_down) { return 0; }
2020-05-02 07:10:23 +01:00
2020-05-03 17:37:12 +01:00
wc_stream_active = 0;
2020-05-02 07:10:23 +01:00
if (flag) {
if (!CamServer) {
CamServer = new ESP8266WebServer(81);
CamServer->on("/", CamHandleRoot);
CamServer->on("/cam.mjpeg", handleMjpeg);
CamServer->on("/cam.jpg", handleMjpeg);
CamServer->on("/stream", handleMjpeg);
2020-05-03 17:37:12 +01:00
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream init"));
2020-05-02 07:10:23 +01:00
CamServer->begin();
}
} else {
if (CamServer) {
CamServer->stop();
delete CamServer;
2020-05-03 17:37:12 +01:00
CamServer = NULL;
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit"));
2020-05-02 07:10:23 +01:00
}
}
return 0;
}
2020-05-04 16:13:14 +01:00
void WcStreamControl(uint32_t resolution) {
wc_set_streamserver(resolution);
wc_setup(resolution);
}
2020-05-02 07:10:23 +01:00
void wc_loop(void) {
2020-05-03 17:37:12 +01:00
if (CamServer) { CamServer->handleClient(); }
if (wc_stream_active) { handleMjpeg_task(); }
if (motion_detect) { detect_motion(); }
2020-05-02 07:10:23 +01:00
}
void wc_pic_setup(void) {
Webserver->on("/wc.jpg", HandleImage);
Webserver->on("/wc.mjpeg", HandleImage);
}
/*
typedef enum {
// FRAMESIZE_96x96, // 96x96
FRAMESIZE_QQVGA, // 160x120 0
FRAMESIZE_QQVGA2, // 128x160 1
FRAMESIZE_QCIF, // 176x144 2
FRAMESIZE_HQVGA, // 240x176 3
// FRAMESIZE_240x240, // 240x240 3
FRAMESIZE_QVGA, // 320x240 4
FRAMESIZE_CIF, // 400x296 5
FRAMESIZE_VGA, // 640x480 6
FRAMESIZE_SVGA, // 800x600 7
FRAMESIZE_XGA, // 1024x768 8
FRAMESIZE_SXGA, // 1280x1024 9
FRAMESIZE_UXGA, // 1600x1200 10
FRAMESIZE_QXGA, // 2048*1536
FRAMESIZE_INVALID
} framesize_t;
flash led = gpio4
red led = gpio 33
*/
2020-05-04 16:13:14 +01:00
void WcInit(void) {
if (Settings.esp32_webcam_resolution > 10) {
Settings.esp32_webcam_resolution = 0;
}
}
2020-05-02 07:10:23 +01:00
/*********************************************************************************************\
2020-05-03 17:37:12 +01:00
* Commands
2020-05-02 07:10:23 +01:00
\*********************************************************************************************/
2020-05-03 17:37:12 +01:00
2020-05-04 16:13:14 +01:00
#define D_CMND_WEBCAM "Webcam"
const char kWCCommands[] PROGMEM = "|" // no prefix
2020-05-04 16:13:14 +01:00
D_CMND_WEBCAM
;
void (* const WCCommand[])(void) PROGMEM = {
2020-05-04 16:13:14 +01:00
&CmndWebcam,
};
2020-05-04 16:13:14 +01:00
void CmndWebcam(void) {
2020-05-03 17:37:12 +01:00
uint32_t flag = 0;
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) {
2020-05-04 16:13:14 +01:00
Settings.esp32_webcam_resolution = XdrvMailbox.payload;
WcStreamControl(Settings.esp32_webcam_resolution);
}
2020-05-03 17:37:12 +01:00
if (CamServer) { flag = 1; }
2020-05-04 16:13:14 +01:00
Response_P(PSTR("{\"" D_CMND_WEBCAM "\":{\"Streaming\":\"%s\"}"),GetStateText(flag));
}
2020-05-03 17:37:12 +01:00
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
2020-05-02 07:10:23 +01:00
bool Xdrv39(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_LOOP:
wc_loop();
break;
case FUNC_WEB_ADD_HANDLER:
wc_pic_setup();
break;
case FUNC_WEB_ADD_MAIN_BUTTON:
2020-05-04 16:13:14 +01:00
WcStreamControl(Settings.esp32_webcam_resolution);
wc_show_stream();
break;
case FUNC_COMMAND:
result = DecodeCommand(kWCCommands, WCCommand);
break;
2020-05-04 16:13:14 +01:00
case FUNC_PRE_INIT:
WcInit();
break;
2020-05-02 07:10:23 +01:00
}
return result;
}
2020-05-03 17:37:12 +01:00
#endif // USE_WEBCAM
#endif // ESP32