diff --git a/README.md b/README.md index fb41122..973e0a6 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ https://wiki.ai-thinker.com/esp32-cam ## Troubleshooting: -Please read this excellent guide for help with all the common issues: +Please read this excellent guide for help with some common issues seen with the camera modules: https://randomnerdtutorials.com/esp32-cam-troubleshooting-guide/ ## Setup: @@ -102,12 +102,6 @@ Contributions are welcome; please see the [Contribution guidelines](CONTRIBUTING Time allowing; my Current plan is: -V3 Options, UI and server enhancements; -* All the primary config options moved to the `myconfig.h` file. -* Miniviewer, favicons -* UI now shows stream links and build info -* Nearly Complete - V4 Remove face recognition entirely; * Dont try to make it optional, this is a code and maintenance nightmare. V3 can be maintained on a branch for those who need it. * Investigate using SD card to capture images @@ -115,5 +109,3 @@ V4 Remove face recognition entirely; * UI Skinning/Theming You can check the [enhancement list](https://github.com/easytarget/esp32-cam-webserver/issues?q=is%3Aissue+label%3Aenhancement) (past and present), and add any thoghts you may have there. Things that have occurred to me are, in no particular order: -* The module has a SD/TF card slot; this is currently unused, but I would like to add the ability to store snapshots; recording Video at low resolution may be possible, but the card interface is too slow for HD video as far as I know. -* Remove face rcognition to save a Mb+ of code space and then implement over the air updates. diff --git a/app_httpd.cpp b/app_httpd.cpp index fb126f3..01d5df4 100644 --- a/app_httpd.cpp +++ b/app_httpd.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// Original Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,14 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#include "esp_http_server.h" -#include "esp_timer.h" -#include "esp_camera.h" -#include "img_converters.h" -#include "Arduino.h" + +#include +#include +#include +#include +#include +#include +#include #include #include "index_ov2640.h" @@ -52,6 +55,7 @@ extern bool autoLamp; extern int8_t detection_enabled; extern int8_t recognition_enabled; extern bool filesystem; +extern String critERR; extern bool debugData; extern int sketchSize; extern int sketchSpace; @@ -622,14 +626,18 @@ static esp_err_t cmd_handler(httpd_req_t *req){ if (filesystem) removePrefs(SPIFFS); } else if(!strcmp(variable, "reboot")) { + esp_task_wdt_init(3,true); // schedule a a watchdog panic event for 3 seconds in the future + esp_task_wdt_add(NULL); + periph_module_disable(PERIPH_I2C0_MODULE); // try to shut I2C down properly + periph_module_disable(PERIPH_I2C1_MODULE); + periph_module_reset(PERIPH_I2C0_MODULE); + periph_module_reset(PERIPH_I2C1_MODULE); Serial.print("REBOOT requested"); - for (int i=0; i<20; i++) { + while(true) { flashLED(50); delay(150); Serial.print('.'); } - Serial.printf(" Thats all folks!\n\n"); - ESP.restart(); } else { res = -1; @@ -730,7 +738,7 @@ static esp_err_t dump_handler(httpd_req_t *req){ Serial.println("\nDump Requested"); Serial.print("Preferences file: "); dumpPrefs(SPIFFS); - static char dumpOut[1200] = ""; + static char dumpOut[2000] = ""; char * d = dumpOut; // Header d+= sprintf(d,"\n"); @@ -739,9 +747,14 @@ static esp_err_t dump_handler(httpd_req_t *req){ d+= sprintf(d,"\n"); d+= sprintf(d,"\n"); d+= sprintf(d,"\n"); - d+= sprintf(d,"\n\n"); + d+= sprintf(d,"\n"); + d+= sprintf(d,"\n"); d+= sprintf(d,"\n"); - d+= sprintf(d,"

ESP32 Cam Webserver

\n"); + if (critERR.length() > 0) { + d+= sprintf(d,"%s
\n", critERR.c_str()); + Serial.printf("\n\nA critical error has occurred when initialising Hardware, see startup megssages\n\n\n"); + } + d+= sprintf(d,"

ESP32 Cam Webserver

