mirror of
https://github.com/rzeldent/esp32cam-rtsp.git
synced 2025-11-18 14:08:02 +00:00
Merge pull request #3 from rzeldent/feature/html_template
Feature/html template
This commit is contained in:
57
README.md
57
README.md
@@ -1,6 +1,6 @@
|
||||
# ESP32CAM-RTSP
|
||||
|
||||

|
||||

|
||||
|
||||
Simple [RTSP](https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol) server.
|
||||
Easy configuration through the web interface. Stable.
|
||||
@@ -63,7 +63,7 @@ When using an FTDI adapter, make sure the adapter is set to 3.3 volt before conn
|
||||
|
||||
After programming remove the wire to tge GPIO00 pin to exit the download mode.
|
||||
|
||||
## Compiling the software
|
||||
## Compiling and deploying the software
|
||||
|
||||
Open a command line or terminal window and clone this repository from GitHub.
|
||||
|
||||
@@ -75,14 +75,22 @@ go into the folder
|
||||
cd esp32cam-rtsp
|
||||
```
|
||||
|
||||
Next, the software has to be compiled. Type:
|
||||
Next, the firmware has to be build and deployed to the ESP32.
|
||||
There are to flavours to do this; using the command line or the graphical interface of Visual Studio Code. I recommend to use VIsual Studio Code as it is free to use and offers more insight.
|
||||
|
||||
### Using the command line
|
||||
|
||||
First the source code and SPIFF partition (data) has to be compiled. Type:
|
||||
```
|
||||
pio run
|
||||
pio run buildfs
|
||||
```
|
||||
|
||||
When finished, make sure the ESP32-CAM is in download mode (see previous section) and type:
|
||||
When finished, the SPIFF partition and software have to be uploaded.
|
||||
Make sure the ESP32-CAM is in download mode (see previous section) and type:
|
||||
```
|
||||
pio run -t upload
|
||||
pio run -t uploadfs
|
||||
```
|
||||
|
||||
When done remove the jumper when using a FTDI adapter or press the reset button on the ESP32-CAM.
|
||||
@@ -91,6 +99,18 @@ To monitor the output, start a terminal using:
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
### Using Visual studio
|
||||
|
||||
Open the project in a new window. Run the following tasks using the ```Terminal -> Run Task``` or CTRL+ALT+T command in the menu. Make sure the ESP32-CAM is in download mode during the uploads.
|
||||
|
||||
- PlatformIO: Build Filesystem Image (esp32cam)
|
||||
- PlatformIO: Build (esp32cam)
|
||||
- PlatformIO: Upload Filesystem Image (esp32cam)
|
||||
- PlatformIO: Upload (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 / accesspoint called **ESP32CAM-RTSP**.
|
||||
@@ -100,30 +120,37 @@ After connecting, the browser should automatically open the status page.
|
||||
In case this does not happens automatically, connect to [http://192.168.4.1](http://192.168.4.1).
|
||||
This page will display the current settings and status. On the bottom, there is a link to the config. Click on this link.
|
||||
|
||||
This link brings up the configuration screen.
|
||||
This link brings up the configuration screen when connecting fot the first time.
|
||||
|
||||

|
||||
|
||||
Configure at least:
|
||||
- The WiFi network settings. No dropdown is present to show available networks! Enter the Access point name manually.
|
||||
- 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)
|
||||
- the type of the ESP32-CAM board
|
||||
- Type of the ESP32-CAM board
|
||||
|
||||
When finished press Apply to save the configuration. The screen will redirect to the status screen.
|
||||
When finished press ```Apply``` to save the configuration. The screen will redirect to the status screen.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
In case changes have been made to the configuration, this is shown and the possibility to restart is given.
|
||||
|
||||
Clicking on the ```change configuration``` button will open the configuration. It is possible that a password dialog is shown before entering.
|
||||
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/).
|
||||
|
||||
**Please be aware that there is no password present on the stream!**
|
||||
|
||||
## Connecting to the configuration
|
||||
When a connection is made to [http://esp32cam-rtsp](http://esp32cam-rtsp) the status screen is shown.
|
||||
Clicking on the configuration link will open the configuration. It is possible that a password dialog is shown.
|
||||
For the user enter 'admin' and for the password the value that has been configured as the Access point password.
|
||||
:warning: **Please be aware that there is no password present on the stream!**
|
||||
|
||||
## Credits
|
||||
esp32cam-ready depends on PlatformIO and Micro-RTSP by Kevin Hester.
|
||||
esp32cam-ready depends on PlatformIO, Bootstap5 and Micro-RTSP by Kevin Hester.
|
||||
|
||||
|
||||
BIN
assets/index.png
Normal file
BIN
assets/index.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
7
data/bootstrap.min.css
vendored
Normal file
7
data/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
82
data/index.html
Normal file
82
data/index.html
Normal file
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<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">
|
||||
<title>{{AppTitle}} v{{AppVersion}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>{{ThingName}}</h1>
|
||||
<hr>
|
||||
|
||||
{{#ConfigChanged}}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<p>The configuration has been changed. It is recommended to restart the device</p>
|
||||
<p>This can be done by pressing the restart button below</p>
|
||||
<button type="button" class="btn btn-danger" onclick="location.href='restart'">Restart</button>
|
||||
</div>
|
||||
{{/ConfigChanged}}
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h5 class="card-header">ESP32</h5>
|
||||
<div class="card-body">
|
||||
<p>CPU model: {{ChipModel}}</p>
|
||||
<p>CPU speed: {{CpuFreqMHz}}Mhz</p>
|
||||
<p>Mac address: {{MacAddress}}</p>
|
||||
<p>IPv4 address: {{IpV4}}</p>
|
||||
<p>IPv6 address: {{IpV6}}</p>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h5 class="card-header">Settings</h5>
|
||||
<div class="card-body">
|
||||
<p>Camera type: {{CameraType}}</p>
|
||||
<p>Frame size: {{FrameSize}}</p>
|
||||
<p>Frame rate: {{FrameDuration}} ms ({{FrameFrequency}} f/s)</p>
|
||||
<p>JPEG quality: {{JpegQuality}} (0-100)</p>
|
||||
{{#CameraInitialized}}
|
||||
<div class="alert alert-success" role="alert">
|
||||
Camera was initialized successfully!
|
||||
</div>
|
||||
{{/CameraInitialized}}
|
||||
{{^CameraInitialized}}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Failed to initialize the camera! No streaming possible. Please change the camera settings and
|
||||
restart
|
||||
</div>
|
||||
{{/CameraInitialized}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h5 class="card-header">Diagnostics</h5>
|
||||
<div class="card-body">
|
||||
<p>Uptime: {{Uptime}}</p>
|
||||
<p>Free heap: {{FreeHeap}}b</p>
|
||||
<p>Max free block: {{MaxAllocHeap}}b</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h5 class="card-header">Camera stream</h5>
|
||||
<div class="card-body">
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-lg btn-warning" onclick="location.href='config'">Change
|
||||
configuration</button>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
29
data/restart.html
Normal file
29
data/restart.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<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">
|
||||
<title>{{AppTitle}} v{{AppVersion}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>{{ThingName}}</h1>
|
||||
<hr>
|
||||
<div class="jumbotron bg-light">
|
||||
<h1 class="display-4">Restart</h1>
|
||||
<p class="lead">The device is currently restarting. Please stand by...</p>
|
||||
<hr class="my-4">
|
||||
<p>In some cases, the device requires a hard reset (power cycle).</p>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="spinner-border text-danger" role="status">
|
||||
<span class="visually-hidden">Restarting...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -11,11 +11,86 @@ typedef struct camera_config_entry
|
||||
const camera_config_t config;
|
||||
} camera_config_entry_t;
|
||||
|
||||
constexpr camera_config_t esp32cam_settings = {
|
||||
.pin_pwdn = -1,
|
||||
.pin_reset = 15,
|
||||
.pin_xclk = 27,
|
||||
.pin_sscb_sda = 25,
|
||||
.pin_sscb_scl = 23,
|
||||
.pin_d7 = 19,
|
||||
.pin_d6 = 36,
|
||||
.pin_d5 = 18,
|
||||
.pin_d4 = 39,
|
||||
.pin_d3 = 5,
|
||||
.pin_d2 = 34,
|
||||
.pin_d1 = 35,
|
||||
.pin_d0 = 17,
|
||||
.pin_vsync = 22,
|
||||
.pin_href = 26,
|
||||
.pin_pclk = 21,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_0,
|
||||
.ledc_channel = LEDC_CHANNEL_0,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
.frame_size = FRAMESIZE_SVGA,
|
||||
.jpeg_quality = 12,
|
||||
.fb_count = 2};
|
||||
|
||||
constexpr camera_config_t esp32cam_aithinker_settings = {
|
||||
.pin_pwdn = 32,
|
||||
.pin_reset = -1,
|
||||
.pin_xclk = 0,
|
||||
.pin_sscb_sda = 26,
|
||||
.pin_sscb_scl = 27,
|
||||
.pin_d7 = 35,
|
||||
.pin_d6 = 34,
|
||||
.pin_d5 = 39,
|
||||
.pin_d4 = 36,
|
||||
.pin_d3 = 21,
|
||||
.pin_d2 = 19,
|
||||
.pin_d1 = 18,
|
||||
.pin_d0 = 5,
|
||||
.pin_vsync = 25,
|
||||
.pin_href = 23,
|
||||
.pin_pclk = 22,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_1,
|
||||
.ledc_channel = LEDC_CHANNEL_1,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
.frame_size = FRAMESIZE_SVGA,
|
||||
.jpeg_quality = 12,
|
||||
.fb_count = 2};
|
||||
|
||||
constexpr camera_config_t esp32cam_ttgo_t_settings = {
|
||||
.pin_pwdn = 26,
|
||||
.pin_reset = -1,
|
||||
.pin_xclk = 32,
|
||||
.pin_sscb_sda = 13,
|
||||
.pin_sscb_scl = 12,
|
||||
.pin_d7 = 39,
|
||||
.pin_d6 = 36,
|
||||
.pin_d5 = 23,
|
||||
.pin_d4 = 18,
|
||||
.pin_d3 = 15,
|
||||
.pin_d2 = 4,
|
||||
.pin_d1 = 14,
|
||||
.pin_d0 = 5,
|
||||
.pin_vsync = 27,
|
||||
.pin_href = 25,
|
||||
.pin_pclk = 19,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_0,
|
||||
.ledc_channel = LEDC_CHANNEL_0,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
.frame_size = FRAMESIZE_SVGA,
|
||||
.jpeg_quality = 12,
|
||||
.fb_count = 2};
|
||||
|
||||
constexpr const camera_config_entry_t camera_configs[] = {
|
||||
|
||||
{"ESP32CAM", {.pin_pwdn = -1, .pin_reset = 15, .pin_xclk = 27, .pin_sscb_sda = 25, .pin_sscb_scl = 23, .pin_d7 = 19, .pin_d6 = 36, .pin_d5 = 18, .pin_d4 = 39, .pin_d3 = 5, .pin_d2 = 34, .pin_d1 = 35, .pin_d0 = 17, .pin_vsync = 22, .pin_href = 26, .pin_pclk = 21, .xclk_freq_hz = 20000000, .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 12, .fb_count = 2}},
|
||||
{"AI THINKER", {.pin_pwdn = 32, .pin_reset = -1, .pin_xclk = 0, .pin_sscb_sda = 26, .pin_sscb_scl = 27, .pin_d7 = 35, .pin_d6 = 34, .pin_d5 = 39, .pin_d4 = 36, .pin_d3 = 21, .pin_d2 = 19, .pin_d1 = 18, .pin_d0 = 5, .pin_vsync = 25, .pin_href = 23, .pin_pclk = 22, .xclk_freq_hz = 20000000, .ledc_timer = LEDC_TIMER_1, .ledc_channel = LEDC_CHANNEL_1, .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 12, .fb_count = 2}},
|
||||
{"TTGO T-CAM", {.pin_pwdn = 26, .pin_reset = -1, .pin_xclk = 32, .pin_sscb_sda = 13, .pin_sscb_scl = 12, .pin_d7 = 39, .pin_d6 = 36, .pin_d5 = 23, .pin_d4 = 18, .pin_d3 = 15, .pin_d2 = 4, .pin_d1 = 14, .pin_d0 = 5, .pin_vsync = 27, .pin_href = 25, .pin_pclk = 19, .xclk_freq_hz = 20000000, .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 12, .fb_count = 2}}};
|
||||
{"ESP32CAM", esp32cam_settings},
|
||||
{"AI THINKER", esp32cam_aithinker_settings},
|
||||
{"TTGO T-CAM", esp32cam_ttgo_t_settings}};
|
||||
|
||||
const camera_config_t lookup_camera_config(const char *pin)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
#define RTSP_PORT 554
|
||||
#define DEFAULT_CAMERA_CONFIG "AI THINKER"
|
||||
#define DEFAULT_FRAMERATE "20"
|
||||
#define DEFAULT_FRAMEDURATION "20"
|
||||
#define DEFAULT_FRAMESIZE "SVGA (800x600)"
|
||||
#define DEFAULT_JPEG_QUALITY "12"
|
||||
5
lib/template_render/library.json
Normal file
5
lib/template_render/library.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "template_render",
|
||||
"version": "1.0.0",
|
||||
"description": "A mini template renderer"
|
||||
}
|
||||
53
lib/template_render/template_render.h
Normal file
53
lib/template_render/template_render.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *key;
|
||||
const String value;
|
||||
} template_variable_t;
|
||||
|
||||
template <typename T, size_t n>
|
||||
inline String template_render(const String& format, T (&values)[n])
|
||||
{
|
||||
auto s = String(format);
|
||||
// Conditional sections
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
// Include Section {{#expr}}
|
||||
auto match_section_begin = "{{#" + String(values[i].key) + "}}";
|
||||
// Inverted section {{^expr}}
|
||||
auto match_section_inverted_begin = "{{^" + String(values[i].key) + "}}";
|
||||
// End section {{/expr}}
|
||||
auto match_section_end = "{{/" + String(values[i].key) + "}}";
|
||||
while (true)
|
||||
{
|
||||
bool inverted = false;
|
||||
auto first = s.indexOf(match_section_begin);
|
||||
if (first < 0)
|
||||
{
|
||||
inverted = true;
|
||||
first = s.indexOf(match_section_inverted_begin);
|
||||
if (first < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
auto second = s.indexOf(match_section_end, first + match_section_begin.length());
|
||||
if (second < 0)
|
||||
break;
|
||||
|
||||
// Arduino returns 0 and 1 for bool.toString()
|
||||
if ((!inverted && (values[i].value == "1")) || (inverted && (values[i].value == "0")))
|
||||
s = s.substring(0, first) + s.substring(first + match_section_begin.length(), second) + s.substring(second + match_section_end.length());
|
||||
else
|
||||
s = s.substring(0, first) + s.substring(second + match_section_end.length());
|
||||
}
|
||||
}
|
||||
|
||||
// Replace variables {{variable}}
|
||||
for (size_t i = 0; i < n; i++)
|
||||
s.replace("{{" + String(values[i].key) + "}}", values[i].value);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -14,6 +14,8 @@ board = esp32cam
|
||||
framework = arduino
|
||||
|
||||
monitor_speed = 115200
|
||||
monitor_rts = 0
|
||||
monitor_dtr = 0
|
||||
monitor_filters = log2file, time, default
|
||||
|
||||
build_flags =
|
||||
|
||||
123
src/main.cpp
123
src/main.cpp
@@ -9,16 +9,18 @@
|
||||
#include <camera_config.h>
|
||||
#include <format_duration.h>
|
||||
#include <format_si.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <template_render.h>
|
||||
#include <settings.h>
|
||||
|
||||
char camera_config_val[sizeof(camera_config_entry)];
|
||||
char frame_rate_val[6];
|
||||
char frame_duration_val[6];
|
||||
char frame_size_val[sizeof(frame_size_entry_t)];
|
||||
char jpeg_quality_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);
|
||||
auto config_frame_rate = iotwebconf::NumberParameter("Frame rate (ms)", "fr", frame_rate_val, sizeof(frame_rate_val), DEFAULT_FRAMERATE, nullptr, "min=\"10\"");
|
||||
auto config_frame_rate = iotwebconf::NumberParameter("Frame duration (ms)", "fd", frame_duration_val, sizeof(frame_duration_val), DEFAULT_FRAMEDURATION, nullptr, "min=\"10\"");
|
||||
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_FRAMESIZE);
|
||||
auto config_jpg_quality = iotwebconf::NumberParameter("JPEG quality", "q", jpeg_quality_val, sizeof(jpeg_quality_val), DEFAULT_JPEG_QUALITY, nullptr, "min=\"1\" max=\"100\"");
|
||||
|
||||
@@ -34,6 +36,17 @@ IotWebConf iotWebConf(WIFI_SSID, &dnsServer, &web_server, WIFI_PASSWORD, CONFIG_
|
||||
|
||||
// Keep track of config changes. This will allow a reset of the device
|
||||
bool config_changed = false;
|
||||
// Check if camera is initialized
|
||||
bool camera_initialized = false;
|
||||
|
||||
void stream_file(const char *spiffs_file, const char *mime_type)
|
||||
{
|
||||
// Cache for 86400 seconds (one day)
|
||||
web_server.sendHeader("Cache-Control", "max-age=86400");
|
||||
auto file = SPIFFS.open(spiffs_file);
|
||||
web_server.streamFile(file, mime_type);
|
||||
file.close();
|
||||
}
|
||||
|
||||
void handle_root()
|
||||
{
|
||||
@@ -42,51 +55,31 @@ void handle_root()
|
||||
if (iotWebConf.handleCaptivePortal())
|
||||
return;
|
||||
|
||||
auto url = "rtsp://" + String(iotWebConf.getThingName()) + ".local:" + String(RTSP_PORT) + "/mjpeg/1";
|
||||
const template_variable_t substitutions[] = {
|
||||
{"AppTitle", APP_TITLE},
|
||||
{"AppVersion", APP_VERSION},
|
||||
{"ThingName", iotWebConf.getThingName()},
|
||||
{"ChipModel", ESP.getChipModel()},
|
||||
{"CpuFreqMHz", String(ESP.getCpuFreqMHz())},
|
||||
{"MacAddress", WiFi.macAddress()},
|
||||
{"IpV4", WiFi.localIP().toString()},
|
||||
{"IpV6", WiFi.localIPv6().toString()},
|
||||
{"CameraType", camera_config_val},
|
||||
{"FrameSize", frame_size_val},
|
||||
{"FrameDuration", frame_duration_val},
|
||||
{"FrameFrequency", String(1000.0 / atol(frame_duration_val), 1)},
|
||||
{"JpegQuality", jpeg_quality_val},
|
||||
{"Uptime", String(format_duration(millis() / 1000))},
|
||||
{"FreeHeap", format_si(ESP.getFreeHeap())},
|
||||
{"MaxAllocHeap", format_si(ESP.getMaxAllocHeap())},
|
||||
{"RtspPort", String(RTSP_PORT)},
|
||||
{"ConfigChanged", String(config_changed)},
|
||||
{"CameraInitialized", String(camera_initialized)}};
|
||||
|
||||
String html;
|
||||
html += "<!DOCTYPE html><html lang=\"en\">"
|
||||
"<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"
|
||||
"<head><title>" APP_TITLE " v" APP_VERSION "</title></head>"
|
||||
"<body>";
|
||||
|
||||
html += "<h2>Status page for " + String(iotWebConf.getThingName()) + "</h2><hr />";
|
||||
|
||||
html += "<h3>ESP32</h3>";
|
||||
html += "<ul>";
|
||||
html += "<li>CPU model: " + String(ESP.getChipModel()) + "</li>";
|
||||
html += "<li>CPU speed: " + String(ESP.getCpuFreqMHz()) + "Mhz</li>";
|
||||
html += "<li>Mac address: " + WiFi.macAddress() + "</li>";
|
||||
html += "<li>IPv4 address: " + WiFi.localIP().toString() + "</li>";
|
||||
html += "<li>IPv6 address: " + WiFi.localIPv6().toString() + "</li>";
|
||||
html += "</ul>";
|
||||
|
||||
html += "<h3>Settings</h3>";
|
||||
html += "<ul>";
|
||||
html += "<li>Camera type: " + String(camera_config_val) + "</li>";
|
||||
html += "<li>Frame size: " + String(frame_size_val) + "</li>";
|
||||
html += "<li>Frame rate: " + String(frame_rate_val) + " ms (" + String(1000.0 / atol(frame_rate_val), 1) + " f/s)</li>";
|
||||
html += "<li>JPEG quality: " + String(jpeg_quality_val) + " (0-100)</li>";
|
||||
html += "</ul>";
|
||||
|
||||
html += "<h3>Diagnostics</h3>";
|
||||
html += "<ul>";
|
||||
html += "<li>Uptime: " + String(format_duration(millis() / 1000)) + "</li>";
|
||||
html += "<li>Free heap: " + format_si(ESP.getFreeHeap()) + "b</li>";
|
||||
html += "<li>Max free block: " + format_si(ESP.getMaxAllocHeap()) + "b</li>";
|
||||
html += "</ul>";
|
||||
|
||||
html += "<br/>camera stream: <a href=\"" + url + "\">" + url + "</a>";
|
||||
html += "<br />";
|
||||
html += "<br/>Go to <a href=\"config\">configure page</a> to change settings.";
|
||||
|
||||
if (config_changed)
|
||||
{
|
||||
html += "<br />";
|
||||
html += "<br/><h3 style=\"color:red\">Configuration has changed. Please <a href=\"restart\">restart</a> the device.</h3>";
|
||||
}
|
||||
|
||||
html += "</body></html>";
|
||||
web_server.sendHeader("Cache-Control", "no-cache");
|
||||
auto file = SPIFFS.open("/index.html");
|
||||
auto html = template_render(file.readString(), substitutions);
|
||||
file.close();
|
||||
web_server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
@@ -95,20 +88,24 @@ void handle_restart()
|
||||
log_v("Handle restart");
|
||||
if (!config_changed)
|
||||
{
|
||||
// Redirect to root page.
|
||||
// Redirect to root page
|
||||
web_server.sendHeader("Location", "/", true);
|
||||
web_server.send(302, "text/plain", "");
|
||||
return;
|
||||
}
|
||||
|
||||
String html;
|
||||
html += "<h2>Restarting...</h2>";
|
||||
html += "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>";
|
||||
html += "<head><title>" APP_TITLE " v" APP_VERSION "</title></head>";
|
||||
html += "<body>";
|
||||
const template_variable_t substitutions[] = {
|
||||
{"AppTitle", APP_TITLE},
|
||||
{"AppVersion", APP_VERSION},
|
||||
{"ThingName", iotWebConf.getThingName()}};
|
||||
|
||||
web_server.sendHeader("Cache-Control", "no-cache");
|
||||
auto file = SPIFFS.open("/restart.html");
|
||||
auto html = template_render(file.readString(), substitutions);
|
||||
file.close();
|
||||
web_server.send(200, "text/html", html);
|
||||
log_v("Restarting... Press refresh to connect again");
|
||||
sleep(250);
|
||||
sleep(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
@@ -127,7 +124,7 @@ bool initialize_camera()
|
||||
auto frame_size = lookup_frame_size(frame_size_val);
|
||||
log_i("JPEG quality: %s", jpeg_quality_val);
|
||||
auto jpeg_quality = atoi(jpeg_quality_val);
|
||||
log_i("Framerate: %s ms", frame_rate_val);
|
||||
log_i("Frame rate: %s ms", frame_duration_val);
|
||||
|
||||
camera_config.frame_size = frame_size;
|
||||
camera_config.jpeg_quality = jpeg_quality;
|
||||
@@ -137,13 +134,15 @@ bool initialize_camera()
|
||||
void start_rtsp_server()
|
||||
{
|
||||
log_v("start_rtsp_server");
|
||||
if (!initialize_camera())
|
||||
camera_initialized = initialize_camera();
|
||||
if (!camera_initialized)
|
||||
{
|
||||
log_e("Failed to initialize camera. Type: %s, frame size: %s, frame rate: %s ms, jpeg quality: %s", camera_config_val, frame_size_val, frame_rate_val, jpeg_quality_val);
|
||||
log_e("Failed to initialize camera. Type: %s, frame size: %s, frame rate: %s ms, jpeg quality: %s", camera_config_val, frame_size_val, frame_duration_val, jpeg_quality_val);
|
||||
return;
|
||||
}
|
||||
|
||||
auto frame_rate = atol(frame_rate_val);
|
||||
log_i("Camera initialized");
|
||||
auto frame_rate = atol(frame_duration_val);
|
||||
camera_server = std::unique_ptr<rtsp_server>(new rtsp_server(cam, frame_rate, RTSP_PORT));
|
||||
// Add service to mDNS - rtsp
|
||||
MDNS.addService("rtsp", "tcp", 554);
|
||||
@@ -168,10 +167,13 @@ void setup()
|
||||
Serial.setDebugOutput(true);
|
||||
#endif
|
||||
|
||||
log_i("CPU Freq = %d Mhz", getCpuFrequencyMhz());
|
||||
log_i("CPU Freq: %d Mhz", getCpuFrequencyMhz());
|
||||
log_i("Free heap: %d bytes", ESP.getFreeHeap());
|
||||
log_i("Starting " APP_TITLE "...");
|
||||
|
||||
if (!SPIFFS.begin())
|
||||
log_e("Error while mounting SPIFFS. Please upload the filesystem");
|
||||
|
||||
config_group_stream_settings.addItem(&config_camera_config);
|
||||
config_group_stream_settings.addItem(&config_frame_rate);
|
||||
config_group_stream_settings.addItem(&config_frame_size);
|
||||
@@ -187,6 +189,11 @@ void setup()
|
||||
web_server.on("/config", []
|
||||
{ iotWebConf.handleConfig(); });
|
||||
web_server.on("/restart", HTTP_GET, handle_restart);
|
||||
|
||||
// bootstrap
|
||||
web_server.on("/bootstrap.min.css", HTTP_GET, []()
|
||||
{ stream_file("/bootstrap.min.css", "text/css"); });
|
||||
|
||||
web_server.onNotFound([]()
|
||||
{ iotWebConf.handleNotFound(); });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user