Merge branch 'develop' into feature/update_moustache

This commit is contained in:
rzeldent
2023-02-05 13:13:16 +01:00
committed by GitHub
14 changed files with 226 additions and 80 deletions

View File

@@ -1,16 +1,17 @@
# ESP32CAM-RTSP
![status badge](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml/badge.svg?event=push)
[![Platform IO CI](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml/badge.svg)](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml)
Simple [RTSP](https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol) server.
Easy configuration through the web interface.
Flashing this software on a ESP32CAM module will make it a **RTSP streaming camera** server.
The RTSP protocol is an industry standard and allows many CCTV systems and applications (like for example [VLC](https://www.videolan.org/vlc/)) to connect directly to the ESP32CAM camera stream.
It is also possible to to stream directly to a server using [ffmpeg](https://ffmpeg.org).
It is also possible to stream directly to a server using [ffmpeg](https://ffmpeg.org).
This makes the module a camera server allowing recording and the stream can be stored on a disk and replayed later.
This software supports the following ESP32-CAM (and alike) modules:
- ESP32CAM
- AI THINKER
- TTGO T-CAM
@@ -20,6 +21,7 @@ This software supports the following ESP32-CAM (and alike) modules:
![ESP32CAM module](assets/ESP32-CAM.jpg)
This software provides a **configuration web server**, that can be used to:
- Provide information about the state of the device, wifi connection and camera,
- Set the WiFi parameters,
- Set the timeout for connecting to the access point,
@@ -40,44 +42,52 @@ It advertises HTTP (port 80) and RTSP (port 554)
- [**PlatformIO**](https://platformio.org/) software (free download)
## Installing and running PlatformIO
PlatformIO is available for all major operating systems: Windows, Linux and MacOS. It is also provided as a plugin to [Visual Studio Code](https://visualstudio.microsoft.com).
More information can be found at: [https://docs.platformio.org/en/latest/installation.html](https://docs.platformio.org/en/latest/installation.html) below the basics.
### Debian based systems command-line install
Install platformIO
```
```sh
sudo apt-get install python-pip
sudo pip install platformio
pio upgrade
```
### Windows, Linux and MacOS
Install [**Visual Studio code**](https://code.visualstudio.com) and install the PlatformIO plugin.
For command line usage Python and PlatformIO-Core is sufficient.
## Putting the ESP32-CAM in download mode
### ESP32-CAM-MB
When using the ESP32-CAM-MB board, press and hold the GP0 button on the ESP32-CAM-MB board.
Then press short the reset button (on the inside) on the ESP32-CAM board and release the GP0 button.
This will put the ESP32-CAM board in download mode.
### FTDI adapter
When using an FTDI adapter, make sure the adapter is set to 3.3 volt before connecting. Use the wiring schema below.
![ESP FTDI wiring](assets/ESP32CAM-to-FTDI.png)
After programming remove the wire to tge GPIO00 pin to exit the download mode.
After programming remove the wire to tge GPIO0 pin to exit the download mode.
## Compiling and deploying the software
Open a command line or terminal window and clone this repository from GitHub.
```
```sh
git clone https://github.com/rzeldent/esp32cam-rtsp.git
```
go into the folder
```
```sh
cd esp32cam-rtsp
```
@@ -88,19 +98,22 @@ I recommend to use VIsual Studio Code as it is free to use and offers more insig
### Using the command line
First the source code has to be compiled. Type:
```
```sh
pio run
```
When finished, firmware has to be uploaded.
Make sure the ESP32-CAM is in download mode (see previous section) and type:
```
```sh
pio run -t upload
```
When done remove the jumper when using a FTDI adapter or press the reset button on the ESP32-CAM.
To monitor the output, start a terminal using:
```
```sh
pio device monitor
```
@@ -111,10 +124,10 @@ Open the project in a new window. Run the following tasks using the ```Terminal
- PlatformIO: Build (esp32cam)
- PlatformIO: Upload (esp32cam)
To monitor the behavior run the task, run:
- PlatformIO: Monitor (esp32cam)
To monitor the behavior run the task, run: ```PlatformIO: Monitor (esp32cam)```
## Setting up the ESP32CAM-RTSP
After the programming of the ESP32, there is no configuration present. This needs to be added.
To connect initially to the device open the WiFi connections and select the WiFi network / access point called **ESP32CAM-RTSP**.
Initially there is no password present.
@@ -128,6 +141,7 @@ This link brings up the configuration screen when connecting fot the first time.
![Configuration screen](assets/Configuration.png)
Configure at least:
- The access point to connect to. No dropdown is present to show available networks!
- A password for accessing the Access point (AP) when starting. (required)
- Type of the ESP32-CAM board
@@ -137,6 +151,7 @@ Here it is possible to reboot the device so the settings take effect.
It is also possible to restart manually by pressing the reset button.
## Connecting to the configuration
After the initial configuration and the device is connected to an access point, the device can be configured over http.
When a connection is made to [http://esp32cam-rtsp](http://esp32cam-rtsp) the status screen is shown.
@@ -149,39 +164,75 @@ Clicking on the ```change configuration``` button will open the configuration. I
If this happens, for the user enter 'admin' and for the password the value that has been configured as the Access Point password.
## Connecting to the RTSP stream
RTSP stream is available at: [rtsp://esp32cam-rtsp.local:554/mjpeg/1](rtsp://esp32cam-rtsp.local:554/mjpeg/1).
This link can be opened with for example [VLC](https://www.videolan.org/vlc/).
:warning: **Please be aware that there is no password present on the stream!**
## API
There is a minimum API present to perform some tasks using HTTP requests. For some requests authentication is required.
The authentication used is basic authentication. The user is always admin and the password the access point key.\
If using a browser, remember that the authentication is stored in the browser session so needs to be entered only once.
The URLs are below:
### GET: /restart
Calling this URL will restart the device. Authentication is required.
### GET: /config
Calling this URL will start the form for configuring the device in the browser. Authentication is required.
### GET: /snapshot
Calling this URL will return a JPEG snapshot of the camera in the browser.
This request can also be used (for example using cURL) to save the snapshot to a file.
### GET: /flash?v={intensity}
Calling this URL will set the intensity of the flash LED. Authentication is required.
The parameter v for the intensity must be between 0 (off) and 255 (max).
If no v parameter is present, it will be set to the value of the flash LED intensity from configuration.
## Issues / Nice to know
- The red LED on the back of the device indicates the device is not connected.
- Sometimes after configuration a reboot is required.
If the error screen is shown that it is unable to make a connection, first try to reboot the device,
- When booting, the device waits 30 seconds for a connection (configurable).
You can make a connection to the SSID and log in using the crdentials below,
You can make a connection to the SSID and log in using the credentials below,
- When connected, go to the ip of the device and, when prompted for the credentials, enter 'admin' and the AP password.
This is a **required** field before saving the credentials,
- When the password is lost, a fix is to completely erase the ESP32 using the ```pio run -t erase``` command.
This will reset the device including configuration.
If using the esptool, you can do this using ```esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash```.
However, after erasing, reflashing of the firmware is required.
However, after erasing, re-flashing of the firmware is required.
- When finished configuring for the first time and the access point is entered, disconnect from the wireless network provided by the device.
This should reset the device and connect to the access point.
Resetting is also a good alternative...
### Power
Make sure the power is 5 volts and stable, although the ESP32 is a 3.3V module, this voltage is created on the board.
If not stable, it has been reported that restarts occur when starting up (probably when power is required for WiFi).
The software disableds the brown out protection so there is some margin in the voltage.
The software disables the brown out protection so there is some margin in the voltage.
### PSRAM
Some esp32cam modules have additional ram on the board. This allows to use this ram as frame buffer.
Detecting and using this special RAM is handled automatically.
The availability of PSRAM can be seen in the HTML status overview.
### Camera modules
Be carefull when connecting the camera module.
Make sure it is connected the right way around (Camera poining away from the board) and the ribbon cable inserted to the end before locking it.
Be careful when connecting the camera module.
Make sure it is connected the right way around (Camera pointing away from the board) and the ribbon cable inserted to the end before locking it.
## Credits
esp32cam-ready depends on PlatformIO, Bootstap5 and Micro-RTSP by Kevin Hester.
esp32cam-ready depends on PlatformIO, Bootstrap5 and Micro-RTSP by Kevin Hester.

View File

@@ -2,3 +2,4 @@
. python3 -m pip install htmlmin
. python3 ./html_to_cpp.py ./html ./include/html_data.h
. python3 ./html_to_cpp_gzip.py ./html_gzip ./include/html_data_gzip.h

View File

@@ -3,3 +3,4 @@ python3 -m pip install --upgrade pip setuptools wheel
python3 -m pip install htmlmin
python3 ./html_to_cpp.py ./html ./include/html_data.h
python3 ./html_to_cpp_gzip.py ./html_gzip ./include/html_data_gzip.h

View File

@@ -82,6 +82,10 @@
<div class="card bg-light mb-3">
<h5 class="card-header">Network</h5>
<div class="card-body">
<div class="row">
<div class="col-4">Host name:</div>
<div class="col-8">{{HostName}}</div>
</div>
<div class="row">
<div class="col-4">Mac address:</div>
<div class="col-8">{{MacAddress}}</div>
@@ -107,12 +111,12 @@
<div class="col-8">{{IpV6}}</div>
</div>
{{#NetworkState.ApMode}}
<div class="alert alert-warning" role="alert">
<div class="mt-4 alert alert-warning" role="alert">
<p>Not connected to an access point. Consider configuring the access point.</p>
</div>
{{/NetworkState.ApMode}}
{{#NetworkState.OnLine}}
<div class="alert alert-success" role="alert">
<div class="mt-4 alert alert-success" role="alert">
<p>Connected to the access point</p>
</div>
{{/NetworkState.OnLine}}
@@ -145,17 +149,21 @@
</div>
<div class="row">
<div class="col-4">JPEG quality:</div>
<div class="col-8">{{JpegQuality}} (0-100)</div>
<div class="col-8">{{JpegQuality}} (1-100)</div>
</div>
<div class="row">
<div class="col-4">Flash LED intensity:</div>
<div class="col-8">{{FlashLedIntensity}} (0-100)</div>
</div>
{{#CameraInitialized}}
<div class="alert alert-success" role="alert">
<div class="mt-4 alert alert-success" role="alert">
<p>Camera was initialized successfully!</p>
</div>
{{/CameraInitialized}}
{{^CameraInitialized}}
<div class="alert alert-danger" role="alert">
<div class="mt-4 alert alert-danger" role="alert">
<p>Failed to initialize the camera!</p>
<p>Result: {{CameraInitResultText}} ({{CameraInitResult}})</p>
<p>Result: {{CameraInitResultText}}</p>
<p>Please check hardware or correct the camera settings and restart.</p>
<button type="button" class="btn btn-danger"
onclick="location.href='restart'">Restart</button>
@@ -168,21 +176,42 @@
<div class="row">
<div class="card bg-light mb-3">
<h5 class="card-header">Camera stream</h5>
<h5 class="card-header">Special URLs / API</h5>
<div class="card-body">
</p>The camera stream can be found at the following location:
<a
href="rtsp://{{ThingName}}.local:{{RtspPort}}/mjpeg/1">rtsp://{{ThingName}}.local:{{RtspPort}}/mjpeg/1</a>
</p>
<img class="rounded mx-auto d-block" src="snapshot" alt="Camera image" width="25%">
<div class="row">
<span>
The camera RTSP stream can be found at:
<a
href="rtsp://{{IpV4}}:{{RtspPort}}/mjpeg/1">rtsp://{{IpV4}}:{{RtspPort}}/mjpeg/1</a>
</span>
</div>
<div class="row">
<span>
A snapshot of the camera can be found at:
<a href="http://{{IpV4}}/snapshot">http://{{IpV4}}/snapshot</a>
</span>
</div>
<div class="row">
<span>
The intensity of the flash led (0-255) can be controlled using:
<a href="http://{{IpV4}}/flash?v=0">http://{{IpV4}}/flash?v=value</a>.
Authentication is required and if no value is present, the configuration value is used.
</span>
</div>
<div class="row">
<span>
Restarting the camera can be done using:
<a href="http://{{IpV4}}/restart">http://{{IpV4}}/restart</a>.
Authentication is required.
</span>
</div>
</div>
</div>
</div>
</div>
<div class="d-grid gap-2 col-6 mx-auto">
<button type="button" class="btn btn-lg btn-warning" onclick="location.href='config'">Settings</button>
</div>
<div class="d-grid gap-2 col-6 mx-auto">
<button type="button" class="btn btn-lg btn-warning" onclick="location.href='config'">Settings</button>
</div>
</div>

View File

@@ -5,7 +5,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="bootstrap.min.css">
<meta http-equiv="refresh" content="30;url=/index.html">
<meta http-equiv="refresh" content="1;url=/index.html">
<title>{{AppTitle}} v{{AppVersion}}</title>
</head>

View File

@@ -16,11 +16,11 @@ file_names = filter(lambda x: x[0] != '.' and os.path.isfile(os.path.join(input_
file_names = sorted(file_names)
output_file = open(file_h, 'w')
output_file.write('//*******************************************************************************\n')
output_file.write('// HTML import\n')
output_file.write('// Machine generated file\n')
output_file.write('// ******************************************************************************\n')
output_file.write('\n')
output_file.write('//*******************************************************************************\n'
'// HTML import\n'
'// Machine generated file\n'
'// ******************************************************************************\n'
'\n\n')
for file_name in file_names:
print(f'Processing: {file_name}... ')
@@ -33,13 +33,9 @@ for file_name in file_names:
file.close()
html_mimified = htmlmin.minify(html, remove_empty_space=True)
output_file.write('\n')
output_file.write('constexpr char ' + file_data_name + '[] = "')
# escape "
html_mimified_escaped = html_mimified.replace('"', '\\"')
output_file.write(html_mimified_escaped)
output_file.write('";\n')
output_file.write(f'constexpr char {file_data_name }[] = "{html_mimified_escaped}";\n')
output_file.close()

View File

@@ -37,9 +37,9 @@ for file_name in file_names:
html_mimified = htmlmin.minify(html, remove_empty_space=True)
html_mimified_gzip = gzip.compress(bytes(html_mimified, 'utf-8'))
html_mimified_gzip_values = ','.join(f'0x{i:02x}' for x in html_mimified_gzip)
html_mimified_gzip_values = ','.join(f'0x{i:02x}' for i in html_mimified_gzip)
output_file.write(f'constexpr unsigned char {file_data_name}[] = {{{html_mimified_gzip_values}}};\n\n')
output_file.write(f'constexpr unsigned char {file_data_name}[] = {{\n{html_mimified_gzip_values}\n}};\n\n')
output_file.close()

View File

@@ -1,7 +1,7 @@
#pragma once
#include <string.h>
#include <sensor.h>
#include <esp_camera.h>
typedef char camera_config_name_t[18];

File diff suppressed because one or more lines are too long

10
include/html_data_gzip.h Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
#define WIFI_SSID "ESP32CAM-RTSP"
#define WIFI_PASSWORD nullptr
#define CONFIG_VERSION "1.1"
#define CONFIG_VERSION "1.3"
#define OTA_PASSWORD "ESP32CAM-RTSP"
@@ -15,3 +15,4 @@
#define DEFAULT_FRAME_BUFFERS "2"
#define DEFAULT_FRAME_SIZE "SVGA (800x600)"
#define DEFAULT_JPEG_QUALITY "12"
#define DEFAULT_LIGHT_INTENSITY "1"

View File

@@ -13,6 +13,14 @@ platform = espressif32
board = esp32cam
framework = arduino
#upload_protocol = espota
#upload_port = 192.168.50.222
#upload_flags =
# --auth='ESP32CAM-RTSP'
# Partition scheme for OTA
board_build.partitions = min_spiffs.csv
monitor_speed = 115200
monitor_rts = 0
monitor_dtr = 0
@@ -21,9 +29,11 @@ monitor_filters = log2file, time, default
build_flags =
-O2
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-D LED_BUILTIN=4
-D LED_FLASH=4
-D LED_BUILTIN=33
-D BOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-D IOTWEBCONF_PASSWORD_LEN=64
lib_deps =
prampec/IotWebConf @ ^3.2.1

View File

@@ -12,6 +12,7 @@
#include <format_number.h>
#include <moustache.h>
#include <html_data.h>
#include <html_data_gzip.h>
#include <settings.h>
char camera_config_val[sizeof(camera_config_entry)];
@@ -19,6 +20,7 @@ char frame_duration_val[6];
char frame_size_val[sizeof(frame_size_entry_t)];
char frame_buffers_val[3];
char jpeg_quality_val[4];
char flash_led_intensity_val[4];
auto config_group_stream_settings = iotwebconf::ParameterGroup("settings", "Streaming settings");
auto config_camera_config = iotwebconf::SelectParameter("Camera config", "config", camera_config_val, sizeof(camera_config_val), (const char *)camera_configs, (const char *)camera_configs, sizeof(camera_configs) / sizeof(camera_configs[0]), sizeof(camera_configs[0]), DEFAULT_CAMERA_CONFIG);
@@ -26,6 +28,7 @@ auto config_frame_rate = iotwebconf::NumberParameter("Frame duration (ms)", "fd"
auto config_frame_size = iotwebconf::SelectParameter("Frame size", "fs", frame_size_val, sizeof(frame_size_val), (const char *)frame_sizes, (const char *)frame_sizes, sizeof(frame_sizes) / sizeof(frame_sizes[0]), sizeof(frame_sizes[0]), DEFAULT_FRAME_SIZE);
auto config_frame_buffers = iotwebconf::NumberParameter("Frame buffers", "fb", frame_buffers_val, sizeof(frame_buffers_val), DEFAULT_FRAME_BUFFERS, nullptr, "min=\"1\" max=\"16\"");
auto config_jpg_quality = iotwebconf::NumberParameter("JPEG quality", "q", jpeg_quality_val, sizeof(jpeg_quality_val), DEFAULT_JPEG_QUALITY, nullptr, "min=\"1\" max=\"100\"");
auto config_flash_led_intensity = iotwebconf::NumberParameter("Flash LED intensity", "li", flash_led_intensity_val, sizeof(flash_led_intensity_val), DEFAULT_LIGHT_INTENSITY, nullptr, "min=\"0\" max=\"100\"");
// Camera
OV2640 cam;
@@ -42,11 +45,14 @@ bool config_changed = false;
// Camera initialization result
esp_err_t camera_init_result;
void stream_text_file(const char *contents, const char *mime_type)
void stream_text_file_gzip(const unsigned char *content, size_t length, const char *mime_type)
{
// Cache for 86400 seconds (one day)
web_server.sendHeader("Cache-Control", "max-age=86400");
web_server.send(200, mime_type, contents);
web_server.sendHeader("Content-encoding", "gzip");
web_server.setContentLength(length);
web_server.send(200, mime_type, "");
web_server.sendContent(reinterpret_cast<const char *>(content), length);
}
void handle_root()
@@ -56,6 +62,12 @@ void handle_root()
if (iotWebConf.handleCaptivePortal())
return;
// Format hostname
auto hostname = "esp32-" + WiFi.macAddress() + ".local";
hostname.replace(":", "");
hostname.toLowerCase();
// Wifi Modes
const char *wifi_modes[] = {"NULL", "STA", "AP", "STA+AP"};
moustache_variable_t substitutions[] = {
@@ -78,6 +90,7 @@ void handle_root()
{"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())},
{"NumRTSPSessions", camera_server != nullptr ? String(camera_server->num_connected()) : "N/A"},
// Network
{"HostName", hostname},
{"MacAddress", WiFi.macAddress()},
{"AccessPoint", WiFi.SSID()},
{"SignalStrength", String(WiFi.RSSI())},
@@ -95,8 +108,8 @@ void handle_root()
{"FrameBuffers", frame_buffers_val},
{"JpegQuality", jpeg_quality_val},
{"CameraInitialized", String(camera_init_result == ESP_OK)},
{"CameraInitResult", "0x" + String(camera_init_result, 16)},
{"CameraInitResultText", esp_err_to_name(camera_init_result)},
{"FlashLedIntensity", flash_led_intensity_val},
// RTSP
{"RtspPort", String(RTSP_PORT)}};
@@ -108,12 +121,10 @@ void handle_root()
void handle_restart()
{
log_v("Handle restart");
// If configuration is not changed and camera working, do not allow a restart
if (!config_changed && camera_init_result == ESP_OK)
if (!web_server.authenticate("admin", iotWebConf.getApPasswordParameter()->valueBuffer))
{
// Redirect to root page
web_server.sendHeader("Location", "/", true);
web_server.send(302, "text/plain", "Restart not possible.");
web_server.requestAuthentication();
return;
}
@@ -131,28 +142,56 @@ void handle_restart()
void handle_snapshot()
{
log_v("handle_jpg");
log_v("handle_snapshot");
if (camera_init_result != ESP_OK)
{
web_server.send(404, "text/plain", "Camera is not initialized");
return;
}
web_server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
web_server.sendContent("HTTP/1.1 200 OK\r\n"
"Content-Disposition: inline; filename=snapshot.jpg\r\n"
"Content-Type: image/jpeg\r\n\r\n");
// Make a copy in memory to prevent interaction with RTSP
// Remove old images stored in the framebuffer
auto frame_buffers = atoi(frame_buffers_val);
while (frame_buffers--)
cam.run();
auto fb_len = cam.getSize();
cam.run();
auto fb = (uint8_t *)memcpy(new uint8_t[cam.getSize()], cam.getfb(), fb_len);
web_server.sendContent(reinterpret_cast<const char *>(fb), fb_len);
delete[] fb;
auto fb = (const char*)cam.getfb();
if (fb == nullptr)
{
web_server.send(404, "text/plain", "Unable to obtain frame buffer from the camera");
return;
}
web_server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
web_server.setContentLength(fb_len);
web_server.send(200, "image/jpeg", "");
web_server.sendContent(fb, fb_len);
}
void handle_flash()
{
log_v("handle_flash");
if (!web_server.authenticate("admin", iotWebConf.getApPasswordParameter()->valueBuffer))
{
web_server.requestAuthentication();
return;
}
// If no value present, use value from config
auto value = web_server.hasArg("v") ? web_server.arg("v") : flash_led_intensity_val;
// If conversion fails, v = 0
auto v = (uint8_t)min(value.toInt(), 255l);
analogWrite(LED_FLASH, v);
web_server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
web_server.send(200);
}
void on_config_saved()
{
log_v("on_config_saved");
// Set flash led intensity
analogWrite(LED_FLASH, atoi(flash_led_intensity_val));
config_changed = true;
}
@@ -183,7 +222,7 @@ void start_rtsp_server()
camera_init_result = initialize_camera();
if (camera_init_result != ESP_OK)
{
log_e("Failed to initialize camera: 0x%0xd. Type: %s, frame size: %s, frame buffers: %s, frame rate: %s ms, jpeg quality: %s", camera_init_result, camera_config_val, frame_size_val, frame_buffers_val, frame_duration_val, jpeg_quality_val);
log_e("Failed to initialize camera: 0x%0x. Type: %s, frame size: %s, frame buffers: %s, frame rate: %s ms, jpeg quality: %s", camera_init_result, camera_config_val, frame_size_val, frame_buffers_val, frame_duration_val, jpeg_quality_val);
return;
}
@@ -197,6 +236,13 @@ void start_rtsp_server()
void on_connected()
{
log_v("on_connected");
// Turn LED off (has inverted logic GPIO33) => red LED off => connected
digitalWrite(LED_BUILTIN, true);
// Set flash led intensity
analogWrite(LED_FLASH, atoi(flash_led_intensity_val));
// Start (OTA) Over The Air programming when connected
ArduinoOTA.begin();
// Start the RTSP Server
start_rtsp_server();
}
@@ -206,8 +252,13 @@ void setup()
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
pinMode(LED_BUILTIN, OUTPUT);
// Turn LED on (has inverted logic GPIO33) => red LED on => not connected
digitalWrite(LED_BUILTIN, false);
pinMode(LED_FLASH, OUTPUT);
// Turn flash led off
analogWrite(LED_FLASH, 0);
#ifdef CORE_DEBUG_LEVEL
Serial.begin(115200);
Serial.setDebugOutput(true);
@@ -222,6 +273,7 @@ void setup()
config_group_stream_settings.addItem(&config_frame_size);
config_group_stream_settings.addItem(&config_frame_buffers);
config_group_stream_settings.addItem(&config_jpg_quality);
config_group_stream_settings.addItem(&config_flash_led_intensity);
iotWebConf.addParameterGroup(&config_group_stream_settings);
iotWebConf.getApTimeoutParameter()->visible = true;
iotWebConf.setConfigSavedCallback(on_config_saved);
@@ -235,10 +287,12 @@ void setup()
web_server.on("/restart", HTTP_GET, handle_restart);
// Camera snapshot
web_server.on("/snapshot", handle_snapshot);
// Camera flash light
web_server.on("/flash", HTTP_GET, handle_flash);
// bootstrap
web_server.on("/bootstrap.min.css", HTTP_GET, []()
{ stream_text_file(file_data_bootstrap_min_css, "text/css"); });
{ stream_text_file_gzip(file_data_bootstrap_min_css, sizeof(file_data_bootstrap_min_css), "text/css"); });
web_server.onNotFound([]()
{ iotWebConf.handleNotFound(); });
@@ -262,10 +316,6 @@ void setup()
default: log_e("OTA error: %u", error);
} });
ArduinoOTA.setPassword(OTA_PASSWORD);
// Start (OTA) Over The Air programming when connected
iotWebConf.setWifiConnectionCallback([]()
{ ArduinoOTA.begin(); });
}
void loop()