Tasmotify ESP32 webcam

This commit is contained in:
Theo Arends 2020-05-03 18:37:12 +02:00
parent 5c744b573e
commit d410420110
3 changed files with 246 additions and 217 deletions

View File

@ -1441,14 +1441,9 @@ void GpioInit(void)
#ifdef ESP8266 #ifdef ESP8266
if ((2 == Pin(GPIO_TXD)) || (H801 == my_module_type)) { Serial.set_tx(2); } if ((2 == Pin(GPIO_TXD)) || (H801 == my_module_type)) { Serial.set_tx(2); }
#endif // ESP8266
#ifdef ESP8266
analogWriteRange(Settings.pwm_range); // Default is 1023 (Arduino.h) analogWriteRange(Settings.pwm_range); // Default is 1023 (Arduino.h)
analogWriteFreq(Settings.pwm_frequency); // Default is 1000 (core_esp8266_wiring_pwm.c) analogWriteFreq(Settings.pwm_frequency); // Default is 1000 (core_esp8266_wiring_pwm.c)
#else
analogWriteFreqRange(0,Settings.pwm_frequency,Settings.pwm_range);
#endif
#ifdef USE_SPI #ifdef USE_SPI
spi_flg = (((PinUsed(GPIO_SPI_CS) && (Pin(GPIO_SPI_CS) > 14)) || (Pin(GPIO_SPI_CS) < 12)) || ((PinUsed(GPIO_SPI_DC) && (Pin(GPIO_SPI_DC) > 14)) || (Pin(GPIO_SPI_DC) < 12))); spi_flg = (((PinUsed(GPIO_SPI_CS) && (Pin(GPIO_SPI_CS) > 14)) || (Pin(GPIO_SPI_CS) < 12)) || ((PinUsed(GPIO_SPI_DC) && (Pin(GPIO_SPI_DC) > 14)) || (Pin(GPIO_SPI_DC) < 12)));
@ -1462,6 +1457,14 @@ void GpioInit(void)
} }
soft_spi_flg = (PinUsed(GPIO_SSPI_CS) && PinUsed(GPIO_SSPI_SCLK) && (PinUsed(GPIO_SSPI_MOSI) || PinUsed(GPIO_SSPI_MISO))); soft_spi_flg = (PinUsed(GPIO_SSPI_CS) && PinUsed(GPIO_SSPI_SCLK) && (PinUsed(GPIO_SSPI_MOSI) || PinUsed(GPIO_SSPI_MISO)));
#endif // USE_SPI #endif // USE_SPI
#else // ESP32
analogWriteFreqRange(0, Settings.pwm_frequency, Settings.pwm_range);
#ifdef USE_SPI
spi_flg = (PinUsed(GPIO_SPI_CLK) && (PinUsed(GPIO_SPI_MOSI) || PinUsed(GPIO_SPI_MISO)));
soft_spi_flg = (PinUsed(GPIO_SSPI_SCLK) && (PinUsed(GPIO_SSPI_MOSI) || PinUsed(GPIO_SSPI_MISO)));
#endif // USE_SPI
#endif // ESP8266 - ESP32
// Set any non-used GPIO to INPUT - Related to resetPins() in support_legacy_cores.ino // Set any non-used GPIO to INPUT - Related to resetPins() in support_legacy_cores.ino
// Doing it here solves relay toggles at restart. // Doing it here solves relay toggles at restart.

View File

