forked from external-repos/esp32cam-rtsp
Merge branch 'develop' into feature/update_moustache
This commit is contained in:
85
README.md
85
README.md
@@ -1,16 +1,17 @@
|
||||
# ESP32CAM-RTSP
|
||||
|
||||

|
||||
[](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:
|
||||

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

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

|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
10
include/html_data_gzip.h
Normal file
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
@@ -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
|
||||
|
||||
98
src/main.cpp
98
src/main.cpp
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user