Skip to content

Commit cfe81b0

Browse files
authored
Merge branch '4-0-rc1' into framerate_limit
2 parents 20f4c24 + d064e88 commit cfe81b0

15 files changed

+300
-185
lines changed

.travis.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ branches:
1515
before_script:
1616
- "export DISPLAY=:99.0"
1717
- sleep 3 # give xvfb some time to start
18-
- wget https://downloads.arduino.cc/arduino-1.8.18-linux64.tar.xz
19-
- tar xf arduino-1.8.18-linux64.tar.xz
20-
- mv arduino-1.8.18 $HOME/arduino_ide
18+
- wget https://downloads.arduino.cc/arduino-1.8.19-linux64.tar.xz
19+
- tar xf arduino-1.8.19-linux64.tar.xz
20+
- mv arduino-1.8.19 $HOME/arduino_ide
2121
- cd $HOME/arduino_ide/hardware
2222
- mkdir esp32
2323
- cd esp32
24-
- wget https://github.com/espressif/arduino-esp32/archive/refs/tags/2.0.1.tar.gz
25-
- tar -xzf 2.0.1.tar.gz
26-
- mv arduino-esp32-2.0.1/ esp32
24+
- wget https://github.com/espressif/arduino-esp32/archive/refs/tags/2.0.2.tar.gz
25+
- tar -xzf 2.0.2.tar.gz
26+
- mv arduino-esp32-2.0.2/ esp32
2727
- cd esp32/tools
2828
- python --version
2929
- python get.py

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Pull requests are the best way to propose changes to the codebase (I use [Github
1616
1. Fork the repo and create your branch from `master`.
1717
2. Give your branch a clear descriptive name and do your changes there.
1818
3. If you've changed the HTTP APIs, update the documentation.
19-
4. Issue a pull request against a branch *of the same name* in the main repo.
19+
4. Issue a pull request against the master branch in the main repo.
2020
5. Clearly describe your changes and the reason for them in the pull request.
2121

2222
## Any contributions you make will be under the GNU Lesser General Public License v2.1

README.md

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ But expanded with:
1313
* Over The Air firmware updates
1414
* Lots of minor fixes and tweaks, documentation etc.
1515

16-
And 'reduced' by removing the Face Recognition features
16+
And 'reduced' by removing the Face Recognition features
1717
* **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 receiving any further development.
1818
* 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.
1919
* There are other (specialised) sketches for the ESP-CAM that do use face recognitioni more effectively, if this is your thing :-)
@@ -33,7 +33,7 @@ I have four [AI-THINKER ESP32-CAM](https://github.com/raphaelbs/esp32-cam-ai-thi
3333
https://github.com/raphaelbs/esp32-cam-ai-thinker
3434
* The AI thinker wiki can be quite informative, when run through an online translator and read sensibly:
3535
https://wiki.ai-thinker.com/esp32-cam
36-
* Default pinouts are also included for WRover Kit, ESP Eye and M5Stack esp32 camera modules.
36+
* Default pinouts are also included for WRover Kit, ESP Eye and M5Stack esp32 camera modules.
3737
I do not have any of these boards, so they are untested by me. Please [let me know](https://github.com/easytarget/esp32-cam-webserver/issues) if you find issues or have a board not [in the list](./camera_pins.h).
3838

3939
## Troubleshooting:
@@ -48,7 +48,11 @@ The ESP itself is susceptible to the usual list of WiFi problems, not helped by
4848
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.
4949
* You can check the `/dump` page of the cam to see if it currently reports the camera as streaming or not.
5050

51-
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.
51+
A lot of common issues with this sketch are discussed and covered in the discussion forums:
52+
53+
https://github.com/easytarget/esp32-cam-webserver/discussions/categories/common-issues
54+
55+
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 or in the forums.
5256

5357
Note that I do not respond to any Private Messages (via github, hackaday, or wherever) for support.
5458

@@ -74,7 +78,7 @@ Is pretty simple, You just need jumper wires, no soldering really required, see
7478
Download the latest release of the sketch from https://github.com/easytarget/esp32-cam-webserver/releases/latest
7579
- You can get the latest stable development release by cloning / downloading the `master` branch of the repo.
7680

77-
This will give you an archive file with the Version number in it, eg.`esp32-cam-webserver-3.0.zip`. Tou need to unpack this into your Arduino sketch folder, and then you need to rename the folder you just extracted to remove the version number, eg.`esp32-cam-webserver-3.0` becomes `esp32-cam-webserver`.
81+
This will give you an archive file with the Version number in it, eg.`esp32-cam-webserver-4.0.zip`. You need to unpack this into your Arduino sketch folder, and then you need to **rename the folder you extracted to remove the version number**, eg.`esp32-cam-webserver-4.0` becomes `esp32-cam-webserver`.
7882

7983
Once you have done that you can open the sketch in the IDE by going to the `esp32-cam-webserver` sketch folder and selecting `esp32-cam-webserver.ino`.
8084

@@ -84,7 +88,7 @@ By default the sketch assumes you have an AI-THINKER board, it creates an Access
8488

8589
To make a permanent config with your home wifi settings, different defaults or a different board; copy (or rename) the file `myconfig.sample.h` in the sketch folder to `myconfig.h` and edit that, all the usable defaults are in that file. Because this is your private copy of the config it will not get overwritten if you update the main sketch!
8690

87-
### Programming
91+
### Programming
8892

8993
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.
9094
![IDE board config](Docs/ota-board-selection.png)
@@ -107,7 +111,7 @@ Go to the URL given in the serial output, the web UI should appear with the sett
107111

108112
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.
109113

110-
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.
114+
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.
111115

112116
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.
113117

@@ -141,17 +145,11 @@ Contributions are welcome; please see the [Contribution guidelines](CONTRIBUTING
141145

142146
Time allowing; my Current plan is:
143147

144-
V4
145-
* Remove face recognition entirely;
146-
* **Done**, see the `NoFace` branch :sunglasses:
147-
* Not optional, this is a code and maintenance nightmare. V3 can be maintained on a branch for those who need it.
148+
V4
148149
* Investigate using SD card to capture images
149-
* Implement OTA and a better network stack for remembering multiple AP's, auto-config etc.
150-
* **Basic OTA is Done**, see the `NoFace` branch.
150+
* Implement a better network stack for remembering multiple AP's, auto-config etc.
151151
* Advanced (web upload) OTA might be nice to have if possible
152-
* For the Network setup I want to implement https://github.com/Hieromon/AutoConnect
153152
* UI Skinning/Theming
154153
* OSD
155154
* Temperature/humidity/pressure sensor support (bme20,dht11)
156155
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 thoughts you may have there.
157-

app_httpd.cpp

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ extern int sketchSpace;
6464
extern String sketchMD5;
6565
extern bool otaEnabled;
6666
extern char otaPassword[];
67+
extern unsigned long xclk;
6768

6869
typedef struct {
6970
httpd_req_t *req;
@@ -78,6 +79,9 @@ static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %
7879
httpd_handle_t stream_httpd = NULL;
7980
httpd_handle_t camera_httpd = NULL;
8081

82+
// Flag that can be set to kill all active streams
83+
bool streamKill;
84+
8185
#ifdef __cplusplus
8286
extern "C" {
8387
#endif
@@ -143,7 +147,7 @@ void serialDump() {
143147
int McuTf = temprature_sens_read(); // fahrenheit
144148
Serial.printf("System up: %" PRId64 ":%02i:%02i:%02i (d:h:m:s)\r\n", upDays, upHours, upMin, upSec);
145149
Serial.printf("Active streams: %i, Previous streams: %lu, Images captured: %lu\r\n", streamCount, streamsServed, imagesServed);
146-
Serial.printf("Freq: %i MHz\r\n", ESP.getCpuFreqMHz());
150+
Serial.printf("CPU Freq: %i MHz, Xclk Freq: %i MHz\r\n", ESP.getCpuFreqMHz(), xclk);
147151
Serial.printf("MCU temperature : %i C, %i F (approximate)\r\n", McuTc, McuTf);
148152
Serial.printf("Heap: %i, free: %i, min free: %i, max block: %i\r\n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
149153
if(psramFound()) {
@@ -173,7 +177,10 @@ static esp_err_t capture_handler(httpd_req_t *req){
173177
esp_err_t res = ESP_OK;
174178

175179
Serial.println("Capture Requested");
176-
if (autoLamp && (lampVal != -1)) setLamp(lampVal);
180+
if (autoLamp && (lampVal != -1)) {
181+
setLamp(lampVal);
182+
delay(75); // coupled with the status led flash this gives ~150ms for lamp to settle.
183+
}
177184
flashLED(75); // little flash of status LED
178185

179186
int64_t fr_start = esp_timer_get_time();
@@ -199,12 +206,16 @@ static esp_err_t capture_handler(httpd_req_t *req){
199206
Serial.println("Capture Error: Non-JPEG image returned by camera module");
200207
}
201208
esp_camera_fb_return(fb);
209+
fb = NULL;
210+
202211
int64_t fr_end = esp_timer_get_time();
203212
if (debugData) {
204213
Serial.printf("JPG: %uB %ums\r\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));
205214
}
206215
imagesServed++;
207-
if (autoLamp && (lampVal != -1)) setLamp(0);
216+
if (autoLamp && (lampVal != -1)) {
217+
setLamp(0);
218+
}
208219
return res;
209220
}
210221

@@ -215,6 +226,8 @@ static esp_err_t stream_handler(httpd_req_t *req){
215226
uint8_t * _jpg_buf = NULL;
216227
char * part_buf[64];
217228

229+
streamKill = false;
230+
218231
Serial.println("Stream requested");
219232
if (autoLamp && (lampVal != -1)) setLamp(lampVal);
220233
streamCount = 1; // at present we only have one stream handler, so values are 0 or 1..
@@ -273,7 +286,7 @@ static esp_err_t stream_handler(httpd_req_t *req){
273286
free(_jpg_buf);
274287
_jpg_buf = NULL;
275288
}
276-
if(res != ESP_OK){
289+
if((res != ESP_OK) || streamKill){
277290
// This is the only exit point from the stream loop.
278291
// We end the stream here only if a Hard failure has been encountered or the connection has been interrupted.
279292
break;
@@ -340,6 +353,7 @@ static esp_err_t cmd_handler(httpd_req_t *req){
340353
if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val);
341354
}
342355
else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
356+
else if(!strcmp(variable, "xclk")) { xclk = val; res = s->set_xclk(s, LEDC_TIMER_0, val); }
343357
else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
344358
else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
345359
else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
@@ -389,6 +403,7 @@ static esp_err_t cmd_handler(httpd_req_t *req){
389403
if (filesystem) removePrefs(SPIFFS);
390404
}
391405
else if(!strcmp(variable, "reboot")) {
406+
if (lampVal != -1) setLamp(0); // kill the lamp; otherwise it can remain on during the soft-reboot
392407
esp_task_wdt_init(3,true); // schedule a a watchdog panic event for 3 seconds in the future
393408
esp_task_wdt_add(NULL);
394409
periph_module_disable(PERIPH_I2C0_MODULE); // try to shut I2C down properly
@@ -422,6 +437,7 @@ static esp_err_t status_handler(httpd_req_t *req){
422437
p+=sprintf(p, "\"min_frame_time\":%d,", minFrameTime);
423438
p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
424439
p+=sprintf(p, "\"quality\":%u,", s->status.quality);
440+
p+=sprintf(p, "\"xclk\":%u,", xclk);
425441
p+=sprintf(p, "\"brightness\":%d,", s->status.brightness);
426442
p+=sprintf(p, "\"contrast\":%d,", s->status.contrast);
427443
p+=sprintf(p, "\"saturation\":%d,", s->status.saturation);
@@ -496,7 +512,7 @@ static esp_err_t logo_svg_handler(httpd_req_t *req){
496512

497513
static esp_err_t dump_handler(httpd_req_t *req){
498514
flashLED(75);
499-
Serial.println("\r\nDump Requested via Web");
515+
Serial.println("\r\nDump requested via Web");
500516
serialDump();
501517
static char dumpOut[2000] = "";
502518
char * d = dumpOut;
@@ -509,7 +525,7 @@ static esp_err_t dump_handler(httpd_req_t *req){
509525
d+= sprintf(d,"<link rel=\"stylesheet\" type=\"text/css\" href=\"/style.css\">\n");
510526
d+= sprintf(d,"</head>\n");
511527
d+= sprintf(d,"<body>\n");
512-
d+= sprintf(d,"<img src=\"/logo.svg\" style=\"position: relative; float: right;\">\n");
528+
d+= sprintf(d,"<img src=\"/logo.svg\" style=\"position: relative; float: right;\">\n");
513529
if (critERR.length() > 0) {
514530
d+= sprintf(d,"<span style=\"color:red;\">%s<hr></span>\n", critERR.c_str());
515531
d+= sprintf(d,"<h2 style=\"color:red;\">(the serial log may give more information)</h2><br>\n");
@@ -570,7 +586,7 @@ static esp_err_t dump_handler(httpd_req_t *req){
570586

571587
d+= sprintf(d,"Up: %" PRId64 ":%02i:%02i:%02i (d:h:m:s)<br>\n", upDays, upHours, upMin, upSec);
572588
d+= sprintf(d,"Active streams: %i, Previous streams: %lu, Images captured: %lu<br>\n", streamCount, streamsServed, imagesServed);
573-
d+= sprintf(d,"Freq: %i MHz<br>\n", ESP.getCpuFreqMHz());
589+
d+= sprintf(d,"CPU Freq: %i MHz, Xclk Freq: %i MHz<br>\n", ESP.getCpuFreqMHz(), xclk);
574590
d+= sprintf(d,"<span title=\"NOTE: Internal temperature sensor readings can be innacurate on the ESP32-c1 chipset, and may vary significantly between devices!\">");
575591
d+= sprintf(d,"MCU temperature : %i &deg;C, %i &deg;F</span>\n<br>", McuTc, McuTf);
576592
d+= sprintf(d,"Heap: %i, free: %i, min free: %i, max block: %i<br>\n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
@@ -590,6 +606,7 @@ static esp_err_t dump_handler(httpd_req_t *req){
590606
// Footer
591607
d+= sprintf(d,"<br><div class=\"input-group\">\n");
592608
d+= sprintf(d,"<button title=\"Instant Refresh; the page reloads every minute anyway\" onclick=\"location.replace(document.URL)\">Refresh</button>\n");
609+
d+= sprintf(d,"<button title=\"Stop any active streams\" onclick=\"let throwaway = fetch('stop');setTimeout(function(){\nlocation.replace(document.URL);\n}, 200);\">Stop Stream</button>\n");
593610
d+= sprintf(d,"<button title=\"Close this page\" onclick=\"javascript:window.close()\">Close</button>\n");
594611
d+= sprintf(d,"</div>\n</body>\n");
595612
// A javascript timer to refresh the page every minute.
@@ -601,6 +618,15 @@ static esp_err_t dump_handler(httpd_req_t *req){
601618
return httpd_resp_send(req, dumpOut, strlen(dumpOut));
602619
}
603620

621+
static esp_err_t stop_handler(httpd_req_t *req){
622+
flashLED(75);
623+
Serial.println("\r\nStream stop requested via Web");
624+
streamKill = true;
625+
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
626+
return httpd_resp_send(req, NULL, 0);
627+
}
628+
629+
604630
static esp_err_t style_handler(httpd_req_t *req){
605631
httpd_resp_set_type(req, "text/css");
606632
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
@@ -609,15 +635,15 @@ static esp_err_t style_handler(httpd_req_t *req){
609635

610636
static esp_err_t streamviewer_handler(httpd_req_t *req){
611637
flashLED(75);
612-
Serial.println("Stream Viewer requested");
638+
Serial.println("Stream viewer requested");
613639
httpd_resp_set_type(req, "text/html");
614640
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
615641
return httpd_resp_send(req, (const char *)streamviewer_html, streamviewer_html_len);
616642
}
617643

618644
static esp_err_t error_handler(httpd_req_t *req){
619645
flashLED(75);
620-
Serial.println("Sending Error page");
646+
Serial.println("Sending error page");
621647
std::string s(error_html);
622648
size_t index;
623649
while ((index = s.find("<APPURL>")) != std::string::npos)
@@ -705,7 +731,7 @@ static esp_err_t index_handler(httpd_req_t *req){
705731

706732
void startCameraServer(int hPort, int sPort){
707733
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
708-
config.max_uri_handlers = 12; // we use more than the default 8 (on port 80)
734+
config.max_uri_handlers = 16; // we use more than the default 8 (on port 80)
709735

710736
httpd_uri_t index_uri = {
711737
.uri = "/",
@@ -767,6 +793,12 @@ void startCameraServer(int hPort, int sPort){
767793
.handler = dump_handler,
768794
.user_ctx = NULL
769795
};
796+
httpd_uri_t stop_uri = {
797+
.uri = "/stop",
798+
.method = HTTP_GET,
799+
.handler = stop_handler,
800+
.user_ctx = NULL
801+
};
770802
httpd_uri_t stream_uri = {
771803
.uri = "/",
772804
.method = HTTP_GET,
@@ -817,6 +849,7 @@ void startCameraServer(int hPort, int sPort){
817849
httpd_register_uri_handler(camera_httpd, &favicon_ico_uri);
818850
httpd_register_uri_handler(camera_httpd, &logo_svg_uri);
819851
httpd_register_uri_handler(camera_httpd, &dump_uri);
852+
httpd_register_uri_handler(camera_httpd, &stop_uri);
820853
}
821854

822855
config.server_port = sPort;

0 commit comments

Comments
 (0)