diff --git a/.travis.yml b/.travis.yml index db24e40..693813a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_script: script: - cd $TRAVIS_BUILD_DIR - export PATH="$HOME/arduino_ide:$PATH" - - arduino --board esp32:esp32:esp32:PartitionScheme=huge_app,FlashFreq=80 --pref compiler.warning_level=all --save-prefs + - arduino --board esp32:esp32:esp32:PartitionScheme=default,FlashFreq=80 --pref compiler.warning_level=all --save-prefs - arduino --verbose --verify esp32-cam-webserver.ino - cp --preserve --verbose myconfig.sample.h myconfig.h - arduino --verbose --verify esp32-cam-webserver.ino diff --git a/Docs/ota-board-selection-small.png b/Docs/ota-board-selection-small.png new file mode 100644 index 0000000..f90fb6b Binary files /dev/null and b/Docs/ota-board-selection-small.png differ diff --git a/Docs/ota-board-selection-vanilla.png b/Docs/ota-board-selection-vanilla.png new file mode 100644 index 0000000..0dc81d6 Binary files /dev/null and b/Docs/ota-board-selection-vanilla.png differ diff --git a/Docs/ota-board-selection.png b/Docs/ota-board-selection.png new file mode 100644 index 0000000..f56dbdc Binary files /dev/null and b/Docs/ota-board-selection.png differ diff --git a/README.md b/README.md index 987087e..933ee92 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,14 @@ But expanded with: * Save and restore settings * Control of on-board lamps, rotate the view in the browser * Dedicated standalone stream viewer +* Over The Air firmware updates * Lots of minor fixes and tweaks, documentation etc. +And 'reduced' by removing the Face Recognition features +* **If you want to try the Face Recognition features** please use the [`3.x` maintenance branch](https://github.com/easytarget/esp32-cam-webserver/tree/3.x), which still recieves bugfixes, but is not reciving any further development. +* They were a demo, only worked in low resolution modes, did not preserve the face database between power cycles, and were of little use in real-world applications. +* There are other (specialised) sketches for the ESP-CAM that do use face recognitioni more effectively, if this is your thing :-) + The original example is a bit incomprehensible and hard to modify as supplied. It is very focused on showing off the face recognition capabilities, and forgets the 'webcam' part. * There are many other variants of a webcam server for these modules online, but most are created for a specific scenario and not good for general, casual, webcam use. @@ -41,8 +47,6 @@ The ESP itself is susceptable to the usual list of WiFi problems, not helped by A basic limitation of the sketch is that it can can only support one stream at a time. If you try to connect to a cam that is already streaming (or attempting to stream) you will get no response and, eventually, a timeout. The stream itself is a [MJPEG stream](https://en.wikipedia.org/wiki/Motion_JPEG), which relies on the client (the web browser) to hold the connection open and request each new frame in turn via javascript. This can cause errors when browsers run into Javascript or caching problem, fail to request new frames or refuse to close the connection. -Another known issue is that if you are streaming with face detection turned on any new tatic mage capture request will cause the stream to exit. - The existing [issues list](https://github.com/easytarget/esp32-cam-webserver/issues?q=is%3Aissue) on Github is a good place to start if you have a specific issue not covered above ## Setup: @@ -78,13 +82,17 @@ To make a permanent config with your home wifi settings, different defaults or a ### Programming -Assuming you are using the latest Espressif Arduino core the `AI-THINKER` board (or whatever board you select for programming) will appear in the ESP32 Arduino section of the boards list. -![IDE board config](Docs/board-selection-small.png) +Assuming you are using the latest Espressif Arduino core the `ESP32 Dev Module` board will appear in the ESP32 Arduino section of the boards list. Select this (do not use the `AI-THINKER` entry listed in the boiards menu, it is not OTA compatible, and will caus the module to crash and reboot rather than updating if you use it. +![IDE board config](Docs/ota-board-selection.png) + +Make sure you select the `Default 4MB with Spiffs` partition scheme and turn `PSRAM` on. -Compile and upload the code from the IDE, when the `Connecting...` appears in the console reboot the ESP32 module while keeping **GPIO0** grounded. You can release GPO0 once the sketch is uploading, most boards have a 'boot' button to trigger a reboot. +The first time you program (or if OTA is failing) you need to compile and upload the code from the IDE, and when the `Connecting...` appears in the console reboot the ESP32 module while keeping **GPIO0** grounded. You can release GPO0 once the sketch is uploading, most boards have a 'boot' button to trigger a reboot. Once the upload completes (be patient, it can be a bit slow) open the serial monitor in the IDE and reboot the board again without GPIO0 grounded. In the serial monitor you should see the board start, connect to the wifi and then report the IP address it has been assigned. +Once you have the initial upload done and the board is connected to the wifi network you should see it appearing in the `network ports` list of the IDE, and you can upload wirelessly. + If you have a status LED configured it will give a double flash when it begins attempting to conenct to WiFi, and five short flashes once it has succeeded. It will also flash briefly when you access the camera to change settings. Go to the URL given in the serial output, the web UI should appear with the settings panel open. Click away! @@ -95,17 +103,17 @@ Go to the URL given in the serial output, the web UI should appear with the sett The WiFi details can be stored in an (optional) header file to allow easier code development, and a camera name for the UI title can be configured. The lamp and status LED's are optional, and the lamp uses a exponential scale for brightness so that the control has some finess. +All of the face recognition code has been removed as of V4.0; this reduces the code size enough to allow OTA programming while improving compile and programming times. + The compressed and binary encoded HTML used in the example has been unpacked to raw text, this makes it much easier to access and modify the Javascript and UI elements. Given the relatively small size of the index page there is very little benefit from compressing it. The streamviewer, lamp control, and all the other new features have been added. I have tried to retain the basic structure of the original example,extending where necesscary. -I have left all the Face Recognition code untouched, it works, and with good lighting and camera position it can work quite well. But you can only use it in low-resolution modes, and it is not something I will be using. - The web UI has had changes to add the lamp control (only when enabled) and make the streamm window rotate and resize appropriately. I also made the 'Start Stream' and 'Snapshot' controls more prominent, and added feedback of the camera name + firmware. I would also like to shoutout to @jmfloyd; who suggested rotating the image in the browser since the esp32 itself cannot do this. -![The stream viewer](Docs/streamview.png)
*Standalone StreamViewer; No decoration or controls, resizable, doubleclick image for fullscreen* +![The stream viewer](Docs/streamview.png)
*Standalone StreamViewer; No decoration or controls, the image is resizable, and you can doubleclick it for fullscreen* ![The info page](Docs/infodump.png)
*Boring Details* @@ -125,10 +133,15 @@ Contributions are welcome; please see the [Contribution guidelines](CONTRIBUTING Time allowing; my Current plan is: -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. +V4 +* Remove face recognition entirely; + * **Done**, see the `NoFace` branch :sunglasses: + * Not 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 -* implement OTA and a better network stack for remembering multiple AP's, auto-config etc. +* Implement OTA and a better network stack for remembering multiple AP's, auto-config etc. + * **Basic OTA is Done**, see the `NoFace` branch. + * Advanced (web upload) OTA might be nice to have is possible + * For the Network setup I want to implement https://github.com/Hieromon/AutoConnect * UI Skinning/Theming * OSD * Temperature/humidity/pressure sensor suport (bme20,dht11) diff --git a/app_httpd.cpp b/app_httpd.cpp index a8ab2d4..5facbcc 100644 --- a/app_httpd.cpp +++ b/app_httpd.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -54,41 +53,12 @@ extern unsigned long imagesServed; extern int myRotation; extern int lampVal; 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; extern String sketchMD5; -extern char knownFaceText[]; -extern char unknownFaceText[]; - - -#include "fb_gfx.h" -#include "fd_forward.h" -#include "fr_forward.h" - -#define ENROLL_CONFIRM_TIMES 5 -#define FACE_ID_SAVE_NUMBER 7 - -#define FACE_COLOR_WHITE 0x00FFFFFF -#define FACE_COLOR_BLACK 0x00000000 -#define FACE_COLOR_RED 0x000000FF -#define FACE_COLOR_GREEN 0x0000FF00 -#define FACE_COLOR_BLUE 0x00FF0000 -#define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN) -#define FACE_COLOR_CYAN (FACE_COLOR_BLUE | FACE_COLOR_GREEN) -#define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED) - -typedef struct { - size_t size; //number of values used for filtering - size_t index; //current value index - size_t count; //value count - int sum; - int * values; //array to be filled with values -} ra_filter_t; typedef struct { httpd_req_t *req; @@ -100,156 +70,9 @@ static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; -static ra_filter_t ra_filter; httpd_handle_t stream_httpd = NULL; httpd_handle_t camera_httpd = NULL; -static mtmn_config_t mtmn_config = {0}; -static int8_t is_enrolling = 0; -static face_id_list id_list = {0}; -int id_list_alloc = 0; - -static ra_filter_t * ra_filter_init(ra_filter_t * filter, size_t sample_size){ - memset(filter, 0, sizeof(ra_filter_t)); - - filter->values = (int *)malloc(sample_size * sizeof(int)); - if(!filter->values){ - return NULL; - } - memset(filter->values, 0, sample_size * sizeof(int)); - - filter->size = sample_size; - return filter; -} - -static int ra_filter_run(ra_filter_t * filter, int value) { - if(!filter->values){ - return value; - } - filter->sum -= filter->values[filter->index]; - filter->values[filter->index] = value; - filter->sum += filter->values[filter->index]; - filter->index++; - filter->index = filter->index % filter->size; - if (filter->count < filter->size) { - filter->count++; - } - return filter->sum / filter->count; -} - -static void rgb_print(dl_matrix3du_t *image_matrix, uint32_t color, const char * str){ - fb_data_t fb; - fb.width = image_matrix->w; - fb.height = image_matrix->h; - fb.data = image_matrix->item; - fb.bytes_per_pixel = 3; - fb.format = FB_BGR888; - fb_gfx_print(&fb, (fb.width - (strlen(str) * 14)) / 2, 10, color, str); -} - -static int rgb_printf(dl_matrix3du_t *image_matrix, uint32_t color, const char *format, ...){ - char loc_buf[64]; - char * temp = loc_buf; - int len; - va_list arg; - va_list copy; - va_start(arg, format); - va_copy(copy, arg); - len = vsnprintf(loc_buf, sizeof(loc_buf), format, arg); - va_end(copy); - if(len >= sizeof(loc_buf)){ - temp = (char*)malloc(len+1); - if(temp == NULL) { - return 0; - } - } - vsnprintf(temp, len+1, format, arg); - va_end(arg); - rgb_print(image_matrix, color, temp); - if(len > 64){ - free(temp); - } - return len; -} - -static void draw_face_boxes(dl_matrix3du_t *image_matrix, box_array_t *boxes, int face_id){ - int x, y, w, h, i; - uint32_t color = FACE_COLOR_YELLOW; - if(face_id < 0){ - color = FACE_COLOR_RED; - } else if(face_id > 0){ - color = FACE_COLOR_GREEN; - } - fb_data_t fb; - fb.width = image_matrix->w; - fb.height = image_matrix->h; - fb.data = image_matrix->item; - fb.bytes_per_pixel = 3; - fb.format = FB_BGR888; - for (i = 0; i < boxes->len; i++){ - // rectangle box - x = (int)boxes->box[i].box_p[0]; - y = (int)boxes->box[i].box_p[1]; - w = (int)boxes->box[i].box_p[2] - x + 1; - h = (int)boxes->box[i].box_p[3] - y + 1; - fb_gfx_drawFastHLine(&fb, x, y, w, color); - fb_gfx_drawFastHLine(&fb, x, y+h-1, w, color); - fb_gfx_drawFastVLine(&fb, x, y, h, color); - fb_gfx_drawFastVLine(&fb, x+w-1, y, h, color); - #if 0 - // landmark - int x0, y0, j; - for (j = 0; j < 10; j+=2) { - x0 = (int)boxes->landmark[i].landmark_p[j]; - y0 = (int)boxes->landmark[i].landmark_p[j+1]; - fb_gfx_fillRect(&fb, x0, y0, 3, 3, color); - } - #endif - } -} - -static int run_face_recognition(dl_matrix3du_t *image_matrix, box_array_t *net_boxes){ - dl_matrix3du_t *aligned_face = NULL; - int matched_id = 0; - - aligned_face = dl_matrix3du_alloc(1, FACE_WIDTH, FACE_HEIGHT, 3); - if(!aligned_face){ - Serial.println("FACE: could not allocate face recognition buffer"); - return matched_id; - } - if (align_face(net_boxes, image_matrix, aligned_face) == ESP_OK){ - if (is_enrolling == 1){ - int8_t this_face = id_list.tail + 1; - int8_t left_sample_face = enroll_face(&id_list, aligned_face); - - if(left_sample_face == (ENROLL_CONFIRM_TIMES - 1)){ - Serial.printf("FACE: enrolling new face ID: %d\r\n", this_face); - } - Serial.printf("FACE: enroll ID: %d sample %d\r\n", this_face, ENROLL_CONFIRM_TIMES - left_sample_face); - rgb_printf(image_matrix, FACE_COLOR_CYAN, "ID[%u] Sample[%u]", this_face, ENROLL_CONFIRM_TIMES - left_sample_face); - if (left_sample_face == 0){ - is_enrolling = 0; - Serial.printf("FACE: enrolled face ID: %d\r\n", this_face); - } - } else { - matched_id = recognize_face(&id_list, aligned_face) + 1; - if (matched_id > 0) { - Serial.printf("FACE: match ID: %u: ", matched_id); - rgb_printf(image_matrix, FACE_COLOR_GREEN, "%s%u", knownFaceText, matched_id); - } else { - matched_id = -1; - Serial.println("FACE: no match found:"); - rgb_printf(image_matrix, FACE_COLOR_RED, "%s", unknownFaceText); - } - } - } else { - Serial.println("FACE: not aligned:"); - rgb_print(image_matrix, FACE_COLOR_YELLOW, "???"); - } - - dl_matrix3du_free(aligned_face); - return matched_id; -} void serialDump() { Serial.println("\r\nPreferences file: "); @@ -303,7 +126,6 @@ void serialDump() { if (filesystem) { Serial.printf("Spiffs: %i, used: %i\r\n", SPIFFS.totalBytes(), SPIFFS.usedBytes()); } - Serial.printf("Enrolled faces: %i (max %i)\r\n", id_list.count, id_list.size); Serial.println(); return; } @@ -342,84 +164,21 @@ static esp_err_t capture_handler(httpd_req_t *req){ httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - size_t out_len, out_width, out_height; - uint8_t * out_buf; - bool s; - bool detected = false; - int face_id = 0; - if(!detection_enabled || fb->width > 400 || streamCount != 0){ - size_t fb_len = 0; - if(fb->format == PIXFORMAT_JPEG){ - fb_len = fb->len; - res = httpd_resp_send(req, (const char *)fb->buf, fb->len); - } else { - jpg_chunking_t jchunk = {req, 0}; - res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; - httpd_resp_send_chunk(req, NULL, 0); - fb_len = jchunk.len; - } - esp_camera_fb_return(fb); - int64_t fr_end = esp_timer_get_time(); - if (debugData) { - Serial.printf("JPG: %uB %ums\r\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000)); - } - imagesServed++; - if (autoLamp && (lampVal != -1)) setLamp(0); - return res; - } - - dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3); - if (!image_matrix) { - esp_camera_fb_return(fb); - Serial.println("CAPTURE: dl_matrix3du_alloc failed"); - httpd_resp_send_500(req); - if (autoLamp && (lampVal != -1)) setLamp(0); - return ESP_FAIL; + size_t fb_len = 0; + if(fb->format == PIXFORMAT_JPEG){ + fb_len = fb->len; + res = httpd_resp_send(req, (const char *)fb->buf, fb->len); + } else { + jpg_chunking_t jchunk = {req, 0}; + res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; + httpd_resp_send_chunk(req, NULL, 0); + fb_len = jchunk.len; } - - out_buf = image_matrix->item; - out_len = fb->width * fb->height * 3; - out_width = fb->width; - out_height = fb->height; - - s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf); esp_camera_fb_return(fb); - if(!s){ - dl_matrix3du_free(image_matrix); - Serial.println("CAPTURE: frame convert to rgb888 failed"); - httpd_resp_send_500(req); - if (autoLamp && (lampVal != -1)) setLamp(0); - return ESP_FAIL; - } - - box_array_t *net_boxes = face_detect(image_matrix, &mtmn_config); - - if (net_boxes){ - detected = true; - if(recognition_enabled){ - face_id = run_face_recognition(image_matrix, net_boxes); - } - draw_face_boxes(image_matrix, net_boxes, face_id); - dl_lib_free(net_boxes->score); - dl_lib_free(net_boxes->box); - dl_lib_free(net_boxes->landmark); - dl_lib_free(net_boxes); - } - - jpg_chunking_t jchunk = {req, 0}; - s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk); - dl_matrix3du_free(image_matrix); - if(!s){ - Serial.println("CAPTURE: JPEG compression failed"); - if (autoLamp && (lampVal != -1)) setLamp(0); - return ESP_FAIL; - } - int64_t fr_end = esp_timer_get_time(); if (debugData) { - Serial.printf("JPG: %uB %ums %s%d\r\n", (uint32_t)(jchunk.len), (uint32_t)((fr_end - fr_start)/1000), detected?"DETECTED ":"", face_id); + Serial.printf("JPG: %uB %ums\r\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000)); } - imagesServed++; if (autoLamp && (lampVal != -1)) setLamp(0); return res; @@ -431,14 +190,6 @@ static esp_err_t stream_handler(httpd_req_t *req){ size_t _jpg_buf_len = 0; uint8_t * _jpg_buf = NULL; char * part_buf[64]; - dl_matrix3du_t *image_matrix = NULL; - int face_id = 0; - bool detected = false; - int64_t fr_start = 0; - int64_t fr_face = 0; - int64_t fr_recognize = 0; - int64_t fr_encode = 0; - int64_t fr_ready = 0; Serial.println("Stream requested"); if (autoLamp && (lampVal != -1)) setLamp(lampVal); @@ -463,78 +214,22 @@ static esp_err_t stream_handler(httpd_req_t *req){ httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); while(true){ - detected = false; - face_id = 0; fb = esp_camera_fb_get(); if (!fb) { Serial.println("STREAM: failed to acquire frame"); res = ESP_FAIL; } else { - fr_start = esp_timer_get_time(); - fr_ready = fr_start; - fr_face = fr_start; - fr_encode = fr_start; - fr_recognize = fr_start; - - if(!detection_enabled || fb->width > 400){ - if(fb->format != PIXFORMAT_JPEG){ - bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); - esp_camera_fb_return(fb); - fb = NULL; - if(!jpeg_converted){ - Serial.println("STREAM: JPEG compression failed"); - res = ESP_FAIL; - } - } else { - _jpg_buf_len = fb->len; - _jpg_buf = fb->buf; - } - } else { - - image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3); - - if (!image_matrix) { - Serial.println("STREAM: dl_matrix3du_alloc failed"); + if(fb->format != PIXFORMAT_JPEG){ + bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + esp_camera_fb_return(fb); + fb = NULL; + if(!jpeg_converted){ + Serial.println("STREAM: JPEG compression failed"); res = ESP_FAIL; - } else { - if(!fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item)){ - Serial.println("STREAM: frame convert to rgb888 failed"); - res = ESP_FAIL; - } else { - fr_ready = esp_timer_get_time(); - box_array_t *net_boxes = NULL; - if(detection_enabled){ - net_boxes = face_detect(image_matrix, &mtmn_config); - } - fr_face = esp_timer_get_time(); - fr_recognize = fr_face; - if (net_boxes || fb->format != PIXFORMAT_JPEG){ - if(net_boxes){ - detected = true; - if(recognition_enabled){ - face_id = run_face_recognition(image_matrix, net_boxes); - } - fr_recognize = esp_timer_get_time(); - draw_face_boxes(image_matrix, net_boxes, face_id); - dl_lib_free(net_boxes->score); - dl_lib_free(net_boxes->box); - dl_lib_free(net_boxes->landmark); - dl_lib_free(net_boxes); - } - if(!fmt2jpg(image_matrix->item, fb->width*fb->height*3, fb->width, fb->height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len)){ - Serial.println("STREAM: fmt2jpg failed"); - res = ESP_FAIL; - } - esp_camera_fb_return(fb); - fb = NULL; - } else { - _jpg_buf = fb->buf; - _jpg_buf_len = fb->len; - } - fr_encode = esp_timer_get_time(); - } - dl_matrix3du_free(image_matrix); } + } else { + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; } } if(res == ESP_OK){ @@ -560,31 +255,13 @@ static esp_err_t stream_handler(httpd_req_t *req){ // We end the stream here only if a Hard failure has been encountered or the connection has been interrupted. break; } - - int64_t fr_end = esp_timer_get_time(); - int64_t ready_time = (fr_ready - fr_start)/1000; - int64_t face_time = (fr_face - fr_ready)/1000; - int64_t recognize_time = (fr_recognize - fr_face)/1000; - int64_t encode_time = (fr_encode - fr_recognize)/1000; - int64_t process_time = (fr_encode - fr_start)/1000; - int64_t frame_time = fr_end - last_frame; - last_frame = fr_end; + int64_t frame_time = esp_timer_get_time() - last_frame; + last_frame = esp_timer_get_time();; frame_time /= 1000; - uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time); if (debugData) { - if (detection_enabled) { - Serial.printf("MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps), %u+%u+%u+%u=%u %s%d\r\n", - (uint32_t)(_jpg_buf_len), - (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, - avg_frame_time, 1000.0 / avg_frame_time, - (uint32_t)ready_time, (uint32_t)face_time, (uint32_t)recognize_time, (uint32_t)encode_time, (uint32_t)process_time, - (detected)?"DETECTED ":"", face_id); - } else { - Serial.printf("MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps)\r\n", - (uint32_t)(_jpg_buf_len), - (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, - avg_frame_time, 1000.0 / avg_frame_time); - } + Serial.printf("MJPG: %uB %ums (%.1ffps)\r\n", + (uint32_t)(_jpg_buf_len), + (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time); } } @@ -659,19 +336,6 @@ static esp_err_t cmd_handler(httpd_req_t *req){ else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val); else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val); else if(!strcmp(variable, "rotate")) myRotation = val; - else if(!strcmp(variable, "face_detect")) { - detection_enabled = val; - if(!detection_enabled) { - recognition_enabled = 0; - } - } - else if(!strcmp(variable, "face_enroll")) is_enrolling = val; - else if(!strcmp(variable, "face_recognize")) { - recognition_enabled = val; - if(recognition_enabled){ - detection_enabled = val; - } - } else if(!strcmp(variable, "autolamp") && (lampVal != -1)) { autoLamp = val; if (autoLamp) { @@ -690,12 +354,6 @@ static esp_err_t cmd_handler(httpd_req_t *req){ setLamp(lampVal); } } - else if(!strcmp(variable, "save_face")) { - if (filesystem) saveFaceDB(SPIFFS); - } - else if(!strcmp(variable, "clear_face")) { - if (filesystem) removeFaceDB(SPIFFS); - } else if(!strcmp(variable, "save_prefs")) { if (filesystem) savePrefs(SPIFFS); } @@ -758,9 +416,6 @@ static esp_err_t status_handler(httpd_req_t *req){ p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror); p+=sprintf(p, "\"dcw\":%u,", s->status.dcw); p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar); - p+=sprintf(p, "\"face_detect\":%u,", detection_enabled); - p+=sprintf(p, "\"face_enroll\":%u,", is_enrolling); - p+=sprintf(p, "\"face_recognize\":%u,", recognition_enabled); p+=sprintf(p, "\"cam_name\":\"%s\",", myName); p+=sprintf(p, "\"code_ver\":\"%s\",", myVer); p+=sprintf(p, "\"rotate\":\"%d\",", myRotation); @@ -880,7 +535,6 @@ static esp_err_t dump_handler(httpd_req_t *req){ if (filesystem) { d+= sprintf(d,"Spiffs: %i, used: %i
\n", SPIFFS.totalBytes(), SPIFFS.usedBytes()); } - d+= sprintf(d,"Enrolled faces: %i (max %i)
\n", id_list.count, id_list.size); // Footer d+= sprintf(d,"
\n"); @@ -1093,29 +747,6 @@ void startCameraServer(int hPort, int sPort){ .user_ctx = NULL }; - // Filter list; used during face detection - ra_filter_init(&ra_filter, 20); - - // Mtmn config values (face detection and recognition parameters) - mtmn_config.type = FAST; - mtmn_config.min_face = 80; - mtmn_config.pyramid = 0.707; - mtmn_config.pyramid_times = 4; - mtmn_config.p_threshold.score = 0.6; - mtmn_config.p_threshold.nms = 0.7; - mtmn_config.p_threshold.candidate_number = 20; - mtmn_config.r_threshold.score = 0.7; - mtmn_config.r_threshold.nms = 0.7; - mtmn_config.r_threshold.candidate_number = 10; - mtmn_config.o_threshold.score = 0.7; - mtmn_config.o_threshold.nms = 0.7; - mtmn_config.o_threshold.candidate_number = 1; - - // Face ID list (settings + pointer to the data allocation) - 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; diff --git a/esp32-cam-webserver.ino b/esp32-cam-webserver.ino index 2f9ed1e..ec1805a 100644 --- a/esp32-cam-webserver.ino +++ b/esp32-cam-webserver.ino @@ -3,6 +3,7 @@ #include #include #include +#include #include "src/parsebytes.h" @@ -51,7 +52,7 @@ #include "camera_pins.h" // Internal filesystem (SPIFFS) -// used for non-volatile camera settings and face DB store +// used for non-volatile camera settings #include "storage.h" // Sketch Info @@ -163,28 +164,12 @@ const int pwmMax = pow(2,pwmresolution)-1; bool filesystem = true; #endif -#if defined(FACE_DETECTION) - int8_t detection_enabled = 1; - #if defined(FACE_RECOGNITION) - int8_t recognition_enabled = 1; - #else - int8_t recognition_enabled = 0; - #endif +#if defined(NO_OTA) + bool otaEnabled = false; #else - int8_t detection_enabled = 0; - int8_t recognition_enabled = 0; + bool otaEnabled = true; #endif -#if defined (GOOD_FACE_TEXT) - char knownFaceText[] = GOOD_FACE_TEXT; -#else - char knownFaceText[] ="Hello Subject "; -#endif -#if defined (BAD_FACE_TEXT) - char unknownFaceText[] = BAD_FACE_TEXT; -#else - char unknownFaceText[] = "Intruder Alert!"; -#endif // Critical error string; if set during init (camera hardware failure) it // will be returned for all http requests @@ -591,9 +576,8 @@ void setup() { if (filesystem) { filesystemStart(); loadPrefs(SPIFFS); - loadFaceDB(SPIFFS); } else { - Serial.println("No Internal Filesystem, cannot save preferences or face DB"); + Serial.println("No Internal Filesystem, cannot save preferences"); } } @@ -615,7 +599,48 @@ void setup() { while ((WiFi.status() != WL_CONNECTED) && !accesspoint) { WifiSetup(); delay(1000); - } + } + + if (otaEnabled) { + // Start OTA once connected + Serial.println("Setting up OTA"); + // Port defaults to 3232 + // ArduinoOTA.setPort(3232); + // Hostname defaults to esp3232-[MAC] + ArduinoOTA.setHostname(myName); + // No authentication by default + #if defined (OTA_PASSWORD) + ArduinoOTA.setPassword(OTA_PASSWORD); + Serial.printf("OTA Password: %s\n\r", OTA_PASSWORD); + #endif + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else // U_SPIFFS + type = "filesystem"; + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + Serial.println("Start updating " + type); + }) + .onEnd([]() { + Serial.println("\nEnd"); + }) + .onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }) + .onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + } else { + Serial.println("OTA is disabled"); + } // Now we have a network we can start the two http handlers for the UI and Stream. startCameraServer(httpPort, streamPort); @@ -647,6 +672,8 @@ void setup() { } else { Serial.printf("\r\nCamera unavailable due to initialisation errors.\r\n\r\n"); } + Serial.print("\r\nThis is the 4.0 alpha\r\n - Face detection has been removed!\r\n"); + // Used when dumping status; these are slow functions, so just do them once during startup sketchSize = ESP.getSketchSize(); @@ -669,6 +696,7 @@ void loop() { unsigned long start = millis(); while (millis() - start < WIFI_WATCHDOG ) { delay(100); + if (otaEnabled) ArduinoOTA.handle(); handleSerial(); if (captivePortal) dnsServer.processNextRequest(); } @@ -686,6 +714,7 @@ void loop() { unsigned long start = millis(); while (millis() - start < WIFI_WATCHDOG ) { delay(100); + if (otaEnabled) ArduinoOTA.handle(); handleSerial(); } } else { diff --git a/index_other.h b/index_other.h index 89cb1cc..71b6971 100644 --- a/index_other.h +++ b/index_other.h @@ -57,8 +57,6 @@ const uint8_t index_simple_html[] = R"=====( - -
@@ -270,10 +268,6 @@ const uint8_t index_simple_html[] = R"=====( framesize.onchange = () => { updateConfig(framesize) - if (framesize.value > 6) { - updateValue(detect, false) - updateValue(recognize, false) - } } swapButton.onclick = () => { diff --git a/index_ov2640.h b/index_ov2640.h index 1c457bc..00724cf 100644 --- a/index_ov2640.h +++ b/index_ov2640.h @@ -237,28 +237,6 @@ const uint8_t index_ov2640_html[] = R"=====( -
- -
- - -
-
-
- -
- - -
-
-
- - - -
@@ -311,15 +289,10 @@ const uint8_t index_ov2640_html[] = R"=====( const viewContainer = document.getElementById('stream-container') const stillButton = document.getElementById('get-still') const streamButton = document.getElementById('toggle-stream') - const enrollButton = document.getElementById('face_enroll') const closeButton = document.getElementById('close-stream') const streamLink = document.getElementById('stream_link') - const detect = document.getElementById('face_detect') - const recognize = document.getElementById('face_recognize') const framesize = document.getElementById('framesize') const swapButton = document.getElementById('swap-viewer') - // const saveFaceButton = document.getElementById('save_face') - // const clearFaceButton = document.getElementById('clear_face') const savePrefsButton = document.getElementById('save_prefs') const clearPrefsButton = document.getElementById('clear_prefs') const rebootButton = document.getElementById('reboot') @@ -368,8 +341,6 @@ const uint8_t index_ov2640_html[] = R"=====( } } else if(el.id === "awb_gain"){ value ? show(wb) : hide(wb) - } else if(el.id === "face_recognize"){ - value ? enable(enrollButton) : disable(enrollButton) } else if(el.id === "lamp"){ if (value == -1) { hide(lampGroup) @@ -521,10 +492,6 @@ const uint8_t index_ov2640_html[] = R"=====( } } - enrollButton.onclick = () => { - updateConfig(enrollButton); - } - // Attach default on change action document .querySelectorAll('.default-action') @@ -572,56 +539,12 @@ const uint8_t index_ov2640_html[] = R"=====( framesize.onchange = () => { updateConfig(framesize) - if (framesize.value > 6) { - updateValue(detect, false) - updateValue(recognize, false) - } - } - - detect.onchange = () => { - if (framesize.value > 6) { - alert("Please select CIF or lower resolution before enabling this feature!"); - updateValue(detect, false) - return; - } - updateConfig(detect) - if (!detect.checked) { - disable(enrollButton) - updateValue(recognize, false) - } - } - - recognize.onchange = () => { - if (framesize.value > 6) { - alert("Please select CIF or lower resolution before enabling this feature!"); - updateValue(recognize, false) - return; - } - updateConfig(recognize) - if (recognize.checked) { - enable(enrollButton) - updateValue(detect, true) - } else { - disable(enrollButton) - } } swapButton.onclick = () => { window.open('/?view=simple','_self'); } -// saveFaceButton.onclick = () => { -// if (confirm("Saving the current face database?")) { -// updateConfig(saveFaceButton); -// } -// } - -// clearFaceButton.onclick = () => { -// if (confirm("Removing the face database?")) { -// updateConfig(clearFaceButton); -// } -// } - savePrefsButton.onclick = () => { if (confirm("Save the current preferences?")) { updateConfig(savePrefsButton); diff --git a/index_ov3660.h b/index_ov3660.h index 8b4fa05..ff4b88d 100644 --- a/index_ov3660.h +++ b/index_ov3660.h @@ -251,28 +251,6 @@ const uint8_t index_ov3660_html[] = R"=====(
-
- -
- - -
-
-
- -
- - -
-
-
- - - -
@@ -312,6 +290,7 @@ const uint8_t index_ov3660_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') @@ -324,15 +303,10 @@ const uint8_t index_ov3660_html[] = R"=====( const viewContainer = document.getElementById('stream-container') const stillButton = document.getElementById('get-still') const streamButton = document.getElementById('toggle-stream') - const enrollButton = document.getElementById('face_enroll') const closeButton = document.getElementById('close-stream') const streamLink = document.getElementById('stream_link') - const detect = document.getElementById('face_detect') - const recognize = document.getElementById('face_recognize') const framesize = document.getElementById('framesize') const swapButton = document.getElementById('swap-viewer') - // const saveFaceButton = document.getElementById('save_face') - // const clearFaceButton = document.getElementById('clear_face') const savePrefsButton = document.getElementById('save_prefs') const clearPrefsButton = document.getElementById('clear_prefs') const rebootButton = document.getElementById('reboot') @@ -379,8 +353,6 @@ const uint8_t index_ov3660_html[] = R"=====( } } else if(el.id === "awb_gain"){ value ? show(wb) : hide(wb) - } else if(el.id === "face_recognize"){ - value ? enable(enrollButton) : disable(enrollButton) } else if(el.id === "lamp"){ if (value == -1) { hide(lampGroup) @@ -532,10 +504,6 @@ const uint8_t index_ov3660_html[] = R"=====( } } - enrollButton.onclick = () => { - updateConfig(enrollButton); - } - // Attach default on change action document .querySelectorAll('.default-action') @@ -580,56 +548,12 @@ const uint8_t index_ov3660_html[] = R"=====( framesize.onchange = () => { updateConfig(framesize) - if (framesize.value > 6) { - updateValue(detect, false) - updateValue(recognize, false) - } - } - - detect.onchange = () => { - if (framesize.value > 6) { - alert("Please select CIF or lower resolution before enabling this feature!"); - updateValue(detect, false) - return; - } - updateConfig(detect) - if (!detect.checked) { - disable(enrollButton) - updateValue(recognize, false) - } - } - - recognize.onchange = () => { - if (framesize.value > 6) { - alert("Please select CIF or lower resolution before enabling this feature!"); - updateValue(recognize, false) - return; - } - updateConfig(recognize) - if (recognize.checked) { - enable(enrollButton) - updateValue(detect, true) - } else { - disable(enrollButton) - } } swapButton.onclick = () => { window.open('/?view=simple','_self'); } -// saveFaceButton.onclick = () => { -// if (confirm("Saving the current face database?")) { -// updateConfig(saveFaceButton); -// } -// } - -// clearFaceButton.onclick = () => { -// if (confirm("Removing the face database?")) { -// updateConfig(clearFaceButton); -// } -// } - savePrefsButton.onclick = () => { if (confirm("Save the current preferences?")) { updateConfig(savePrefsButton); @@ -646,7 +570,12 @@ const uint8_t index_ov3660_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); } } diff --git a/myconfig.sample.h b/myconfig.sample.h index 30a7cd3..d979764 100644 --- a/myconfig.sample.h +++ b/myconfig.sample.h @@ -103,6 +103,16 @@ struct station stationList[] = {{"ssid1", "pass1", true}, */ // #define WIFI_WATCHDOG 5000 +/* + * Over The Air firmware updates can be disabled by uncommenting the folowing line + */ +// #define NO_OTA + +/* + * OTA can be password protected to prevent the device being hijacked + */ +// #define OTA_PASSWORD "SuperVisor" + /* * Camera Defaults * @@ -138,18 +148,6 @@ struct station stationList[] = {{"ssid1", "pass1", true}, // Uncomment to disable this this, the controls will still be shown in the UI but are inoperative. // #define NO_FS -// Uncomment to enable Face Detection (+ Recognition if desired) by default -// Note: 1) You must set DEFAULT_RESOLUTION (above) to FRAMESIZE_CIF or lower before enabling this -// 2) Face recognition enrolements are lost between reboots. -// #define FACE_DETECTION -// #define FACE_RECOGNITION - -// Uncomment and edit the following to change the on screen labels for known and unknown faces -// The 'good' text will have the subject ID number appended. Maximum 20 characters! -// #define GOOD_FACE_TEXT "Hello Subject " -// #define BAD_FACE_TEXT "Intruder Alert!" - - // Uncomment to enable camera debug info on serial by default // #define DEBUG_DEFAULT_ON diff --git a/platformio.ini b/platformio.ini index 74a34ef..d8f71ba 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,4 +14,5 @@ src_dir = ./ [env:esp32cam] platform = espressif32 board = esp32cam +board_build.partitions = default.csv framework = arduino diff --git a/src/version.h b/src/version.h index eb1c845..15d6631 100644 --- a/src/version.h +++ b/src/version.h @@ -1,3 +1,4 @@ /* Version of upstream code */ -char baseVersion[] = "3.2.1"; +char baseVersion[] = "4.0.alpha"; + diff --git a/storage.cpp b/storage.cpp index 8061427..61a245d 100644 --- a/storage.cpp +++ b/storage.cpp @@ -7,8 +7,6 @@ extern void flashLED(int flashtime); extern int myRotation; // Rotation extern int lampVal; // The current Lamp value extern int autoLamp; // Automatic lamp mode -extern int8_t detection_enabled; // Face detection enable -extern int8_t recognition_enabled; // Face recognition enable /* * Useful utility when debugging... @@ -102,8 +100,6 @@ void loadPrefs(fs::FS &fs){ s->set_hmirror(s, jsonExtract(prefs, "hmirror").toInt()); s->set_dcw(s, jsonExtract(prefs, "dcw").toInt()); s->set_colorbar(s, jsonExtract(prefs, "colorbar").toInt()); - detection_enabled = jsonExtract(prefs, "face_detect").toInt(); - recognition_enabled = jsonExtract(prefs, "face_recognize").toInt(); myRotation = jsonExtract(prefs, "rotate").toInt(); // close the file file.close(); @@ -150,8 +146,6 @@ void savePrefs(fs::FS &fs){ p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror); p+=sprintf(p, "\"dcw\":%u,", s->status.dcw); p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar); - p+=sprintf(p, "\"face_detect\":%u,", detection_enabled); - p+=sprintf(p, "\"face_recognize\":%u,", recognition_enabled); p+=sprintf(p, "\"rotate\":\"%d\"", myRotation); *p++ = '}'; *p++ = 0; @@ -171,19 +165,6 @@ void removePrefs(fs::FS &fs) { } } -void saveFaceDB(fs::FS &fs) { - // Stub! - return; -} -void loadFaceDB(fs::FS &fs) { - // Stub! - return; -} -void removeFaceDB(fs::FS &fs) { - // Stub! - return; -} - void filesystemStart(){ while ( !SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED) ) { // if we sit in this loop something is wrong; diff --git a/storage.h b/storage.h index e5ae34c..5f88246 100644 --- a/storage.h +++ b/storage.h @@ -4,14 +4,10 @@ #define FORMAT_SPIFFS_IF_FAILED true #define PREFERENCES_FILE "/esp32cam-preferences.json" -#define FACE_DB_FILE "/esp32cam-facedb" extern void dumpPrefs(fs::FS &fs); extern void loadPrefs(fs::FS &fs); extern void removePrefs(fs::FS &fs); extern void savePrefs(fs::FS &fs); -extern void loadFaceDB(fs::FS &fs); -extern void removeFaceDB(fs::FS &fs); -extern void saveFaceDB(fs::FS &fs); extern void filesystemStart();