@ -197,8 +197,7 @@ enum UserSelectablePins {
ADC0_BUTTON_INV, ADC0_BUTTON_INV,
ADC0_RANGE, // Range ADC0_RANGE, // Range
ADC0_CT_POWER, // Current ADC0_CT_POWER, // Current
// webcam interface GPIO_WEBCAM_PWDN_GPIO_NUM, // Webcam interface
GPIO_WEBCAM_PWDN_GPIO_NUM,
GPIO_WEBCAM_RESET_GPIO_NUM, GPIO_WEBCAM_RESET_GPIO_NUM,
GPIO_WEBCAM_XCLK_GPIO_NUM, GPIO_WEBCAM_XCLK_GPIO_NUM,
GPIO_WEBCAM_SIOD_GPIO_NUM, GPIO_WEBCAM_SIOD_GPIO_NUM,

View File

@ -1,5 +1,45 @@
/*
xdrv_39_webcam.ino - ESP32 webcam support for Tasmota
#if defined(ESP32) && defined(USE_WEBCAM) 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.
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)
\*********************************************************************************************/
#define XDRV_39 39 #define XDRV_39 39
@ -38,9 +78,7 @@ uint16_t wc_height;
uint8_t wc_stream_active; uint8_t wc_stream_active;
uint32_t wc_setup(int32_t fsiz) { uint32_t wc_setup(int32_t fsiz) {
bool psram; if (fsiz > 10) { fsiz = 10; }
if (fsiz>10) fsiz=10;
wc_stream_active = 0; wc_stream_active = 0;
@ -65,7 +103,6 @@ camera_config_t config;
// config.pixel_format = PIXFORMAT_RGB565; // config.pixel_format = PIXFORMAT_RGB565;
#ifndef USE_TEMPLATE #ifndef USE_TEMPLATE
config.pin_d0 = Y2_GPIO_NUM; config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM;
@ -82,14 +119,11 @@ config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM;
#else #else
if (PinUsed(GPIO_WEBCAM_Y2_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y3_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y4_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y5_GPIO_NUM)\ if (PinUsed(GPIO_WEBCAM_Y2_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y3_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y4_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y5_GPIO_NUM)\
&& PinUsed(GPIO_WEBCAM_Y6_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y7_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y8_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y9_GPIO_NUM)\ && PinUsed(GPIO_WEBCAM_Y6_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y7_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y8_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y9_GPIO_NUM)\
&& PinUsed(GPIO_WEBCAM_XCLK_GPIO_NUM) && PinUsed(GPIO_WEBCAM_PCLK_GPIO_NUM) && PinUsed(GPIO_WEBCAM_VSYNC_GPIO_NUM) && PinUsed(GPIO_WEBCAM_HREF_GPIO_NUM)\ && PinUsed(GPIO_WEBCAM_XCLK_GPIO_NUM) && PinUsed(GPIO_WEBCAM_PCLK_GPIO_NUM) && PinUsed(GPIO_WEBCAM_VSYNC_GPIO_NUM) && PinUsed(GPIO_WEBCAM_HREF_GPIO_NUM)\
&& PinUsed(GPIO_WEBCAM_SIOD_GPIO_NUM) && PinUsed(GPIO_WEBCAM_SIOC_GPIO_NUM)) { && PinUsed(GPIO_WEBCAM_SIOD_GPIO_NUM) && PinUsed(GPIO_WEBCAM_SIOC_GPIO_NUM)) {
config.pin_d0 = Pin(GPIO_WEBCAM_Y2_GPIO_NUM); //Y2_GPIO_NUM; config.pin_d0 = Pin(GPIO_WEBCAM_Y2_GPIO_NUM); //Y2_GPIO_NUM;
config.pin_d1 = Pin(GPIO_WEBCAM_Y3_GPIO_NUM); //Y3_GPIO_NUM; config.pin_d1 = Pin(GPIO_WEBCAM_Y3_GPIO_NUM); //Y3_GPIO_NUM;
config.pin_d2 = Pin(GPIO_WEBCAM_Y4_GPIO_NUM); //Y4_GPIO_NUM; config.pin_d2 = Pin(GPIO_WEBCAM_Y4_GPIO_NUM); //Y4_GPIO_NUM;
@ -104,13 +138,12 @@ if (PinUsed(GPIO_WEBCAM_Y2_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y3_GPIO_NUM) && PinU
config.pin_href = Pin(GPIO_WEBCAM_HREF_GPIO_NUM); //HREF_GPIO_NUM; config.pin_href = Pin(GPIO_WEBCAM_HREF_GPIO_NUM); //HREF_GPIO_NUM;
config.pin_sscb_sda = Pin(GPIO_WEBCAM_SIOD_GPIO_NUM); //SIOD_GPIO_NUM; config.pin_sscb_sda = Pin(GPIO_WEBCAM_SIOD_GPIO_NUM); //SIOD_GPIO_NUM;
config.pin_sscb_scl = Pin(GPIO_WEBCAM_SIOC_GPIO_NUM); //SIOC_GPIO_NUM; config.pin_sscb_scl = Pin(GPIO_WEBCAM_SIOC_GPIO_NUM); //SIOC_GPIO_NUM;
int16_t xpin; int16_t xpin;
xpin = Pin(GPIO_WEBCAM_PWDN_GPIO_NUM); xpin = Pin(GPIO_WEBCAM_PWDN_GPIO_NUM);
if (xpin==99) xpin=-1; if (99 == xpin) { xpin = -1; }
config.pin_pwdn = xpin; //PWDN_GPIO_NUM; config.pin_pwdn = xpin; //PWDN_GPIO_NUM;
xpin = Pin(GPIO_WEBCAM_RESET_GPIO_NUM); xpin = Pin(GPIO_WEBCAM_RESET_GPIO_NUM);
if (xpin==99) xpin=-1; if (99 == xpin) { xpin=-1; }
config.pin_reset = xpin; //RESET_GPIO_NUM; config.pin_reset = xpin; //RESET_GPIO_NUM;
} else { } else {
// defaults to AI THINKER // defaults to AI THINKER
@ -131,8 +164,6 @@ if (PinUsed(GPIO_WEBCAM_Y2_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y3_GPIO_NUM) && PinU
config.pin_pwdn = PWDN_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM;
} }
#endif #endif
//ESP.getPsramSize() //ESP.getPsramSize()
@ -143,17 +174,17 @@ if (PinUsed(GPIO_WEBCAM_Y2_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y3_GPIO_NUM) && PinU
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer. // for larger pre-allocated frame buffer.
psram=psramFound(); bool psram = psramFound();
if (psram) { if (psram) {
config.frame_size = FRAMESIZE_UXGA; config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10; config.jpeg_quality = 10;
config.fb_count = 2; config.fb_count = 2;
AddLog_P(WC_LOGLEVEL,"PSRAM found!"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: PSRAM found"));
} else { } else {
config.frame_size = FRAMESIZE_VGA; config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 12; config.jpeg_quality = 12;
config.fb_count = 1; config.fb_count = 1;
AddLog_P(WC_LOGLEVEL,"PSRAM not found!"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: PSRAM not found"));
} }
// stupid workaround camera diver eats up static ram should prefer PSRAM // stupid workaround camera diver eats up static ram should prefer PSRAM
@ -162,19 +193,17 @@ if (PinUsed(GPIO_WEBCAM_Y2_GPIO_NUM) && PinUsed(GPIO_WEBCAM_Y3_GPIO_NUM) && PinU
// void *x=malloc(70000); // void *x=malloc(70000);
void *x = 0; void *x = 0;
esp_err_t err = esp_camera_init(&config); esp_err_t err = esp_camera_init(&config);
if (x) { free(x); }
if (x) free(x);
if (err != ESP_OK) { if (err != ESP_OK) {
AddLog_P2(WC_LOGLEVEL,"Camera init failed with error 0x%x", err); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Init failed with error 0x%x"), err);
return 0; return 0;
} }
sensor_t * wc_s = esp_camera_sensor_get(); sensor_t * wc_s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated // initial sensors are flipped vertically and colors are a bit saturated
if (wc_s->id.PID == OV3660_PID) { if (OV3660_PID == wc_s->id.PID) {
wc_s->set_vflip(wc_s, 1); // flip it back 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_brightness(wc_s, 1); // up the brightness just a bit
wc_s->set_saturation(wc_s, -2); // lower the saturation wc_s->set_saturation(wc_s, -2); // lower the saturation
@ -187,49 +216,46 @@ void *x=0;
wc_height = wc_fb->height; wc_height = wc_fb->height;
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
AddLog_P(WC_LOGLEVEL,"Camera successfully initialized!"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Initialized"));
wc_up = 1; wc_up = 1;
if (psram) { wc_up=2; }
if (psram) {
wc_up=2;
}
return wc_up; return wc_up;
} }
int32_t wc_set_options(uint32_t sel, int32_t value) { int32_t wc_set_options(uint32_t sel, int32_t value) {
int32_t res = 0; int32_t res = 0;
sensor_t *s = esp_camera_sensor_get(); sensor_t *s = esp_camera_sensor_get();
if (!s) return -99; if (!s) { return -99; }
switch (sel) { switch (sel) {
case 0: case 0:
if (value>=0) s->set_framesize(s,(framesize_t)value); if (value >= 0) { s->set_framesize(s, (framesize_t)value); }
res = s->status.framesize; res = s->status.framesize;
break; break;
case 1: case 1:
if (value>=0) s->set_special_effect(s,value); if (value >= 0) { s->set_special_effect(s, value); }
res = s->status.special_effect; res = s->status.special_effect;
break; break;
case 2: case 2:
if (value>=0) s->set_vflip(s,value); if (value >= 0) { s->set_vflip(s, value); }
res = s->status.vflip; res = s->status.vflip;
break; break;
case 3: case 3:
if (value>=0) s->set_hmirror(s,value); if (value >= 0) { s->set_hmirror(s, value); }
res = s->status.hmirror; res = s->status.hmirror;
break; break;
case 4: case 4:
if (value>=-4) s->set_contrast(s,value); if (value >= -4) { s->set_contrast(s, value); }
res = s->status.contrast; res = s->status.contrast;
break; break;
case 5: case 5:
if (value>=-4) s->set_brightness(s,value); if (value >= -4) { s->set_brightness(s, value); }
res = s->status.brightness; res = s->status.brightness;
break; break;
case 6: case 6:
if (value>=-4) s->set_saturation(s,value); if (value >= -4) { s->set_saturation(s,value); }
res = s->status.saturation; res = s->status.saturation;
break; break;
} }
@ -239,7 +265,7 @@ int32_t wc_set_options(uint32_t sel,int32_t value) {
uint32_t wc_get_width(void) { uint32_t wc_get_width(void) {
camera_fb_t *wc_fb = esp_camera_fb_get(); camera_fb_t *wc_fb = esp_camera_fb_get();
if (!wc_fb) return 0; if (!wc_fb) { return 0; }
wc_width = wc_fb->width; wc_width = wc_fb->width;
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
return wc_width; return wc_width;
@ -247,7 +273,7 @@ uint32_t wc_get_width(void) {
uint32_t wc_get_height(void) { uint32_t wc_get_height(void) {
camera_fb_t *wc_fb = esp_camera_fb_get(); camera_fb_t *wc_fb = esp_camera_fb_get();
if (!wc_fb) return 0; if (!wc_fb) { return 0; }
wc_height = wc_fb->height; wc_height = wc_fb->height;
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
return wc_height; return wc_height;
@ -268,7 +294,7 @@ struct PICSTORE tmp_picstore;
#endif #endif
uint32_t get_picstore(int32_t num, uint8_t **buff) { uint32_t get_picstore(int32_t num, uint8_t **buff) {
if (num<0) return MAX_PICSTORE; if (num<0) { return MAX_PICSTORE; }
*buff = picstore[num].buff; *buff = picstore[num].buff;
return picstore[num].len; return picstore[num].len;
} }
@ -278,7 +304,7 @@ size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL; uint8_t * _jpg_buf = NULL;
camera_fb_t *wc_fb; camera_fb_t *wc_fb;
wc_fb = esp_camera_fb_get(); wc_fb = esp_camera_fb_get();
if (!wc_fb) return 0; if (!wc_fb) { return 0; }
if (wc_fb->format != PIXFORMAT_JPEG) { if (wc_fb->format != PIXFORMAT_JPEG) {
bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len); bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
if (!jpeg_converted) { if (!jpeg_converted) {
@ -294,7 +320,6 @@ camera_fb_t *wc_fb;
return _jpg_buf_len; return _jpg_buf_len;
} }
uint32_t wc_get_frame(int32_t bnum) { uint32_t wc_get_frame(int32_t bnum) {
size_t _jpg_buf_len = 0; size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL; uint8_t * _jpg_buf = NULL;
@ -302,10 +327,10 @@ uint32_t wc_get_frame(int32_t bnum) {
bool jpeg_converted = false; bool jpeg_converted = false;
if (bnum < 0) { if (bnum < 0) {
if (bnum<-MAX_PICSTORE) bnum=-1; if (bnum < -MAX_PICSTORE) { bnum=-1; }
bnum = -bnum; bnum = -bnum;
bnum--; bnum--;
if (picstore[bnum].buff) free(picstore[bnum].buff); if (picstore[bnum].buff) { free(picstore[bnum].buff); }
picstore[bnum].len = 0; picstore[bnum].len = 0;
return 0; return 0;
} }
@ -315,14 +340,14 @@ uint32_t wc_get_frame(int32_t bnum) {
bnum &= 0xf; bnum &= 0xf;
_jpg_buf = tmp_picstore.buff; _jpg_buf = tmp_picstore.buff;
_jpg_buf_len = tmp_picstore.len; _jpg_buf_len = tmp_picstore.len;
if (!_jpg_buf_len) return 0; if (!_jpg_buf_len) { return 0; }
goto pcopy; goto pcopy;
} }
#endif #endif
wc_fb = esp_camera_fb_get(); wc_fb = esp_camera_fb_get();
if (!wc_fb) { if (!wc_fb) {
AddLog_P(WC_LOGLEVEL, "cant get frame"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Can't get frame"));
return 0; return 0;
} }
if (!bnum) { if (!bnum) {
@ -345,20 +370,21 @@ uint32_t wc_get_frame(int32_t bnum) {
} }
pcopy: pcopy:
if (bnum<1 || bnum>MAX_PICSTORE) bnum=1; if ((bnum < 1) || (bnum > MAX_PICSTORE)) { bnum = 1; }
bnum--; bnum--;
if (picstore[bnum].buff) free(picstore[bnum].buff); 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); picstore[bnum].buff = (uint8_t *)heap_caps_malloc(_jpg_buf_len+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (picstore[bnum].buff) { if (picstore[bnum].buff) {
memcpy(picstore[bnum].buff, _jpg_buf, _jpg_buf_len); memcpy(picstore[bnum].buff, _jpg_buf, _jpg_buf_len);
picstore[bnum].len = _jpg_buf_len; picstore[bnum].len = _jpg_buf_len;
} else { } else {
AddLog_P(WC_LOGLEVEL, "cant allocate picstore"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Can't allocate picstore"));
picstore[bnum].len = 0; picstore[bnum].len = 0;
} }
if (wc_fb) esp_camera_fb_return(wc_fb); if (wc_fb) { esp_camera_fb_return(wc_fb); }
if (jpeg_converted) free(_jpg_buf); if (jpeg_converted) { free(_jpg_buf); }
if (!picstore[bnum].buff) return 0; if (!picstore[bnum].buff) { return 0; }
return _jpg_buf_len; return _jpg_buf_len;
} }
@ -369,7 +395,7 @@ void HandleImage(void) {
if (!HttpCheckPriviledgedAccess(true)) { return; } if (!HttpCheckPriviledgedAccess(true)) { return; }
uint32_t bnum = Webserver->arg(F("p")).toInt(); uint32_t bnum = Webserver->arg(F("p")).toInt();
if (bnum<0 || bnum>MAX_PICSTORE) bnum=1; if ((bnum < 0) || (bnum > MAX_PICSTORE)) { bnum= 1; }
WiFiClient client = Webserver->client(); WiFiClient client = Webserver->client();
String response = "HTTP/1.1 200 OK\r\n"; String response = "HTTP/1.1 200 OK\r\n";
response += "Content-disposition: inline; filename=cap.jpg\r\n"; response += "Content-disposition: inline; filename=cap.jpg\r\n";
@ -387,14 +413,13 @@ void HandleImage(void) {
} else { } else {
bnum--; bnum--;
if (!picstore[bnum].len) { if (!picstore[bnum].len) {
AddLog_P2(WC_LOGLEVEL, PSTR("no image #: %d"), bnum); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: No image #: %d"), bnum);
return; return;
} }
client.write((char *)picstore[bnum].buff, picstore[bnum].len); client.write((char *)picstore[bnum].buff, picstore[bnum].len);
} }
AddLog_P2(WC_LOGLEVEL, PSTR("sending image #: %d"), bnum+1); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Sending image #: %d"), bnum+1);
} }
ESP8266WebServer *CamServer; ESP8266WebServer *CamServer;
@ -403,17 +428,14 @@ ESP8266WebServer *CamServer;
WiFiClient client; WiFiClient client;
void handleMjpeg(void) { void handleMjpeg(void) {
AddLog_P(WC_LOGLEVEL, "handle camserver"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Handle camserver"));
//if (!wc_stream_active) { //if (!wc_stream_active) {
wc_stream_active = 1; wc_stream_active = 1;
client = CamServer->client(); client = CamServer->client();
AddLog_P(WC_LOGLEVEL, "create client"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Create client"));
//} //}
} }
void handleMjpeg_task(void) { void handleMjpeg_task(void) {
camera_fb_t *wc_fb; camera_fb_t *wc_fb;
size_t _jpg_buf_len = 0; size_t _jpg_buf_len = 0;
@ -425,31 +447,30 @@ void handleMjpeg_task(void) {
if (!client.connected()) { if (!client.connected()) {
wc_stream_active = 0; wc_stream_active = 0;
AddLog_P(WC_LOGLEVEL,"client fail"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Client fail"));
goto exit; goto exit;
} }
if (wc_stream_active==1) { if (1 == wc_stream_active) {
client.flush(); client.flush();
client.setTimeout(3); client.setTimeout(3);
AddLog_P(WC_LOGLEVEL, "start stream"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Start stream"));
client.print("HTTP/1.1 200 OK\r\n" client.print("HTTP/1.1 200 OK\r\n"
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
"\r\n"); "\r\n");
wc_stream_active = 2; wc_stream_active = 2;
} else { } else {
wc_fb = esp_camera_fb_get(); wc_fb = esp_camera_fb_get();
if (!wc_fb) { if (!wc_fb) {
wc_stream_active = 0; wc_stream_active = 0;
AddLog_P(WC_LOGLEVEL, "frame fail"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame fail"));
goto exit; goto exit;
} }
if (wc_fb->format != PIXFORMAT_JPEG) { if (wc_fb->format != PIXFORMAT_JPEG) {
jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len); jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
if (!jpeg_converted){ if (!jpeg_converted){
AddLog_P(WC_LOGLEVEL, "JPEG compression failed"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: JPEG compression failed"));
_jpg_buf_len = wc_fb->len; _jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf; _jpg_buf = wc_fb->buf;
} }
@ -466,12 +487,12 @@ void handleMjpeg_task(void) {
if (tlen!=_jpg_buf_len) { if (tlen!=_jpg_buf_len) {
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
wc_stream_active=0; wc_stream_active=0;
AddLog_P(WC_LOGLEVEL, "send fail"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Send fail"));
}*/ }*/
client.print("\r\n--" BOUNDARY "\r\n"); client.print("\r\n--" BOUNDARY "\r\n");
#ifdef COPYFRAME #ifdef COPYFRAME
if (tmp_picstore.buff) free(tmp_picstore.buff); 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); tmp_picstore.buff = (uint8_t *)heap_caps_malloc(_jpg_buf_len+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (tmp_picstore.buff) { if (tmp_picstore.buff) {
memcpy(tmp_picstore.buff, _jpg_buf, _jpg_buf_len); memcpy(tmp_picstore.buff, _jpg_buf, _jpg_buf_len);
@ -481,13 +502,13 @@ void handleMjpeg_task(void) {
} }
#endif #endif
if (jpeg_converted) free(_jpg_buf); if (jpeg_converted) { free(_jpg_buf); }
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
//AddLog_P(WC_LOGLEVEL, "send frame"); //AddLog_P2(WC_LOGLEVEL, PSTR("CAM: send frame"));
exit: exit:
if (!wc_stream_active) { if (!wc_stream_active) {
AddLog_P(WC_LOGLEVEL, "stream exit"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit"));
client.flush(); client.flush();
client.stop(); client.stop();
} }
@ -508,8 +529,8 @@ uint32_t motion_brightness;
uint8_t *last_motion_buffer; uint8_t *last_motion_buffer;
uint32_t wc_set_motion_detect(int32_t value) { uint32_t wc_set_motion_detect(int32_t value) {
if (value>=0) motion_detect=value; if (value >= 0) { motion_detect=value; }
if (value==-1) { if (-1 == value) {
return motion_trigger; return motion_trigger;
} else { } else {
return motion_brightness; return motion_brightness;
@ -524,13 +545,13 @@ uint8_t *out_buf=0;
if ((millis()-motion_ltime) > motion_detect) { if ((millis()-motion_ltime) > motion_detect) {
motion_ltime = millis(); motion_ltime = millis();
wc_fb = esp_camera_fb_get(); wc_fb = esp_camera_fb_get();
if (!wc_fb) return; if (!wc_fb) { return; }
if (!last_motion_buffer) { if (!last_motion_buffer) {
last_motion_buffer=(uint8_t *)heap_caps_malloc((wc_fb->width*wc_fb->height)+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); last_motion_buffer=(uint8_t *)heap_caps_malloc((wc_fb->width*wc_fb->height)+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} }
if (last_motion_buffer) { if (last_motion_buffer) {
if (wc_fb->format==PIXFORMAT_JPEG) { 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); out_buf = (uint8_t *)heap_caps_malloc((wc_fb->width*wc_fb->height*3)+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (out_buf) { if (out_buf) {
fmt2rgb888(wc_fb->buf, wc_fb->len, wc_fb->format, out_buf); fmt2rgb888(wc_fb->buf, wc_fb->len, wc_fb->format, out_buf);
@ -563,14 +584,15 @@ uint8_t *out_buf=0;
void wc_show_stream(void) { void wc_show_stream(void) {
#ifndef USE_SCRIPT #ifndef USE_SCRIPT
if (CamServer) WSContentSend_P("<br><img src=\"http://%s:81/stream\"><br><center>webcam stream",WiFi.localIP().toString().c_str()); if (CamServer) {
WSContentSend_P(PSTR("<p><center><img src='http://%s:81/stream' alt='Webcam stream' style='width:99%%;'></center></p><br>"),
WiFi.localIP().toString().c_str());
}
#endif #endif
} }
uint32_t wc_set_streamserver(uint32_t flag) { uint32_t wc_set_streamserver(uint32_t flag) {
if (global_state.wifi_down) { return 0; }
if (global_state.wifi_down) return 0;
wc_stream_active = 0; wc_stream_active = 0;
@ -581,7 +603,7 @@ uint32_t wc_set_streamserver(uint32_t flag) {
CamServer->on("/cam.mjpeg", handleMjpeg); CamServer->on("/cam.mjpeg", handleMjpeg);
CamServer->on("/cam.jpg", handleMjpeg); CamServer->on("/cam.jpg", handleMjpeg);
CamServer->on("/stream", handleMjpeg); CamServer->on("/stream", handleMjpeg);
AddLog_P(WC_LOGLEVEL, "cam stream init"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream init"));
CamServer->begin(); CamServer->begin();
} }
} else { } else {
@ -589,16 +611,16 @@ uint32_t wc_set_streamserver(uint32_t flag) {
CamServer->stop(); CamServer->stop();
delete CamServer; delete CamServer;
CamServer = NULL; CamServer = NULL;
AddLog_P(WC_LOGLEVEL, "cam stream exit"); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit"));
} }
} }
return 0; return 0;
} }
void wc_loop(void) { void wc_loop(void) {
if (CamServer) CamServer->handleClient(); if (CamServer) { CamServer->handleClient(); }
if (wc_stream_active) handleMjpeg_task(); if (wc_stream_active) { handleMjpeg_task(); }
if (motion_detect) detect_motion(); if (motion_detect) { detect_motion(); }
} }
void wc_pic_setup(void) { void wc_pic_setup(void) {
@ -634,9 +656,10 @@ red led = gpio 33
*/ */
/*********************************************************************************************\ /*********************************************************************************************\
* Interface * Commands
\*********************************************************************************************/ \*********************************************************************************************/
#define D_CMND_WC "webcam"
#define D_CMND_WC "Webcam"
const char kWCCommands[] PROGMEM = "|" // no prefix const char kWCCommands[] PROGMEM = "|" // no prefix
D_CMND_WC D_CMND_WC
@ -647,15 +670,18 @@ void (* const WCCommand[])(void) PROGMEM = {
}; };
void CmndWC(void) { void CmndWC(void) {
uint8_t flag=0; uint32_t flag = 0;
if (XdrvMailbox.data_len > 0) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) {
wc_set_streamserver(XdrvMailbox.payload); wc_set_streamserver(XdrvMailbox.payload);
wc_setup(flag); wc_setup(flag);
} }
if (CamServer) flag=1; if (CamServer) { flag = 1; }
Response_P(PSTR("{\"" D_CMND_WC "\":{\"streaming\":%d}"),flag); Response_P(PSTR("{\"" D_CMND_WC "\":{\"Streaming\":\"%s\"}"),GetStateText(flag));
} }
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv39(uint8_t function) { bool Xdrv39(uint8_t function) {
bool result = false; bool result = false;
@ -677,4 +703,5 @@ bool Xdrv39(uint8_t function) {
return result; return result;
} }
#endif #endif // USE_WEBCAM
#endif // ESP32