Skip to content

Commit 9c96ded

Browse files
authored
Merge pull request #229 from easytarget/4-0-rc1
4.0 release candidate
2 parents ed71e54 + 44042e0 commit 9c96ded

File tree

14 files changed

+447
-302
lines changed

14 files changed

+447
-302
lines changed

API.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The WebUI and camera server communicate entirely via HTTP requests and responses
1111
* `/status` - Returns a JSON string with all camera status <key>/<value> pairs listed
1212
* `/control?var=<key>&val=<val>` - Set `<key>` to `<val>`
1313
* `/dump` - Status page
14+
* `/stop` - End all active streams
1415

1516
### Stream Port
1617
* `/` - Raw stream
@@ -26,6 +27,7 @@ Call `/control?var=<key>&val=<val>` with a settings key and value to set camera
2627
```
2728
lamp - Lamp value in percent; integer, 0 - 100 (-1 = disabled)
2829
framesize - See below
30+
min_frame_time - Minimal frame duration in ms; used to limit max FPS. Must be positive integer
2931
quality - 10 to 63 (ov3660: 4 to 10)
3032
contrast - -2 to 2 (ov3660: -3 to 3)
3133
brightness - -2 to 2 (ov3660: -3 to 3)
@@ -101,7 +103,7 @@ reboot - Reboots the camera
101103
* All settings are returned via single `status` call in [JSON](https://www.json.org/) format.
102104
* `http://<IP-ADDRESS>/status`
103105
* Returns:
104-
``` {"lamp":0,"autolamp":0,"framesize":10,"quality":10,"brightness":0,"contrast":0,"saturation":0,"sharpness":0,"special_effect":0,"wb_mode":0,"awb":1,"awb_gain":1,"aec":1,"aec2":0,"ae_level":0,"aec_value":168,"agc":1,"agc_gain":0,"gainceiling":0,"bpc":0,"wpc":1,"raw_gma":1,"lenc":1,"vflip":0,"hmirror":0,"dcw":1,"colorbar":0,"face_detect":0,"face_enroll":0,"face_recognize":0,"cam_name":"General","code_ver":"Mar 6 2021 @ 17:54:00","rotate":"0","stream_url":"http://10.0.0.190:81/"}```
106+
``` {"lamp":0,"autolamp":0,"min_frame_time":0,"framesize":9,"quality":10,"xclk":8,"brightness":0,"contrast":0,"saturation":0,"sharpness":0,"special_effect":0,"wb_mode":0,"awb":1,"awb_gain":1,"aec":1,"aec2":0,"ae_level":0,"aec_value":204,"agc":1,"agc_gain":0,"gainceiling":0,"bpc":0,"wpc":1,"raw_gma":1,"lenc":1,"vflip":1,"hmirror":1,"dcw":1,"colorbar":0,"cam_name":"ESP32 test camera","code_ver":"Mar 10 2022 @ 14:00:45","rotate":"0","stream_url":"http://10.0.0.181:81/"}```
105107
* Reboot the camera
106108
* `http://<IP-ADDRESS>/control?var=reboot&val=0`
107109

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: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,21 @@ https://wiki.ai-thinker.com/esp32-cam
3838

3939
## Troubleshooting:
4040

41-
Please read this excellent guide for help with some common issues seen with the camera modules:
41+
A lot of common issues with this sketch are discussed and covered in the discussion forums:
42+
43+
https://github.com/easytarget/esp32-cam-webserver/discussions/categories/common-issues
44+
45+
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.
46+
47+
There is also this excellent guide for help with some common issues seen with the camera modules:
4248
https://randomnerdtutorials.com/esp32-cam-troubleshooting-guide/
4349

4450
### Known Issues
4551