\n"); // Module d+= sprintf(d,"Name: %s
\n", myName); Serial.printf("Name: %s\n", myName); @@ -817,9 +830,12 @@ static esp_err_t dump_handler(httpd_req_t *req){ // Footer d+= sprintf(d,"
\n"); - d+= sprintf(d,"\n"); + d+= sprintf(d,"\n"); d+= sprintf(d,"\n"); - d+= sprintf(d,"
\n\n\n"); + d+= sprintf(d,"\n\n"); + // A javascript timer to refresh the page every minute. + d+= sprintf(d,"\n\n"); *d++ = 0; httpd_resp_set_type(req, "text/html"); httpd_resp_set_hdr(req, "Content-Encoding", "identity"); @@ -840,6 +856,22 @@ static esp_err_t streamviewer_handler(httpd_req_t *req){ return httpd_resp_send(req, (const char *)streamviewer_html, streamviewer_html_len); } +static esp_err_t error_handler(httpd_req_t *req){ + flashLED(75); + Serial.println("Sending Error page"); + std::string s(error_html); + size_t index; + while ((index = s.find("")) != std::string::npos) + s.replace(index, strlen(""), httpURL); + while ((index = s.find("")) != std::string::npos) + s.replace(index, strlen(""), myName); + while ((index = s.find("")) != std::string::npos) + s.replace(index, strlen(""), critERR.c_str()); + httpd_resp_set_type(req, "text/html"); + httpd_resp_set_hdr(req, "Content-Encoding", "identity"); + return httpd_resp_send(req, (const char *)s.c_str(), s.length()); +} + static esp_err_t index_handler(httpd_req_t *req){ char* buf; size_t buf_len; @@ -994,6 +1026,18 @@ void startCameraServer(int hPort, int sPort){ .handler = info_handler, .user_ctx = NULL }; + httpd_uri_t error_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = error_handler, + .user_ctx = NULL + }; + httpd_uri_t viewerror_uri = { + .uri = "/view", + .method = HTTP_GET, + .handler = error_handler, + .user_ctx = NULL + }; // Filter list; used during face detection ra_filter_init(&ra_filter, 20); @@ -1017,15 +1061,20 @@ void startCameraServer(int hPort, int sPort){ face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES); // The size of the allocated data block; calculated in dl_lib_calloc() + + // Request Handlers; config.max_uri_handlers (above) must be >= the number of handlers config.server_port = hPort; config.ctrl_port = hPort; Serial.printf("Starting web server on port: '%d'\n", config.server_port); if (httpd_start(&camera_httpd, &config) == ESP_OK) { - // Note; config.max_uri_handlers (above) must be >= the number of handlers - httpd_register_uri_handler(camera_httpd, &index_uri); - httpd_register_uri_handler(camera_httpd, &cmd_uri); - httpd_register_uri_handler(camera_httpd, &status_uri); - httpd_register_uri_handler(camera_httpd, &capture_uri); + if (critERR.length() > 0) { + httpd_register_uri_handler(camera_httpd, &error_uri); + } else { + httpd_register_uri_handler(camera_httpd, &index_uri); + httpd_register_uri_handler(camera_httpd, &cmd_uri); + httpd_register_uri_handler(camera_httpd, &status_uri); + httpd_register_uri_handler(camera_httpd, &capture_uri); + } httpd_register_uri_handler(camera_httpd, &style_uri); httpd_register_uri_handler(camera_httpd, &favicon_16x16_uri); httpd_register_uri_handler(camera_httpd, &favicon_32x32_uri); @@ -1034,14 +1083,18 @@ void startCameraServer(int hPort, int sPort){ httpd_register_uri_handler(camera_httpd, &dump_uri); } - config.server_port = sPort; config.ctrl_port = sPort; Serial.printf("Starting stream server on port: '%d'\n", config.server_port); if (httpd_start(&stream_httpd, &config) == ESP_OK) { - httpd_register_uri_handler(stream_httpd, &stream_uri); - httpd_register_uri_handler(stream_httpd, &info_uri); - httpd_register_uri_handler(stream_httpd, &streamviewer_uri); + if (critERR.length() > 0) { + httpd_register_uri_handler(camera_httpd, &error_uri); + httpd_register_uri_handler(camera_httpd, &viewerror_uri); + } else { + httpd_register_uri_handler(stream_httpd, &stream_uri); + httpd_register_uri_handler(stream_httpd, &info_uri); + httpd_register_uri_handler(stream_httpd, &streamviewer_uri); + } httpd_register_uri_handler(stream_httpd, &favicon_16x16_uri); httpd_register_uri_handler(stream_httpd, &favicon_32x32_uri); httpd_register_uri_handler(stream_httpd, &favicon_ico_uri); diff --git a/esp32-cam-webserver.ino b/esp32-cam-webserver.ino index 023f7a1..8b1fd7a 100644 --- a/esp32-cam-webserver.ino +++ b/esp32-cam-webserver.ino @@ -1,8 +1,11 @@ -#include "esp_camera.h" +#include +#include +#include #include #include #include "src/parsebytes.h" + /* This sketch is a extension/expansion/reork of the 'official' ESP32 Camera example * sketch from Expressif: * https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer @@ -169,6 +172,10 @@ const int pwmMax = pow(2,pwmresolution)-1; int8_t recognition_enabled = 0; #endif +// Critical error string; if set during init (camera hardware failure) it +// will be returned for all http requests +String critERR = ""; + // Debug Data for stream and capture #if defined(DEBUG_DEFAULT_ON) bool debugData = true; @@ -185,7 +192,7 @@ void flashLED(int flashtime) { #else return; // No notifcation LED, do nothing, no delay #endif -} +} // Lamp Control void setLamp(int newVal) { @@ -381,6 +388,8 @@ void WifiSetup() { } void setup() { + // This might reduce boot loops caused by camera init failures when soft rebooting + // See, for instance, https://esp32.com/viewtopic.php?t=3152 Serial.begin(115200); Serial.setDebugOutput(true); Serial.println(); @@ -443,96 +452,108 @@ void setup() { // camera init esp_err_t err = esp_camera_init(&config); - if (err == ESP_OK) { - Serial.println("Camera init succeeded"); - } else { + if (err != ESP_OK) { delay(100); // need a delay here or the next serial o/p gets missed - Serial.println("Halted: Camera sensor failed to initialise"); - Serial.println("Will reboot to try again in 10s\n"); - delay(10000); - ESP.restart(); - } - sensor_t * s = esp_camera_sensor_get(); - - // Dump camera module, warn for unsupported modules. - switch (s->id.PID) { - case OV9650_PID: Serial.println("WARNING: OV9650 camera module is not properly supported, will fallback to OV2640 operation"); break; - case OV7725_PID: Serial.println("WARNING: OV7725 camera module is not properly supported, will fallback to OV2640 operation"); break; - case OV2640_PID: Serial.println("OV2640 camera module detected"); break; - case OV3660_PID: Serial.println("OV3660 camera module detected"); break; - default: Serial.println("WARNING: Camera module is unknown and not properly supported, will fallback to OV2640 operation"); - } - - // OV3660 initial sensors are flipped vertically and colors are a bit saturated - if (s->id.PID == OV3660_PID) { - s->set_vflip(s, 1); //flip it back - s->set_brightness(s, 1); //up the blightness just a bit - s->set_saturation(s, -2); //lower the saturation - } - - // M5 Stack Wide has special needs - #if defined(CAMERA_MODEL_M5STACK_WIDE) - s->set_vflip(s, 1); - s->set_hmirror(s, 1); - #endif - - // Config can override mirror and flip - #if defined(H_MIRROR) - s->set_hmirror(s, H_MIRROR); - #endif - #if defined(V_FLIP) - s->set_vflip(s, V_FLIP); - #endif - - // set initial frame rate - #if defined(DEFAULT_RESOLUTION) - s->set_framesize(s, DEFAULT_RESOLUTION); - #else - s->set_framesize(s, FRAMESIZE_SVGA); - #endif - - /* - * Add any other defaults you want to apply at startup here: - * uncomment the line and set the value as desired (see the comments) - * - * these are defined in the esp headers here: - * https://github.com/espressif/esp32-camera/blob/master/driver/include/sensor.h#L149 - */ - - //s->set_framesize(s, FRAMESIZE_SVGA); // FRAMESIZE_[QQVGA|HQVGA|QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA|QXGA(ov3660)]); - //s->set_quality(s, val); // 10 to 63 - //s->set_brightness(s, 0); // -2 to 2 - //s->set_contrast(s, 0); // -2 to 2 - //s->set_saturation(s, 0); // -2 to 2 - //s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia) - //s->set_whitebal(s, 1); // aka 'awb' in the UI; 0 = disable , 1 = enable - //s->set_awb_gain(s, 1); // 0 = disable , 1 = enable - //s->set_wb_mode(s, 0); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) - //s->set_exposure_ctrl(s, 1); // 0 = disable , 1 = enable - //s->set_aec2(s, 0); // 0 = disable , 1 = enable - //s->set_ae_level(s, 0); // -2 to 2 - //s->set_aec_value(s, 300); // 0 to 1200 - //s->set_gain_ctrl(s, 1); // 0 = disable , 1 = enable - //s->set_agc_gain(s, 0); // 0 to 30 - //s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6 - //s->set_bpc(s, 0); // 0 = disable , 1 = enable - //s->set_wpc(s, 1); // 0 = disable , 1 = enable - //s->set_raw_gma(s, 1); // 0 = disable , 1 = enable - //s->set_lenc(s, 1); // 0 = disable , 1 = enable - //s->set_hmirror(s, 0); // 0 = disable , 1 = enable - //s->set_vflip(s, 0); // 0 = disable , 1 = enable - //s->set_dcw(s, 1); // 0 = disable , 1 = enable - //s->set_colorbar(s, 0); // 0 = disable , 1 = enable - - // We now have camera with default init - // check for saved preferences and apply them - - if (filesystem) { - filesystemStart(); - loadPrefs(SPIFFS); - loadFaceDB(SPIFFS); + Serial.printf("\n\nCRITICAL FAILURE: Camera sensor failed to initialise.\n\n"); + Serial.printf("A full (hard, power off/on) reboot will probably be needed to recover from this.\n"); + Serial.printf("Meanwhile; this unit will reboot in 1 minute since these errors sometime clear automatically\n"); + // Reset the I2C bus.. may help when rebooting. + periph_module_disable(PERIPH_I2C0_MODULE); // try to shut I2C down properly in case that is the problem + periph_module_disable(PERIPH_I2C1_MODULE); + periph_module_reset(PERIPH_I2C0_MODULE); + periph_module_reset(PERIPH_I2C1_MODULE); + // And set the error text for the UI + critERR = "

