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 ( 128 x160 )
* 2 = FRAMESIZE_QCIF ( 176 x144 )
* 3 = FRAMESIZE_HQVGA ( 240 x176 )
* 4 = FRAMESIZE_QVGA ( 320 x240 )
* 5 = FRAMESIZE_CIF ( 400 x296 )
* 6 = FRAMESIZE_VGA ( 640 x480 )
* 7 = FRAMESIZE_SVGA ( 800 x600 )
* 8 = FRAMESIZE_XGA ( 1024 x768 )
* 9 = FRAMESIZE_SXGA ( 1280 x1024 )
* 10 = FRAMESIZE_UXGA ( 1600 x1200 )
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 240 Mhz 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 ) ;
}
}
2020-05-02 15:44:44 +01:00
void wc_show_stream ( void ) {
# ifndef USE_SCRIPT
2020-05-03 17:37:12 +01:00
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 ( ) ) ;
}
2020-05-02 15:44:44 +01:00
# 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 ;
}
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-03 17:37:12 +01:00
* Commands
2020-05-02 07:10:23 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-05-03 17:37:12 +01:00
# define D_CMND_WC "Webcam"
2020-05-02 15:44:44 +01:00
const char kWCCommands [ ] PROGMEM = " | " // no prefix
D_CMND_WC
;
void ( * const WCCommand [ ] ) ( void ) PROGMEM = {
& CmndWC ,
} ;
void CmndWC ( void ) {
2020-05-03 17:37:12 +01:00
uint32_t flag = 0 ;
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 10 ) ) {
2020-05-02 15:44:44 +01:00
wc_set_streamserver ( XdrvMailbox . payload ) ;
2020-05-04 05:25:13 +01:00
wc_setup ( XdrvMailbox . payload ) ;
2020-05-02 15:44:44 +01:00
}
2020-05-03 17:37:12 +01:00
if ( CamServer ) { flag = 1 ; }
Response_P ( PSTR ( " { \" " D_CMND_WC " \" :{ \" Streaming \" : \" %s \" } " ) , GetStateText ( flag ) ) ;
2020-05-02 15:44:44 +01:00
}
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 ;
2020-05-02 15:44:44 +01:00
case FUNC_WEB_ADD_MAIN_BUTTON :
wc_show_stream ( ) ;
break ;
case FUNC_COMMAND :
result = DecodeCommand ( kWCCommands , WCCommand ) ;
break ;
2020-05-02 07:10:23 +01:00
}
return result ;
}
2020-05-03 17:37:12 +01:00
# endif // USE_WEBCAM
# endif // ESP32