46-
The ESP itself is susceptible to the usual list of WiFi problems, not helped by having small antennas, older designs, congested airwaves and demanding users. The majority of disconnects, stutters and other comms problems are simply due to 'WiFi issues'. The AI-THINKER camera module & esp32 combination is quite susceptible to power supply problems affecting both WiFi conctivity and Video quality; short cabling and decent power supplies are your friend here; also well cooled cases and, if you have the time, decoupling capacitors on the power lines.
52+
The ESP32 itself is susceptible to the usual list of WiFi problems, not helped by having small antennas, older designs, congested airwaves and demanding users. The majority of disconnects, stutters and other comms problems are simply due to 'WiFi issues'. The AI-THINKER camera module & esp32 combination is quite susceptible to power supply problems affecting both WiFi conctivity and Video quality; short cabling and decent power supplies are your friend here; also well cooled cases and, if you have the time, decoupling capacitors on the power lines.
4753

4854
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.
49-
* You can check the `/dump` page of the cam to see if it currently reports the camera as streaming or not.
50-
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.
55+
* If you cannot start the stream you can check the `/dump` page of the cam to see if it currently reports the camera as streaming or not.
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

@@ -142,14 +146,9 @@ Contributions are welcome; please see the [Contribution guidelines](CONTRIBUTING
142146
Time allowing; my Current plan is:
143147

144148
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.
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)