Error!


Camera module failed to initialise!

Please reset (power off/on) the camera.

"; + critERR += "

We will continue to reboot once per minute since this error sometimes clears automatically.

"; + // Start a 60 second watchdog timer + esp_task_wdt_init(60,true); + esp_task_wdt_add(NULL); } else { - Serial.println("No Internal Filesystem, cannot save preferences or face DB"); + Serial.println("Camera init succeeded"); + + // Get a reference to the sensor + sensor_t * s = esp_camera_sensor_get(); + + // Dump camera module, warn for unsupported modules. + switch (s->id.PID) { + case OV9650_PID: Serial.println("WARNING: OV9650 camera module is not properly supported, will fallback to OV2640 operation"); break; + case OV7725_PID: Serial.println("WARNING: OV7725 camera module is not properly supported, will fallback to OV2640 operation"); break; + case OV2640_PID: Serial.println("OV2640 camera module detected"); break; + case OV3660_PID: Serial.println("OV3660 camera module detected"); break; + default: Serial.println("WARNING: Camera module is unknown and not properly supported, will fallback to OV2640 operation"); + } + + // OV3660 initial sensors are flipped vertically and colors are a bit saturated + if (s->id.PID == OV3660_PID) { + s->set_vflip(s, 1); //flip it back + s->set_brightness(s, 1); //up the blightness just a bit + s->set_saturation(s, -2); //lower the saturation + } + + // M5 Stack Wide has special needs + #if defined(CAMERA_MODEL_M5STACK_WIDE) + s->set_vflip(s, 1); + s->set_hmirror(s, 1); + #endif + + // Config can override mirror and flip + #if defined(H_MIRROR) + s->set_hmirror(s, H_MIRROR); + #endif + #if defined(V_FLIP) + s->set_vflip(s, V_FLIP); + #endif + + // set initial frame rate + #if defined(DEFAULT_RESOLUTION) + s->set_framesize(s, DEFAULT_RESOLUTION); + #else + s->set_framesize(s, FRAMESIZE_SVGA); + #endif + + /* + * Add any other defaults you want to apply at startup here: + * uncomment the line and set the value as desired (see the comments) + * + * these are defined in the esp headers here: + * https://github.com/espressif/esp32-camera/blob/master/driver/include/sensor.h#L149 + */ + + //s->set_framesize(s, FRAMESIZE_SVGA); // FRAMESIZE_[QQVGA|HQVGA|QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA|QXGA(ov3660)]); + //s->set_quality(s, val); // 10 to 63 + //s->set_brightness(s, 0); // -2 to 2 + //s->set_contrast(s, 0); // -2 to 2 + //s->set_saturation(s, 0); // -2 to 2 + //s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia) + //s->set_whitebal(s, 1); // aka 'awb' in the UI; 0 = disable , 1 = enable + //s->set_awb_gain(s, 1); // 0 = disable , 1 = enable + //s->set_wb_mode(s, 0); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) + //s->set_exposure_ctrl(s, 1); // 0 = disable , 1 = enable + //s->set_aec2(s, 0); // 0 = disable , 1 = enable + //s->set_ae_level(s, 0); // -2 to 2 + //s->set_aec_value(s, 300); // 0 to 1200 + //s->set_gain_ctrl(s, 1); // 0 = disable , 1 = enable + //s->set_agc_gain(s, 0); // 0 to 30 + //s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6 + //s->set_bpc(s, 0); // 0 = disable , 1 = enable + //s->set_wpc(s, 1); // 0 = disable , 1 = enable + //s->set_raw_gma(s, 1); // 0 = disable , 1 = enable + //s->set_lenc(s, 1); // 0 = disable , 1 = enable + //s->set_hmirror(s, 0); // 0 = disable , 1 = enable + //s->set_vflip(s, 0); // 0 = disable , 1 = enable + //s->set_dcw(s, 1); // 0 = disable , 1 = enable + //s->set_colorbar(s, 0); // 0 = disable , 1 = enable + + // We now have camera with default init + // check for saved preferences and apply them + + if (filesystem) { + filesystemStart(); + loadPrefs(SPIFFS); + loadFaceDB(SPIFFS); + } else { + Serial.println("No Internal Filesystem, cannot save preferences or face DB"); + } } /* @@ -573,12 +594,15 @@ void setup() { } sprintf(streamURL, "http://%d.%d.%d.%d:%d/", ip[0], ip[1], ip[2], ip[3], streamPort); #endif - Serial.printf("\nCamera Ready!\nUse '%s' to connect\n", httpURL); - Serial.printf("Stream viewer available at '%sview'\n", streamURL); - Serial.printf("Raw stream URL is '%s'\n", streamURL); - - if (debugData) Serial.println("Camera debug data is enabled (send any char to disable)"); - else Serial.println("Camera debug data is disabled (send any char to enable)"); + if (critERR.length() == 0) { + Serial.printf("\nCamera Ready!\nUse '%s' to connect\n", httpURL); + Serial.printf("Stream viewer available at '%sview'\n", streamURL); + Serial.printf("Raw stream URL is '%s'\n", streamURL); + if (debugData) Serial.println("Camera debug data is enabled (send any char to disable)"); + else Serial.println("Camera debug data is disabled (send any char to enable)"); + } else { + Serial.printf("\nCamera unavailable due to initialisation errors.\n\n"); + } // Used when dumping status; these are slow functions, so just do them once during startup sketchSize = ESP.getSketchSize(); diff --git a/index_other.h b/index_other.h index 341c48a..aff496b 100644 --- a/index_other.h +++ b/index_other.h @@ -435,7 +435,7 @@ const uint8_t streamviewer_html[] = R"=====( size_t streamviewer_html_len = sizeof(streamviewer_html)-1; -/* Prototype Captive Portal page +/* Captive Portal page we replace the <> delimited strings with correct values as it is served */ const std::string portal_html = R"=====( @@ -463,3 +463,28 @@ const std::string portal_html = R"=====( Camera Details
)====="; + +/* Error page + we replace the <> delimited strings with correct values as it is served */ + +const std::string error_html = R"=====( + + + + + <CAMNAME> - Error + + + + + + +

+ + + +)====="; diff --git a/index_ov2640.h b/index_ov2640.h index 9d75f31..69179aa 100644 --- a/index_ov2640.h +++ b/index_ov2640.h @@ -293,6 +293,7 @@ const uint8_t index_ov2640_html[] = R"=====( var streamURL = 'Undefined'; var viewerURL = 'Undefined'; + const header = document.getElementById('logo') const settings = document.getElementById('sidebar') const waitSettings = document.getElementById('wait-settings') const lampGroup = document.getElementById('lamp-group') @@ -632,7 +633,12 @@ const uint8_t index_ov2640_html[] = R"=====( if (confirm("Reboot the Camera Module?")) { updateConfig(rebootButton); // Some sort of countdown here? - location.reload(); + hide(settings); + hide(viewContainer); + header.innerHTML = '

Rebooting!


Page will reload after 30 seconds.'; + setTimeout(function() { + location.replace(document.URL); + }, 30000); } }