app_httpd.cpp

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ extern int8_t streamCount;
5252
extern unsigned long streamsServed;
5353
extern unsigned long imagesServed;
5454
extern int myRotation;
55+
extern int minFrameTime;
5556
extern int lampVal;
5657
extern bool autoLamp;
5758
extern bool filesystem;
@@ -63,7 +64,7 @@ extern int sketchSpace;
6364
extern String sketchMD5;
6465
extern bool otaEnabled;
6566
extern char otaPassword[];
66-
extern unsigned long xclkFreqHz;
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
@@ -141,10 +145,9 @@ void serialDump() {
141145
int upSec = sec % 60;
142146
int McuTc = (temprature_sens_read() - 32) / 1.8; // celsius
143147
int McuTf = temprature_sens_read(); // fahrenheit
144-
float xclk = xclkFreqHz/1000000;
145148
Serial.printf("System up: %" PRId64 ":%02i:%02i:%02i (d:h:m:s)\r\n", upDays, upHours, upMin, upSec);
146149
Serial.printf("Active streams: %i, Previous streams: %lu, Images captured: %lu\r\n", streamCount, streamsServed, imagesServed);
147-
Serial.printf("CPU Freq: %i MHz, Xclk Freq: %.1f MHz\r\n", ESP.getCpuFreqMHz(), xclk);
150+
Serial.printf("CPU Freq: %i MHz, Xclk Freq: %i MHz\r\n", ESP.getCpuFreqMHz(), xclk);
148151
Serial.printf("MCU temperature : %i C, %i F (approximate)\r\n", McuTc, McuTf);
149152
Serial.printf("Heap: %i, free: %i, min free: %i, max block: %i\r\n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
150153
if(psramFound()) {
@@ -223,6 +226,8 @@ static esp_err_t stream_handler(httpd_req_t *req){
223226
uint8_t * _jpg_buf = NULL;
224227
char * part_buf[64];
225228

229+
streamKill = false;
230+
226231
Serial.println("Stream requested");
227232
if (autoLamp && (lampVal != -1)) setLamp(lampVal);
228233
streamCount = 1; // at present we only have one stream handler, so values are 0 or 1..
@@ -245,6 +250,10 @@ static esp_err_t stream_handler(httpd_req_t *req){
245250

246251
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
247252

253+
if(res == ESP_OK){
254+
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
255+
}
256+
248257
while(true){
249258
fb = esp_camera_fb_get();
250259
if (!fb) {
@@ -259,16 +268,16 @@ static esp_err_t stream_handler(httpd_req_t *req){
259268
_jpg_buf = fb->buf;
260269
}
261270
}
262-
if(res == ESP_OK){
263-
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
264-
}
265271
if(res == ESP_OK){
266272
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
267273
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
268274
}
269275
if(res == ESP_OK){
270276
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
271277
}
278+
if(res == ESP_OK){
279+
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
280+
}
272281
if(fb){
273282
esp_camera_fb_return(fb);
274283
fb = NULL;
@@ -277,19 +286,22 @@ static esp_err_t stream_handler(httpd_req_t *req){
277286
free(_jpg_buf);
278287
_jpg_buf = NULL;
279288
}
280-
if(res != ESP_OK){
289+
if((res != ESP_OK) || streamKill){
281290
// This is the only exit point from the stream loop.
282291
// We end the stream here only if a Hard failure has been encountered or the connection has been interrupted.
283292
break;
284293
}
285294
int64_t frame_time = esp_timer_get_time() - last_frame;
286-
last_frame = esp_timer_get_time();;
287295
frame_time /= 1000;
296+
int32_t frame_delay = (minFrameTime > frame_time) ? minFrameTime - frame_time : 0;
297+
delay(frame_delay);
298+
288299
if (debugData) {
289-
Serial.printf("MJPG: %uB %ums (%.1ffps)\r\n",
300+
Serial.printf("MJPG: %uB %ums, delay: %ums, framerate (%.1ffps)\r\n",
290301
(uint32_t)(_jpg_buf_len),
291-
(uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
302+
(uint32_t)frame_time, frame_delay, 1000.0 / (uint32_t)(frame_time + frame_delay));
292303
}
304+
last_frame = esp_timer_get_time();
293305
}
294306

295307
streamsServed++;
@@ -341,6 +353,7 @@ static esp_err_t cmd_handler(httpd_req_t *req){
341353
if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val);
342354
}
343355
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); }
344357
else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
345358
else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
346359
else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
@@ -364,6 +377,7 @@ static esp_err_t cmd_handler(httpd_req_t *req){
364377
else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
365378
else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);
366379
else if(!strcmp(variable, "rotate")) myRotation = val;
380+
else if(!strcmp(variable, "min_frame_time")) minFrameTime = val;
367381
else if(!strcmp(variable, "autolamp") && (lampVal != -1)) {
368382
autoLamp = val;
369383
if (autoLamp) {
@@ -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
@@ -419,8 +434,10 @@ static esp_err_t status_handler(httpd_req_t *req){
419434
*p++ = '{';
420435
p+=sprintf(p, "\"lamp\":%d,", lampVal);
421436
p+=sprintf(p, "\"autolamp\":%d,", autoLamp);
437+
p+=sprintf(p, "\"min_frame_time\":%d,", minFrameTime);
422438
p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
423439
p+=sprintf(p, "\"quality\":%u,", s->status.quality);
440+
p+=sprintf(p, "\"xclk\":%u,", xclk);
424441
p+=sprintf(p, "\"brightness\":%d,", s->status.brightness);
425442
p+=sprintf(p, "\"contrast\":%d,", s->status.contrast);
426443
p+=sprintf(p, "\"saturation\":%d,", s->status.saturation);
@@ -495,7 +512,7 @@ static esp_err_t logo_svg_handler(httpd_req_t *req){
495512

496513
static esp_err_t dump_handler(httpd_req_t *req){
497514
flashLED(75);
498-
Serial.println("\r\nDump Requested via Web");
515+
Serial.println("\r\nDump requested via Web");
499516
serialDump();
500517
static char dumpOut[2000] = "";
501518
char * d = dumpOut;
@@ -508,7 +525,7 @@ static esp_err_t dump_handler(httpd_req_t *req){
508525
d+= sprintf(d,"<link rel=\"stylesheet\" type=\"text/css\" href=\"/style.css\">\n");
509526
d+= sprintf(d,"</head>\n");
510527
d+= sprintf(d,"<body>\n");
511-
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");
512529
if (critERR.length() > 0) {
513530
d+= sprintf(d,"<span style=\"color:red;\">%s<hr></span>\n", critERR.c_str());
514531
d+= sprintf(d,"<h2 style=\"color:red;\">(the serial log may give more information)</h2><br>\n");
@@ -566,11 +583,10 @@ static esp_err_t dump_handler(httpd_req_t *req){
566583
int upSec = sec % 60;
567584
int McuTc = (temprature_sens_read() - 32) / 1.8; // celsius
568585
int McuTf = temprature_sens_read(); // fahrenheit
569-
float xclk = xclkFreqHz/1000000;
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,"CPU Freq: %i MHz, Xclk Freq: %.1f MHz<br>\n", ESP.getCpuFreqMHz(), xclk);
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,8 @@ 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=\"Force-stop all active streams on the camera module\" ");
610+
d+= sprintf(d,"onclick=\"let throwaway = fetch('stop');setTimeout(function(){\nlocation.replace(document.URL);\n}, 200);\">Kill Stream</button>\n");
593611
d+= sprintf(d,"<button title=\"Close this page\" onclick=\"javascript:window.close()\">Close</button>\n");
594612
d+= sprintf(d,"</div>\n</body>\n");
595613
// A javascript timer to refresh the page every minute.
@@ -601,6 +619,15 @@ static esp_err_t dump_handler(httpd_req_t *req){
601619
return httpd_resp_send(req, dumpOut, strlen(dumpOut));
602620
}
603621

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

610637
static esp_err_t streamviewer_handler(httpd_req_t *req){
611638
flashLED(75);
612-
Serial.println("Stream Viewer requested");
639+
Serial.println("Stream viewer requested");
613640
httpd_resp_set_type(req, "text/html");
614641
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
615642
return httpd_resp_send(req, (const char *)streamviewer_html, streamviewer_html_len);
616643
}
617644

618645
static esp_err_t error_handler(httpd_req_t *req){
619646
flashLED(75);
620-
Serial.println("Sending Error page");
647+
Serial.println("Sending error page");
621648
std::string s(error_html);
622649
size_t index;
623650
while ((index = s.find("<APPURL>")) != std::string::npos)
@@ -705,7 +732,7 @@ static esp_err_t index_handler(httpd_req_t *req){
705732

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

710737
httpd_uri_t index_uri = {
711738
.uri = "/",
@@ -767,6 +794,12 @@ void startCameraServer(int hPort, int sPort){
767794
.handler = dump_handler,
768795
.user_ctx = NULL
769796
};
797+
httpd_uri_t stop_uri = {
798+
.uri = "/stop",
799+
.method = HTTP_GET,
800+
.handler = stop_handler,
801+
.user_ctx = NULL
802+
};
770803
httpd_uri_t stream_uri = {
771804
.uri = "/",
772805
.method = HTTP_GET,
@@ -817,6 +850,7 @@ void startCameraServer(int hPort, int sPort){
817850
httpd_register_uri_handler(camera_httpd, &favicon_ico_uri);
818851
httpd_register_uri_handler(camera_httpd, &logo_svg_uri);
819852
httpd_register_uri_handler(camera_httpd, &dump_uri);
853+
httpd_register_uri_handler(camera_httpd, &stop_uri);
820854
}
821855

822856
config.server_port = sPort;

camera_pins.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
/*
1+
/*
22
* Pin definitions for some common ESP-CAM modules
3-
*
3+
*
44
* Select the module to use in myconfig.h
55
* Defaults to AI-THINKER CAM module
6-
*
6+
*
77
*/
88
#if defined(CAMERA_MODEL_AI_THINKER)
99
//
@@ -33,7 +33,7 @@
3333

3434
#elif defined(CAMERA_MODEL_WROVER_KIT)
3535
//
36-
// ESP WROVER
36+
// ESP WROVER
3737
// https://dl.espressif.com/dl/schematics/ESP-WROVER-KIT_SCH-2.pdf
3838
//
3939
#define PWDN_GPIO_NUM -1
@@ -189,7 +189,7 @@
189189

190190
#elif defined(CAMERA_MODEL_TTGO_T_JOURNAL)
191191
//
192-
// LilyGO TTGO T-Journal ESP32; with OLED! but not used here.. :-(
192+
// LilyGO TTGO T-Journal ESP32; with OLED! but not used here.. :-(
193193
#define PWDN_GPIO_NUM 0
194194
#define RESET_GPIO_NUM 15
195195
#define XCLK_GPIO_NUM 27

css.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Master CSS file for the camera pages
33
*/
44

5-
const uint8_t style_css[] = R"=====(/*
5+
const uint8_t style_css[] = R"=====(/*
66
* CSS for the esp32 cam webserver
77
*/
88

0 commit comments

Comments
 (0)