mirror of
https://github.com/rzeldent/esp32cam-rtsp.git
synced 2025-11-12 11:16:22 +00:00
Compare commits
25 Commits
1.0.4
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e4fee3624 | ||
|
|
7a0259d878 | ||
|
|
f68e347c5b | ||
|
|
0d0223e8df | ||
|
|
45e9a92e3c | ||
|
|
3e6df1a56b | ||
|
|
a40f6a8ad8 | ||
|
|
aee7cbddd8 | ||
|
|
8c263be1a6 | ||
|
|
0dc7abc338 | ||
|
|
e88c7a6f62 | ||
|
|
a711eec80c | ||
|
|
5f80cabbed | ||
|
|
621dbe466e | ||
|
|
72482080cf | ||
|
|
97acc08edf | ||
|
|
86dcf88d4e | ||
|
|
730d941988 | ||
|
|
2fdb589bfd | ||
|
|
e69ff1fbe8 | ||
|
|
064f0e9ac9 | ||
|
|
7ceb878a59 | ||
|
|
19b8f77d57 | ||
|
|
5dac9876fb | ||
|
|
1b06941ee8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
.*/*
|
.*/*
|
||||||
.*.*
|
.*.*
|
||||||
|
__pycache__/
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
workspace.code-workspace
|
workspace.code-workspace
|
||||||
85
README.md
85
README.md
@@ -2,13 +2,31 @@
|
|||||||
|
|
||||||
[](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml)
|
[](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml)
|
||||||
|
|
||||||
Simple [RTSP](https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol) server.
|
Simple [RTSP](https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol), [HTTP JPEG Streamer](https://en.wikipedia.org/wiki/Motion_JPEG) and image server with configuration through the web interface.
|
||||||
Easy configuration through the web interface.
|
|
||||||
|
|
||||||
Flashing this software on a ESP32CAM module will make it a **RTSP streaming camera** server.
|
> [!IMPORTANT]
|
||||||
|
> New branch available! Here [branch: develop](https://github.com/rzeldent/esp32cam-rtsp/tree/develop)
|
||||||
|
> This branch supports all the current devices and the Seeed Studio Xiao esp32s3!
|
||||||
|
> Please use this and let me know if this works for you!
|
||||||
|
|
||||||
|
|
||||||
|
Flashing this software on a ESP32CAM module will make it a **RTSP streaming camera** server, a **HTTP Motion JPEG streamer** and a **HTTP image server**.
|
||||||
|
|
||||||
|
Supported protocols
|
||||||
|
|
||||||
|
- :white_check_mark: RTSP
|
||||||
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.
|
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 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 makes the module a camera server allowing recording and the stream can be stored on a disk and replayed later.
|
||||||
|
The URL is rtsp://<ip address>:554/mjpeg/1
|
||||||
|
|
||||||
|
- :white_check_mark: HTTP Motion JPEG
|
||||||
|
The HTTP JPEG streamer makes it possible to watch the camera stream directly in your browser.
|
||||||
|
The URL is http://<ip address>/stream
|
||||||
|
|
||||||
|
- :white_check_mark: HTTP image
|
||||||
|
The HTTP Image returns an HTTP JPEG image of the camera.
|
||||||
|
The URL is http://<ip address>/snapshot
|
||||||
|
|
||||||
This software supports the following ESP32-CAM (and alike) modules:
|
This software supports the following ESP32-CAM (and alike) modules:
|
||||||
|
|
||||||
@@ -20,7 +38,7 @@ This software supports the following ESP32-CAM (and alike) modules:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
This software provides a **configuration web server**, that can be used to:
|
The software provides a **configuration web server**, that can be used to:
|
||||||
|
|
||||||
- Provide information about the state of the device, wifi connection and camera,
|
- Provide information about the state of the device, wifi connection and camera,
|
||||||
- Set the WiFi parameters,
|
- Set the WiFi parameters,
|
||||||
@@ -30,6 +48,8 @@ This software provides a **configuration web server**, that can be used to:
|
|||||||
- Select the image size,
|
- Select the image size,
|
||||||
- Select the frame rate,
|
- Select the frame rate,
|
||||||
- Select the JPEG quality
|
- Select the JPEG quality
|
||||||
|
- Enable the use of the PSRAM
|
||||||
|
- Set the number of frame buffers
|
||||||
- Configure the camera options:
|
- Configure the camera options:
|
||||||
- Brightness
|
- Brightness
|
||||||
- Contrast
|
- Contrast
|
||||||
@@ -185,12 +205,21 @@ In case changes have been made to the configuration, this is shown and the possi
|
|||||||
Clicking on the ```change configuration``` button will open the configuration. It is possible that a password dialog is shown before entering.
|
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.
|
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
|
## Connecting to the RTSP stream :video_camera:
|
||||||
|
|
||||||
RTSP stream is available at: [rtsp://esp32cam-rtsp.local:554/mjpeg/1](rtsp://esp32cam-rtsp.local:554/mjpeg/1).
|
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/).
|
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!**
|
## Connecting to the JPEG motion server :video_camera:
|
||||||
|
|
||||||
|
The JPEG motion server server is available using a normal web browser at: [http://esp32cam-rtsp.local:/stream](http://esp32cam-rtsp.local/stream).
|
||||||
|
|
||||||
|
## Connecting to the image server :camera:
|
||||||
|
|
||||||
|
The image server server is available using a normal web browser at: [http://esp32cam-rtsp.local:/snapshot](http://esp32cam-rtsp.local/snapshot).
|
||||||
|
|
||||||
|
:bangbang: **Please be aware that there is no password present!**.
|
||||||
|
Everybody with access to the device can see the streams or images! Beware of :trollface:!
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
@@ -237,30 +266,66 @@ If no v parameter is present, it will be set to the value of the flash LED inten
|
|||||||
- When finished configuring for the first time and the access point is entered, disconnect from the wireless network provided by the device.
|
- 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.
|
This should reset the device and connect to the access point.
|
||||||
Resetting is also a good alternative...
|
Resetting is also a good alternative...
|
||||||
|
- There are modules that have no or faulty PSRAM (despite advertised as such).
|
||||||
|
This can be the case if the camera fails to initialize.
|
||||||
|
It might help to disable the use of the PSRAM and reduce the buffers and the screen size.
|
||||||
|
|
||||||
### Power
|
### 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.
|
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).
|
If not stable, it has been reported that restarts occur when starting up (probably when power is required for WiFi).
|
||||||
The software disables 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.
|
||||||
|
Some people suggest to add a capacitor over the 5V input to stabilize the voltage.
|
||||||
|
|
||||||
### PSRAM
|
### PSRAM / Buffers / JPEG quality
|
||||||
|
|
||||||
Some esp32cam modules have additional ram on the board. This allows to use this ram as frame buffer.
|
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.
|
The availability of PSRAM can be seen in the HTML status overview.
|
||||||
|
|
||||||
### Camera modules
|
Not all the boards are equipped with PSRAM:
|
||||||
|
|
||||||
|
| Board | PSRAM |
|
||||||
|
|--- |--- |
|
||||||
|
| ESP32CAM | Yes |
|
||||||
|
| ESP32CAM (USB-C) | No |
|
||||||
|
| AI THINKER | Yes |
|
||||||
|
| TTGO T-CAM | No |
|
||||||
|
| M5 STACK| | No |
|
||||||
|
| WROVER KIT | Yes |
|
||||||
|
|
||||||
|
Depending on the image resolution, framerate and quality, the PSRAM must be enabled and/or the number of frame buffers increased to keep up with the data generated by the sensor.
|
||||||
|
There are (a lot of?) boards around with faulty PSRAM. If the camera fails to initialize, this might be a reason. See on [Reddit](https://www.reddit.com/r/esp32/comments/z2hyns/i_have_a_faulty_psram_on_my_esp32cam_what_should/).
|
||||||
|
In this case disable the use of PSRAM in the configuration and do not use camera modes that require PSRAM,
|
||||||
|
|
||||||
|
For the setting JPEG quality, a lower number means higher quality.
|
||||||
|
Be aware that a very high quality (low number) can cause the ESP32 cam to crash or return no image.
|
||||||
|
|
||||||
|
The default settings are:
|
||||||
|
|
||||||
|
- No PSRAM
|
||||||
|
- SVGA (800x600)
|
||||||
|
- 1 frame buffer
|
||||||
|
- JPEG quality 12
|
||||||
|
|
||||||
|
- With PSRAM
|
||||||
|
- UXGA (1600x1200)
|
||||||
|
- 2 frame buffers
|
||||||
|
- JPEG quality 10
|
||||||
|
|
||||||
|
### Camera module
|
||||||
|
|
||||||
Be careful when connecting the camera module.
|
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.
|
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
|
## Credits
|
||||||
|
|
||||||
esp32cam-ready depends on PlatformIO, Bootstrap5 and Micro-RTSP by Kevin Hester.
|
esp32cam-rtsp depends on PlatformIO, Bootstrap 5 and Micro-RTSP by Kevin Hester.
|
||||||
|
|
||||||
## Change history
|
## Change history
|
||||||
|
|
||||||
|
- March 2023
|
||||||
|
- Added options to set PSRAM / Frame buffers
|
||||||
|
- Added JPEG Motion streaming
|
||||||
- Feb 2023
|
- Feb 2023
|
||||||
- Added additional settings for camera configuration
|
- Added additional settings for camera configuration
|
||||||
- Nov 2022
|
- Nov 2022
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
. python3 -m pip install --upgrade pip setuptools wheel
|
. python3 -m pip install --upgrade pip setuptools wheel
|
||||||
. python3 -m pip install htmlmin
|
. python3 -m pip install minify-html
|
||||||
|
|
||||||
. python3 ./html_to_cpp.py ./html ./include/html_data.h
|
. python3 ./minify.py ./html/index.html ./html/index.min.html
|
||||||
. python3 ./html_to_cpp_gzip.py ./html_gzip ./include/html_data_gzip.h
|
. python3 ./minify.py ./html/restart.html ./html/restart.min.html
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
python3 -m pip install --upgrade pip setuptools wheel
|
|
||||||
python3 -m pip install htmlmin
|
|
||||||
|
|
||||||
python3 ./html_to_cpp.py ./html ./include/html_data.h
|
python3 -m pip install --upgrade pip setuptools wheel
|
||||||
python3 ./html_to_cpp_gzip.py ./html_gzip ./include/html_data_gzip.h
|
python3 -m pip install minify-html
|
||||||
|
|
||||||
|
python3 ./minify.py ./html/index.html ./html/index.min.html
|
||||||
|
python3 ./minify.py ./html/restart.html ./html/restart.min.html
|
||||||
506
html/index.html
506
html/index.html
@@ -4,307 +4,271 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link rel="stylesheet" href="bootstrap.min.css">
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-family: Arial, Verdana, Helvetica, sans-serif;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-table {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, 25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-table>.row {
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-column-end: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #3c763d;
|
||||||
|
background-color: #dff0d8;
|
||||||
|
border-color: #d6e9c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: #8a6d3b;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
border-color: #faebcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.3333333;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #337ab7;
|
||||||
|
border-color: #2e6da4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #d9534f;
|
||||||
|
border-color: #d43f3a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<title>{{AppTitle}} v{{AppVersion}}</title>
|
<title>{{AppTitle}} v{{AppVersion}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
|
||||||
<h1 class="text-center">{{ThingName}}</h1>
|
<h1 class="text-center">{{ThingName}}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<h3 class="text-center">
|
||||||
|
Press on the button below to change the settings<br><br>
|
||||||
|
<button type="button" class="btn btn-lg btn-primary" onclick="location.href='config'">Change
|
||||||
|
settings</button>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#ConfigChanged}}
|
{{#ConfigChanged}}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger">
|
||||||
<p>The configuration has been changed.</p>
|
<h3 class="text-center">
|
||||||
<p>It is recommended to restart the device.</p>
|
The configuration has been changed.<br>
|
||||||
|
It is recommended to restart the device.<br><br>
|
||||||
<button type="button" class="btn btn-danger" onclick="location.href='restart'">Restart</button>
|
<button type="button" class="btn btn-danger" onclick="location.href='restart'">Restart</button>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{{/ConfigChanged}}
|
{{/ConfigChanged}}
|
||||||
|
<h2 class="text-center">ESP32</h2>
|
||||||
|
<div class="flex-table">
|
||||||
|
<div class="row">SDK Version:</div>
|
||||||
|
<div>{{SDKVersion}}</div>
|
||||||
|
<div class="row">CPU model:</div>
|
||||||
|
<div>{{ChipModel}} rev. {{ChipRevision}}</div>
|
||||||
|
<div class="row">CPU speed:</div>
|
||||||
|
<div>{{CpuFreqMHz}} Mhz</div>
|
||||||
|
<div class="row">CPU cores:</div>
|
||||||
|
<div>{{CpuCores}}</div>
|
||||||
|
<div class="row">RAM size:</div>
|
||||||
|
<div>{{HeapSize}}</div>
|
||||||
|
<div class="row">PSRAM size:</div>
|
||||||
|
<div>{{PsRamSize}}</div>
|
||||||
|
<div class="row">Flash size:</div>
|
||||||
|
<div>{{FlashSize}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<h2 class="text-center">Diagnostics</h2>
|
||||||
<div class="col-sm-6">
|
<div class="flex-table">
|
||||||
<div class="card bg-light mb-3">
|
<div class="row">Uptime:</div>
|
||||||
<h5 class="card-header">ESP32</h5>
|
<div>{{Uptime}}</div>
|
||||||
<div class="card-body">
|
<div class="row">Chip temperature:</div>
|
||||||
<div class="row">
|
<div>{{Temperature}} °C</div>
|
||||||
<div class="col-4">CPU model:</div>
|
<div class="row">RTSP sessions:</div>
|
||||||
<div class="col-8">{{ChipModel}} rev. {{ChipRevision}}</div>
|
<div>{{NumRTSPSessions}}</div>
|
||||||
|
<div class="row">Free heap:</div>
|
||||||
|
<div>{{FreeHeap}}</div>
|
||||||
|
<div class="row">Max free block:</div>
|
||||||
|
<div>{{MaxAllocHeap}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">CPU speed:</div>
|
<h2 class="text-center">Peripheral</h2>
|
||||||
<div class="col-8">{{CpuFreqMHz}} Mhz</div>
|
<div class="flex-table">
|
||||||
|
<div class="row">Board type:</div>
|
||||||
|
<div>{{BoardType}}</div>
|
||||||
|
<div class="row">LED intensity:</div>
|
||||||
|
<div>{{LedIntensity}} [0-100]</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">CPU cores:</div>
|
<h2 class="text-center">Network</h2>
|
||||||
<div class="col-8">{{CpuCores}}</div>
|
<div class="flex-table">
|
||||||
</div>
|
<div class="row">Host name:</div>
|
||||||
<div class="row">
|
<div>{{HostName}}</div>
|
||||||
<div class="col-4">RAM size:</div>
|
<div class="row">Mac address:</div>
|
||||||
<div class="col-8">{{HeapSize}}</div>
|
<div>{{MacAddress}}</div>
|
||||||
</div>
|
<div class="row">Wifi mode:</div>
|
||||||
<div class="row">
|
<div>{{WifiMode}}</div>
|
||||||
<div class="col-4">PSRAM size:</div>
|
<div class="row">Access point:</div>
|
||||||
<div class="col-8">{{PsRamSize}}</div>
|
<div>{{AccessPoint}}</div>
|
||||||
</div>
|
<div class="row">Signal strength:</div>
|
||||||
<div class="row">
|
<div>{{SignalStrength}} dbm</div>
|
||||||
<div class="col-4">Flash size:</div>
|
<div class="row">IPv4 address:</div>
|
||||||
<div class="col-8">{{FlashSize}}</div>
|
<div>{{IpV4}}</div>
|
||||||
</div>
|
<div class="row">IPv6 address:</div>
|
||||||
</div>
|
<div>{{IpV6}}</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="card bg-light mb-3">
|
|
||||||
<h5 class="card-header">Diagnostics</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Uptime:</div>
|
|
||||||
<div class="col-8">{{Uptime}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">RTSP sessions:</div>
|
|
||||||
<div class="col-8">{{NumRTSPSessions}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Free heap:</div>
|
|
||||||
<div class="col-8">{{FreeHeap}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Max free block:</div>
|
|
||||||
<div class="col-8">{{MaxAllocHeap}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="card bg-light mb-3">
|
|
||||||
<h5 class="card-header">Peripheral</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Board type:</div>
|
|
||||||
<div class="col-8">{{BoardType}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">LED intensity:</div>
|
|
||||||
<div class="col-8">{{LedIntensity}} [0-100]</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Wifi mode:</div>
|
|
||||||
<div class="col-8">{{WifiMode}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Access point:</div>
|
|
||||||
<div class="col-8">{{AccessPoint}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Signal strength:</div>
|
|
||||||
<div class="col-8">{{SignalStrength}} dbm</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">IPv4 address:</div>
|
|
||||||
<div class="col-8">{{IpV4}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">IPv6 address:</div>
|
|
||||||
<div class="col-8">{{IpV6}}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#NetworkState.ApMode}}
|
{{#NetworkState.ApMode}}
|
||||||
<div class="mt-4 alert alert-warning" role="alert">
|
<div class="alert alert-warning">
|
||||||
<p>Not connected to an access point. Consider configuring the access point.</p>
|
<h3 class="text-center">Not connected to an access point.<br>Consider configuring the access point.</h3>
|
||||||
</div>
|
</div>
|
||||||
{{/NetworkState.ApMode}}
|
{{/NetworkState.ApMode}}
|
||||||
|
|
||||||
{{#NetworkState.OnLine}}
|
{{#NetworkState.OnLine}}
|
||||||
<div class="mt-4 alert alert-success" role="alert">
|
<div class="alert alert-success">
|
||||||
<p>Connected to the access point</p>
|
<h3 class="text-center">Connected to the access point</h3>
|
||||||
</div>
|
</div>
|
||||||
{{/NetworkState.OnLine}}
|
{{/NetworkState.OnLine}}
|
||||||
|
|
||||||
|
<h2 class="text-center">Camera</h2>
|
||||||
|
<div class="flex-table">
|
||||||
|
<div class="row">Frame rate:</div>
|
||||||
|
<div>{{FrameDuration}} ms ({{FrameFrequency}} f/s)</div>
|
||||||
|
<div class="row">Frame size:</div>
|
||||||
|
<div>{{FrameSize}}</div>
|
||||||
|
<div class="row">JPEG quality:</div>
|
||||||
|
<div>{{JpegQuality}} [1-100]</div>
|
||||||
|
<div class="row">Enable PSRAM:</div>
|
||||||
|
<div>{{#EnablePSRAM}}Enabled{{/EnablePSRAM}}{{^EnablePSRAM}}Disabled{{/EnablePSRAM}}</div>
|
||||||
|
<div class="row">Number of frame buffers:</div>
|
||||||
|
<div>{{FrameBuffers}}</div>
|
||||||
|
<div class="row">Brightness:</div>
|
||||||
|
<div>{{Brightness}} [-2,2]</div>
|
||||||
|
<div class="row">Contrast:</div>
|
||||||
|
<div>{{Contrast}} [-2,2]</div>
|
||||||
|
<div class="row">Saturation:</div>
|
||||||
|
<div>{{Saturation}} [-2,2]</div>
|
||||||
|
<div class="row">Special effect:</div>
|
||||||
|
<div>{{SpecialEffect}}</div>
|
||||||
|
<div class="row">White balance:</div>
|
||||||
|
<div>{{#WhiteBal}}Auto{{/WhiteBal}}{{^WhiteBal}}Manual{{/WhiteBal}}</div>
|
||||||
|
<div class="row">AWB gain:</div>
|
||||||
|
<div>{{#AwbGain}}Auto{{/AwbGain}}{{^AwbGain}}Manual{{/AwbGain}}</div>
|
||||||
|
<div class="row">WB mode:</div>
|
||||||
|
<div>{{WbMode}}</div>
|
||||||
|
<div class="row">Exposure control:</div>
|
||||||
|
<div>{{#ExposureCtrl}}Auto{{/ExposureCtrl}}{{^ExposureCtrl}}Manual{{/ExposureCtrl}}</div>
|
||||||
|
<div class="row">Auto exposure control (dsp):</div>
|
||||||
|
<div>{{#Aec2}}Enabled{{/Aec2}}{{^Aec2}}Disabled{{/Aec2}}</div>
|
||||||
|
<div class="row">Auto Exposure level:</div>
|
||||||
|
<div>{{AeLevel}}</div>
|
||||||
|
<div class="row">Manual exposure value:</div>
|
||||||
|
<div>{{AecValue}}</div>
|
||||||
|
<div class="row">Gain control:</div>
|
||||||
|
<div>{{#GainCtrl}}Auto{{/GainCtrl}}{{^GainCtrl}}Manual{{/GainCtrl}}</div>
|
||||||
|
<div class="row">AGC gain:</div>
|
||||||
|
<div>{{AgcGain}}</div>
|
||||||
|
<div class="row">Gain ceiling:</div>
|
||||||
|
<div>{{GainCeiling}}</div>
|
||||||
|
<div class="row">Black pixel correct:</div>
|
||||||
|
<div>{{#Bpc}}Auto{{/Bpc}}{{^Bpc}}Manual{{/Bpc}}</div>
|
||||||
|
<div class="row">White pixel correct:</div>
|
||||||
|
<div>{{#Wpc}}Auto{{/Wpc}}{{^Wpc}}Manual{{/Wpc}}</div>
|
||||||
|
<div class="row">Gamma correct:</div>
|
||||||
|
<div>{{#RawGma}}Enabled{{/RawGma}}{{^RawGma}}Disabled{{/RawGma}}</div>
|
||||||
|
<div class="row">Lens correction:</div>
|
||||||
|
<div>{{#Lenc}}Enabled{{/Lenc}}{{^Lenc}}Disabled{{/Lenc}}</div>
|
||||||
|
<div class="row">Horizontal mirror:</div>
|
||||||
|
<div>{{#HMirror}}Mirrored{{/HMirror}}{{^HMirror}}Normal{{/HMirror}}</div>
|
||||||
|
<div class="row">Vertical flip:</div>
|
||||||
|
<div>{{#VFlip}}Flipped{{/VFlip}}{{^VFlip}}Normal{{/VFlip}}</div>
|
||||||
|
<div class="row">Downsize enable:</div>
|
||||||
|
<div>{{#Dcw}}Enabled{{/Dcw}}{{^Dcw}}Disabled{{/Dcw}}</div>
|
||||||
|
<div class="row">Color bar:</div>
|
||||||
|
<div>{{#ColorBar}}Enabled{{/ColorBar}}{{^ColorBar}}Camera{{/ColorBar}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="card bg-light mb-3">
|
|
||||||
<h5 class="card-header">Camera</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Frame rate:</div>
|
|
||||||
<div class="col-8">{{FrameDuration}} ms ({{FrameFrequency}} f/s)</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Frame size:</div>
|
|
||||||
<div class="col-8">{{FrameSize}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Frame buffer location:</div>
|
|
||||||
<div class="col-8">{{FrameBufferLocation}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Frame buffers:</div>
|
|
||||||
<div class="col-8">{{FrameBuffers}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">JPEG quality:</div>
|
|
||||||
<div class="col-8">{{JpegQuality}} [1-100]</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Brightness:</div>
|
|
||||||
<div class="col-8">{{Brightness}} [-2,2]</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Contrast:</div>
|
|
||||||
<div class="col-8">{{Contrast}} [-2,2]</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Saturation:</div>
|
|
||||||
<div class="col-8">{{Saturation}} [-2,2]</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Special effect:</div>
|
|
||||||
<div class="col-8">{{SpecialEffect}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">White balance:</div>
|
|
||||||
<div class="col-8">{{WhiteBal}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">AWB gain:</div>
|
|
||||||
<div class="col-8">{{AwbGain}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">WB mode:</div>
|
|
||||||
<div class="col-8">{{WbMode}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Exposure control:</div>
|
|
||||||
<div class="col-8">{{ExposureCtrl}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Auto exposure control (dsp):</div>
|
|
||||||
<div class="col-8">{{Aec2}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Auto Exposure level:</div>
|
|
||||||
<div class="col-8">{{AeLevel}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Manual exposure value:</div>
|
|
||||||
<div class="col-8">{{AecValue}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Gain control:</div>
|
|
||||||
<div class="col-8">{{GainCtrl}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">AGC gain:</div>
|
|
||||||
<div class="col-8">{{AgcGain}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Gain ceiling:</div>
|
|
||||||
<div class="col-8">{{GainCeiling}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Black pixel correct:</div>
|
|
||||||
<div class="col-8">{{Bpc}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">White pixel correct:</div>
|
|
||||||
<div class="col-8">{{Wpc}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Gamma correct:</div>
|
|
||||||
<div class="col-8">{{RawGma}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Lens correction:</div>
|
|
||||||
<div class="col-8">{{Lenc}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Horizontal mirror:</div>
|
|
||||||
<div class="col-8">{{HMirror}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Vertical flip:</div>
|
|
||||||
<div class="col-8">{{VFlip}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Downsize enable:</div>
|
|
||||||
<div class="col-8">{{Dcw}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">Color bar:</div>
|
|
||||||
<div class="col-8">{{ColorBar}}</div>
|
|
||||||
</div>
|
|
||||||
{{#CameraInitialized}}
|
{{#CameraInitialized}}
|
||||||
<div class="mt-4 alert alert-success" role="alert">
|
<div class="alert alert-success">
|
||||||
<p>Camera was initialized successfully!</p>
|
<h3 class="text-center">Camera was initialized successfully!</h3>
|
||||||
</div>
|
</div>
|
||||||
{{/CameraInitialized}}
|
{{/CameraInitialized}}
|
||||||
{{^CameraInitialized}}
|
{{^CameraInitialized}}
|
||||||
<div class="mt-4 alert alert-danger" role="alert">
|
<div class="alert alert-danger">
|
||||||
<p>Failed to initialize the camera!</p>
|
<h3 class="text-center">Failed to initialize the camera!<br>
|
||||||
<p>Result: {{CameraInitResultText}}</p>
|
Result: {{CameraInitResult}} ({{CameraInitResultText}})<br>
|
||||||
<p>Please check hardware or correct the camera settings and restart.</p>
|
Please check hardware or correct the camera settings and restart.<br><br>
|
||||||
<button type="button" class="btn btn-danger"
|
<button type="button" class="btn btn-danger" onclick="location.href='restart'">Restart</button>
|
||||||
onclick="location.href='restart'">Restart</button>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{{/CameraInitialized}}
|
{{/CameraInitialized}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="card bg-light mb-3">
|
|
||||||
<h5 class="card-header">Special URLs / API</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<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 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>
|
|
||||||
|
|
||||||
|
<h2 class="text-center">Special URLs / API</h2>
|
||||||
|
<div class="flex-table">
|
||||||
|
<div class="row">RTSP camera stream:</div>
|
||||||
|
<div><a href="rtsp://{{IpV4}}:{{RtspPort}}/mjpeg/1">rtsp://{{IpV4}}:{{RtspPort}}/mjpeg/1</a></div>
|
||||||
|
<div class="row">JPEG Motion stream:</div>
|
||||||
|
<div><a href="http://{{IpV4}}/stream" target="_blank">http://{{IpV4}}/stream</a></div>
|
||||||
|
<div class="row">Snapshot of the camera:</div>
|
||||||
|
<div><a href="http://{{IpV4}}/snapshot " target="_blank">http://{{IpV4}}/snapshot</a> </div>
|
||||||
|
<div class="row">Intensity of the flash led (0-255):</div>
|
||||||
|
<div><a href="http://{{IpV4}}/flash?v=0">http://{{IpV4}}/flash?v=0</a> (Authentication required)</div>
|
||||||
|
<div class="row">Restart the camera:</div>
|
||||||
|
<div><a href="http://{{IpV4}}/restart">http://{{IpV4}}/restart</a> (Authentication required)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
1
html/index.min.html
Normal file
1
html/index.min.html
Normal file
File diff suppressed because one or more lines are too long
@@ -4,29 +4,42 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<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="10;url=/index.html">
|
||||||
<meta http-equiv="refresh" content="1;url=/index.html">
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
color: #222;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: Arial, Verdana, Helvetica, sans-serif;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<title>{{AppTitle}} v{{AppVersion}}</title>
|
<title>{{AppTitle}} v{{AppVersion}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
|
||||||
<h1 class="text-center">{{ThingName}}</h1>
|
<h1 class="text-center">{{ThingName}}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="jumbotron bg-light">
|
<div class="alert-info">
|
||||||
<h1 class="display-4 text-center">Restart</h1>
|
<h3 class="text-center">Restarting</h3>
|
||||||
<p class="lead text-center">The device is restarting.</p>
|
<h4 class="text-center">
|
||||||
<hr class="my-4 ">
|
The device is restarting...<br><br>
|
||||||
<p class="text-center">In some cases, the device requires a hard reset (power cycle).</p>
|
If this page takes longer than a minute, consider performing a power cycle.
|
||||||
<p class="text-center">If this page takes longer than a minute, consider performing a power cycle.</p>
|
</h4>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
1
html/restart.min.html
Normal file
1
html/restart.min.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!doctypehtml><html lang=en><meta charset=utf-8><meta content=width=device-width,initial-scale=1,shrink-to-fit=no name=viewport><meta content=10;url=/index.html http-equiv=refresh><style>body,html{color:#222;font:16px Arial,Verdana,Helvetica,sans-serif;min-height:100%}.text-center{text-align:center}.alert-info{color:#31708f;background:#d9edf7;border:#bce8f1}</style><title>{{AppTitle}} v{{AppVersion}}</title><body><h1 class=text-center>{{ThingName}}</h1><hr><div class=alert-info><h3 class=text-center>Restarting</h3><h4 class=text-center>The device is restarting...<br><br> If this page takes longer than a minute, consider performing a power cycle.</h4></div>
|
||||||
7
html_gzip/bootstrap.min.css
vendored
7
html_gzip/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import htmlmin
|
|
||||||
|
|
||||||
if (len(sys.argv) <= 2):
|
|
||||||
print('Usage: html_to_cpp.py <input_dir> <file.h>')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
input_dir = sys.argv[1]
|
|
||||||
file_h = sys.argv[2]
|
|
||||||
|
|
||||||
file_names = os.listdir(input_dir)
|
|
||||||
file_names = filter(lambda x: x[0] != '.' and os.path.isfile(os.path.join(input_dir, x)), file_names)
|
|
||||||
file_names = sorted(file_names)
|
|
||||||
|
|
||||||
output_file = open(file_h, 'w')
|
|
||||||
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}... ')
|
|
||||||
|
|
||||||
file_path = os.path.join(input_dir, file_name)
|
|
||||||
file_data_name = f'file_data_{file_name}'.replace('.', '_')
|
|
||||||
|
|
||||||
file = open(file_path, 'r')
|
|
||||||
html = file.read()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
html_mimified = htmlmin.minify(html, remove_empty_space=True)
|
|
||||||
# escape "
|
|
||||||
html_mimified_escaped = html_mimified.replace('"', '\\"')
|
|
||||||
output_file.write(f'constexpr char {file_data_name }[] = "{html_mimified_escaped}";\n')
|
|
||||||
|
|
||||||
output_file.close()
|
|
||||||
|
|
||||||
print('Done.')
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import htmlmin
|
|
||||||
import gzip
|
|
||||||
|
|
||||||
if (len(sys.argv) <= 2):
|
|
||||||
print('Usage: bin_to_cpp_gzip.py <input_dir> <file.h>')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
input_dir = sys.argv[1]
|
|
||||||
file_h = sys.argv[2]
|
|
||||||
|
|
||||||
file_names = os.listdir(input_dir)
|
|
||||||
file_names = filter(lambda x: x[0] != '.' and
|
|
||||||
os.path.isfile(os.path.join(input_dir, x)), file_names)
|
|
||||||
file_names = sorted(file_names)
|
|
||||||
|
|
||||||
output_file = open(file_h, 'w')
|
|
||||||
output_file.write(
|
|
||||||
'//*******************************************************************************\n'
|
|
||||||
'// HTML import gzipped\n'
|
|
||||||
'// Machine generated file\n'
|
|
||||||
'// ******************************************************************************\n'
|
|
||||||
'\n\n')
|
|
||||||
|
|
||||||
for file_name in file_names:
|
|
||||||
print(f'Processing: {file_name}... ')
|
|
||||||
|
|
||||||
file_path = os.path.join(input_dir, file_name)
|
|
||||||
file_data_name = f'file_data_{file_name}'.replace('.', '_')
|
|
||||||
|
|
||||||
file = open(file_path, 'r')
|
|
||||||
html = file.read()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
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 i in html_mimified_gzip)
|
|
||||||
|
|
||||||
output_file.write(f'constexpr unsigned char {file_data_name}[] = {{\n{html_mimified_gzip_values}\n}};\n\n')
|
|
||||||
|
|
||||||
output_file.close()
|
|
||||||
|
|
||||||
print('Done.')
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,16 +5,18 @@
|
|||||||
|
|
||||||
#define WIFI_SSID "ESP32CAM-RTSP"
|
#define WIFI_SSID "ESP32CAM-RTSP"
|
||||||
#define WIFI_PASSWORD nullptr
|
#define WIFI_PASSWORD nullptr
|
||||||
#define CONFIG_VERSION "1.4"
|
#define CONFIG_VERSION "1.5"
|
||||||
|
|
||||||
#define OTA_PASSWORD "ESP32CAM-RTSP"
|
#define OTA_PASSWORD "ESP32CAM-RTSP"
|
||||||
|
|
||||||
#define RTSP_PORT 554
|
#define RTSP_PORT 554
|
||||||
|
|
||||||
#define DEFAULT_CAMERA_CONFIG "AI THINKER"
|
#define DEFAULT_CAMERA_CONFIG "AI THINKER"
|
||||||
#define DEFAULT_FRAME_DURATION 100
|
#define DEFAULT_ENABLE_PSRAM psramFound()
|
||||||
#define DEFAULT_FRAME_SIZE (psramFound() ? "UXGA (1600x1200)" : "VGA (640x480)")
|
#define DEFAULT_BUFFERS (psramFound() ? 2 : 1)
|
||||||
#define DEFAULT_JPEG_QUALITY (psramFound() ? 10 : 12)
|
#define DEFAULT_FRAME_DURATION 200
|
||||||
|
#define DEFAULT_FRAME_SIZE "VGA (640x480)"
|
||||||
|
#define DEFAULT_JPEG_QUALITY (psramFound() ? 12 : 14)
|
||||||
|
|
||||||
#define DEFAULT_BRIGHTNESS 0
|
#define DEFAULT_BRIGHTNESS 0
|
||||||
#define DEFAULT_CONTRAST 0
|
#define DEFAULT_CONTRAST 0
|
||||||
|
|||||||
20
minify.py
Normal file
20
minify.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import minify_html
|
||||||
|
|
||||||
|
if (len(sys.argv) <= 2):
|
||||||
|
print('Usage: minify.py input.html output.html')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_file = open(sys.argv[1], 'r')
|
||||||
|
output_file = open(sys.argv[2], 'w')
|
||||||
|
|
||||||
|
html = input_file.read()
|
||||||
|
input_file.close()
|
||||||
|
|
||||||
|
html_minified = minify_html.minify(html, minify_css=True)
|
||||||
|
output_file.write(html_minified)
|
||||||
|
|
||||||
|
output_file.close()
|
||||||
|
print('Done.')
|
||||||
@@ -35,6 +35,10 @@ build_flags =
|
|||||||
-mfix-esp32-psram-cache-issue
|
-mfix-esp32-psram-cache-issue
|
||||||
-D IOTWEBCONF_PASSWORD_LEN=64
|
-D IOTWEBCONF_PASSWORD_LEN=64
|
||||||
|
|
||||||
|
board_build.embed_txtfiles =
|
||||||
|
html/index.min.html
|
||||||
|
html/restart.min.html
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
prampec/IotWebConf @ ^3.2.1
|
prampec/IotWebConf @ ^3.2.1
|
||||||
geeksville/Micro-RTSP @ ^0.1.6
|
geeksville/Micro-RTSP @ ^0.1.6
|
||||||
|
|||||||
130
src/main.cpp
130
src/main.cpp
@@ -1,5 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
|
#include <esp_wifi.h>
|
||||||
#include <soc/rtc_cntl_reg.h>
|
#include <soc/rtc_cntl_reg.h>
|
||||||
#include <IotWebConf.h>
|
#include <IotWebConf.h>
|
||||||
#include <IotWebConfTParameter.h>
|
#include <IotWebConfTParameter.h>
|
||||||
@@ -14,10 +15,14 @@
|
|||||||
#include <format_duration.h>
|
#include <format_duration.h>
|
||||||
#include <format_number.h>
|
#include <format_number.h>
|
||||||
#include <moustache.h>
|
#include <moustache.h>
|
||||||
#include <html_data.h>
|
|
||||||
#include <html_data_gzip.h>
|
|
||||||
#include <settings.h>
|
#include <settings.h>
|
||||||
|
|
||||||
|
extern "C" uint8_t temprature_sens_read();
|
||||||
|
|
||||||
|
// HTML files
|
||||||
|
extern const char index_html_min_start[] asm("_binary_html_index_min_html_start");
|
||||||
|
extern const char restart_html_min_start[] asm("_binary_html_restart_min_html_start");
|
||||||
|
|
||||||
auto param_group_board = iotwebconf::ParameterGroup("board", "Board settings");
|
auto param_group_board = iotwebconf::ParameterGroup("board", "Board settings");
|
||||||
auto param_board = iotwebconf::Builder<iotwebconf::SelectTParameter<sizeof(camera_configs[0])>>("bt").label("Board").optionValues((const char *)&camera_configs).optionNames((const char *)&camera_configs).optionCount(sizeof(camera_configs) / sizeof(camera_configs[0])).nameLength(sizeof(camera_configs[0])).defaultValue(DEFAULT_CAMERA_CONFIG).build();
|
auto param_board = iotwebconf::Builder<iotwebconf::SelectTParameter<sizeof(camera_configs[0])>>("bt").label("Board").optionValues((const char *)&camera_configs).optionNames((const char *)&camera_configs).optionCount(sizeof(camera_configs) / sizeof(camera_configs[0])).nameLength(sizeof(camera_configs[0])).defaultValue(DEFAULT_CAMERA_CONFIG).build();
|
||||||
|
|
||||||
@@ -25,6 +30,8 @@ auto param_group_camera = iotwebconf::ParameterGroup("camera", "Camera settings"
|
|||||||
auto param_frame_duration = iotwebconf::Builder<iotwebconf::UIntTParameter<unsigned long>>("fd").label("Frame duration (ms)").defaultValue(DEFAULT_FRAME_DURATION).min(10).build();
|
auto param_frame_duration = iotwebconf::Builder<iotwebconf::UIntTParameter<unsigned long>>("fd").label("Frame duration (ms)").defaultValue(DEFAULT_FRAME_DURATION).min(10).build();
|
||||||
auto param_frame_size = iotwebconf::Builder<iotwebconf::SelectTParameter<sizeof(frame_sizes[0])>>("fs").label("Frame size").optionValues((const char *)&frame_sizes).optionNames((const char *)&frame_sizes).optionCount(sizeof(frame_sizes) / sizeof(frame_sizes[0])).nameLength(sizeof(frame_sizes[0])).defaultValue(DEFAULT_FRAME_SIZE).build();
|
auto param_frame_size = iotwebconf::Builder<iotwebconf::SelectTParameter<sizeof(frame_sizes[0])>>("fs").label("Frame size").optionValues((const char *)&frame_sizes).optionNames((const char *)&frame_sizes).optionCount(sizeof(frame_sizes) / sizeof(frame_sizes[0])).nameLength(sizeof(frame_sizes[0])).defaultValue(DEFAULT_FRAME_SIZE).build();
|
||||||
auto param_jpg_quality = iotwebconf::Builder<iotwebconf::UIntTParameter<byte>>("q").label("JPG quality").defaultValue(DEFAULT_JPEG_QUALITY).min(1).max(100).build();
|
auto param_jpg_quality = iotwebconf::Builder<iotwebconf::UIntTParameter<byte>>("q").label("JPG quality").defaultValue(DEFAULT_JPEG_QUALITY).min(1).max(100).build();
|
||||||
|
auto param_enable_psram = iotwebconf::Builder<iotwebconf::CheckboxTParameter>("eps").label("Enable PSRAM if available").defaultValue(DEFAULT_ENABLE_PSRAM).build();
|
||||||
|
auto param_frame_buffers = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("fb").label("Buffers").defaultValue(DEFAULT_BUFFERS).min(1).max(4).build();
|
||||||
auto param_brightness = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("b").label("Brightness").defaultValue(DEFAULT_BRIGHTNESS).min(-2).max(2).build();
|
auto param_brightness = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("b").label("Brightness").defaultValue(DEFAULT_BRIGHTNESS).min(-2).max(2).build();
|
||||||
auto param_contrast = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("c").label("Contrast").defaultValue(DEFAULT_CONTRAST).min(-2).max(2).build();
|
auto param_contrast = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("c").label("Contrast").defaultValue(DEFAULT_CONTRAST).min(-2).max(2).build();
|
||||||
auto param_saturation = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("s").label("Saturation").defaultValue(DEFAULT_SATURATION).min(-2).max(2).build();
|
auto param_saturation = iotwebconf::Builder<iotwebconf::IntTParameter<int>>("s").label("Saturation").defaultValue(DEFAULT_SATURATION).min(-2).max(2).build();
|
||||||
@@ -59,7 +66,9 @@ DNSServer dnsServer;
|
|||||||
std::unique_ptr<rtsp_server> camera_server;
|
std::unique_ptr<rtsp_server> camera_server;
|
||||||
// Web server
|
// Web server
|
||||||
WebServer web_server(80);
|
WebServer web_server(80);
|
||||||
IotWebConf iotWebConf(WIFI_SSID, &dnsServer, &web_server, WIFI_PASSWORD, CONFIG_VERSION);
|
|
||||||
|
auto thingName = String(WIFI_SSID) + "-" + String(ESP.getEfuseMac(), 16);
|
||||||
|
IotWebConf iotWebConf(thingName.c_str(), &dnsServer, &web_server, WIFI_PASSWORD, CONFIG_VERSION);
|
||||||
|
|
||||||
// Keep track of config changes. This will allow a reset of the device
|
// Keep track of config changes. This will allow a reset of the device
|
||||||
bool config_changed = false;
|
bool config_changed = false;
|
||||||
@@ -90,6 +99,8 @@ void handle_root()
|
|||||||
|
|
||||||
// Wifi Modes
|
// Wifi Modes
|
||||||
const char *wifi_modes[] = {"NULL", "STA", "AP", "STA+AP"};
|
const char *wifi_modes[] = {"NULL", "STA", "AP", "STA+AP"};
|
||||||
|
auto ipv4 = WiFi.getMode() == WIFI_MODE_AP ? WiFi.softAPIP() : WiFi.localIP();
|
||||||
|
auto ipv6 = WiFi.getMode() == WIFI_MODE_AP ? WiFi.softAPIPv6() : WiFi.localIPv6();
|
||||||
|
|
||||||
moustache_variable_t substitutions[] = {
|
moustache_variable_t substitutions[] = {
|
||||||
// Config Changed?
|
// Config Changed?
|
||||||
@@ -98,6 +109,7 @@ void handle_root()
|
|||||||
{"AppTitle", APP_TITLE},
|
{"AppTitle", APP_TITLE},
|
||||||
{"AppVersion", APP_VERSION},
|
{"AppVersion", APP_VERSION},
|
||||||
{"ThingName", iotWebConf.getThingName()},
|
{"ThingName", iotWebConf.getThingName()},
|
||||||
|
{"SDKVersion", ESP.getSdkVersion()},
|
||||||
{"ChipModel", ESP.getChipModel()},
|
{"ChipModel", ESP.getChipModel()},
|
||||||
{"ChipRevision", String(ESP.getChipRevision())},
|
{"ChipRevision", String(ESP.getChipRevision())},
|
||||||
{"CpuFreqMHz", String(ESP.getCpuFreqMHz())},
|
{"CpuFreqMHz", String(ESP.getCpuFreqMHz())},
|
||||||
@@ -107,17 +119,18 @@ void handle_root()
|
|||||||
{"PsRamSize", format_memory(ESP.getPsramSize(), 0)},
|
{"PsRamSize", format_memory(ESP.getPsramSize(), 0)},
|
||||||
// Diagnostics
|
// Diagnostics
|
||||||
{"Uptime", String(format_duration(millis() / 1000))},
|
{"Uptime", String(format_duration(millis() / 1000))},
|
||||||
|
{"Temperature", String((temprature_sens_read() - 32) / 1.8)},
|
||||||
{"FreeHeap", format_memory(ESP.getFreeHeap())},
|
{"FreeHeap", format_memory(ESP.getFreeHeap())},
|
||||||
{"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())},
|
{"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())},
|
||||||
{"NumRTSPSessions", camera_server != nullptr ? String(camera_server->num_connected()) : "N/A"},
|
{"NumRTSPSessions", camera_server != nullptr ? String(camera_server->num_connected()) : "RTSP server disabled"},
|
||||||
// Network
|
// Network
|
||||||
{"HostName", hostname},
|
{"HostName", hostname},
|
||||||
{"MacAddress", WiFi.macAddress()},
|
{"MacAddress", WiFi.macAddress()},
|
||||||
{"AccessPoint", WiFi.SSID()},
|
{"AccessPoint", WiFi.SSID()},
|
||||||
{"SignalStrength", String(WiFi.RSSI())},
|
{"SignalStrength", String(WiFi.RSSI())},
|
||||||
{"IpV4", WiFi.localIP().toString()},
|
|
||||||
{"IpV6", WiFi.localIPv6().toString()},
|
|
||||||
{"WifiMode", wifi_modes[WiFi.getMode()]},
|
{"WifiMode", wifi_modes[WiFi.getMode()]},
|
||||||
|
{"IpV4", ipv4.toString()},
|
||||||
|
{"IpV6", ipv6.toString()},
|
||||||
{"NetworkState.ApMode", String(iotWebConf.getState() == iotwebconf::NetworkState::ApMode)},
|
{"NetworkState.ApMode", String(iotWebConf.getState() == iotwebconf::NetworkState::ApMode)},
|
||||||
{"NetworkState.OnLine", String(iotWebConf.getState() == iotwebconf::NetworkState::OnLine)},
|
{"NetworkState.OnLine", String(iotWebConf.getState() == iotwebconf::NetworkState::OnLine)},
|
||||||
// Camera
|
// Camera
|
||||||
@@ -125,10 +138,11 @@ void handle_root()
|
|||||||
{"FrameSize", String(param_frame_size.value())},
|
{"FrameSize", String(param_frame_size.value())},
|
||||||
{"FrameDuration", String(param_frame_duration.value())},
|
{"FrameDuration", String(param_frame_duration.value())},
|
||||||
{"FrameFrequency", String(1000.0 / param_frame_duration.value(), 1)},
|
{"FrameFrequency", String(1000.0 / param_frame_duration.value(), 1)},
|
||||||
{"FrameBufferLocation", psramFound() ? "PSRAM" : "DRAM)"},
|
|
||||||
{"FrameBuffers", String(psramFound() ? 2 : 1)},
|
|
||||||
{"JpegQuality", String(param_jpg_quality.value())},
|
{"JpegQuality", String(param_jpg_quality.value())},
|
||||||
|
{"EnablePSRAM", String(param_enable_psram.value())},
|
||||||
|
{"FrameBuffers", String(param_frame_buffers.value())},
|
||||||
{"CameraInitialized", String(camera_init_result == ESP_OK)},
|
{"CameraInitialized", String(camera_init_result == ESP_OK)},
|
||||||
|
{"CameraInitResult", String(camera_init_result)},
|
||||||
{"CameraInitResultText", esp_err_to_name(camera_init_result)},
|
{"CameraInitResultText", esp_err_to_name(camera_init_result)},
|
||||||
// Settings
|
// Settings
|
||||||
{"Brightness", String(param_brightness.value())},
|
{"Brightness", String(param_brightness.value())},
|
||||||
@@ -159,7 +173,7 @@ void handle_root()
|
|||||||
{"RtspPort", String(RTSP_PORT)}};
|
{"RtspPort", String(RTSP_PORT)}};
|
||||||
|
|
||||||
web_server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
web_server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
auto html = moustache_render(file_data_index_html, substitutions);
|
auto html = moustache_render(index_html_min_start, substitutions);
|
||||||
web_server.send(200, "text/html", html);
|
web_server.send(200, "text/html", html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +192,7 @@ void handle_restart()
|
|||||||
{"AppVersion", APP_VERSION},
|
{"AppVersion", APP_VERSION},
|
||||||
{"ThingName", iotWebConf.getThingName()}};
|
{"ThingName", iotWebConf.getThingName()}};
|
||||||
|
|
||||||
auto html = moustache_render(file_data_restart_html, substitutions);
|
auto html = moustache_render(restart_html_min_start, substitutions);
|
||||||
web_server.send(200, "text/html", html);
|
web_server.send(200, "text/html", html);
|
||||||
log_v("Restarting... Press refresh to connect again");
|
log_v("Restarting... Press refresh to connect again");
|
||||||
sleep(100);
|
sleep(100);
|
||||||
@@ -195,7 +209,7 @@ void handle_snapshot()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove old images stored in the frame buffer
|
// Remove old images stored in the frame buffer
|
||||||
auto frame_buffers = psramFound() ? 2 : 1;
|
auto frame_buffers = param_frame_buffers.value();
|
||||||
while (frame_buffers--)
|
while (frame_buffers--)
|
||||||
cam.run();
|
cam.run();
|
||||||
|
|
||||||
@@ -213,6 +227,37 @@ void handle_snapshot()
|
|||||||
web_server.sendContent(fb, fb_len);
|
web_server.sendContent(fb, fb_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define STREAM_CONTENT_BOUNDARY "123456789000000000000987654321"
|
||||||
|
|
||||||
|
void handle_stream()
|
||||||
|
{
|
||||||
|
log_v("handle_stream");
|
||||||
|
if (camera_init_result != ESP_OK)
|
||||||
|
{
|
||||||
|
web_server.send(404, "text/plain", "Camera is not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_v("starting streaming");
|
||||||
|
// Blocks further handling of HTTP server until stopped
|
||||||
|
char size_buf[12];
|
||||||
|
auto client = web_server.client();
|
||||||
|
client.write("HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace; boundary=" STREAM_CONTENT_BOUNDARY "\r\n");
|
||||||
|
while (client.connected())
|
||||||
|
{
|
||||||
|
client.write("\r\n--" STREAM_CONTENT_BOUNDARY "\r\n");
|
||||||
|
cam.run();
|
||||||
|
client.write("Content-Type: image/jpeg\r\nContent-Length: ");
|
||||||
|
sprintf(size_buf, "%d\r\n\r\n", cam.getSize());
|
||||||
|
client.write(size_buf);
|
||||||
|
client.write(cam.getfb(), cam.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
log_v("client disconnected");
|
||||||
|
client.stop();
|
||||||
|
log_v("stopped streaming");
|
||||||
|
}
|
||||||
|
|
||||||
void handle_flash()
|
void handle_flash()
|
||||||
{
|
{
|
||||||
log_v("handle_flash");
|
log_v("handle_flash");
|
||||||
@@ -243,24 +288,33 @@ esp_err_t initialize_camera()
|
|||||||
{
|
{
|
||||||
log_v("initialize_camera");
|
log_v("initialize_camera");
|
||||||
log_i("Camera config: %s", param_board.value());
|
log_i("Camera config: %s", param_board.value());
|
||||||
auto camera_config = lookup_camera_config(param_board.value());
|
auto camera_config_template = lookup_camera_config(param_board.value());
|
||||||
|
// Copy the settings
|
||||||
|
camera_config_t camera_config;
|
||||||
|
memset(&camera_config, 0, sizeof(camera_config_t));
|
||||||
|
memcpy(&camera_config, &camera_config_template, sizeof(camera_config_t));
|
||||||
log_i("Frame size: %s", param_frame_size.value());
|
log_i("Frame size: %s", param_frame_size.value());
|
||||||
auto frame_size = lookup_frame_size(param_frame_size.value());
|
auto frame_size = lookup_frame_size(param_frame_size.value());
|
||||||
log_i("JPEG quality: %d", param_jpg_quality.value());
|
log_i("JPEG quality: %d", param_jpg_quality.value());
|
||||||
log_i("Frame duration: %d ms", param_frame_duration.value());
|
log_i("Frame duration: %d ms", param_frame_duration.value());
|
||||||
|
|
||||||
camera_config.frame_size = frame_size;
|
camera_config.frame_size = frame_size;
|
||||||
camera_config.jpeg_quality = param_jpg_quality.value();
|
camera_config.jpeg_quality = param_jpg_quality.value();
|
||||||
if (psramFound())
|
camera_config.grab_mode = CAMERA_GRAB_LATEST;
|
||||||
|
log_i("Enable PSRAM: %d", param_enable_psram.value());
|
||||||
|
log_i("Frame buffers: %d", param_frame_buffers.value());
|
||||||
|
camera_config.fb_count = param_frame_buffers.value();
|
||||||
|
|
||||||
|
if (param_enable_psram.value() && psramFound())
|
||||||
{
|
{
|
||||||
camera_config.fb_count = 2;
|
|
||||||
camera_config.fb_location = CAMERA_FB_IN_PSRAM;
|
camera_config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||||
|
log_i("PSRAM enabled!");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
camera_config.fb_count = 1;
|
|
||||||
camera_config.fb_location = CAMERA_FB_IN_DRAM;
|
camera_config.fb_location = CAMERA_FB_IN_DRAM;
|
||||||
|
log_i("PSRAM disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
return cam.init(camera_config);
|
return cam.init(camera_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,22 +355,21 @@ void start_rtsp_server()
|
|||||||
{
|
{
|
||||||
log_v("start_rtsp_server");
|
log_v("start_rtsp_server");
|
||||||
camera_server = std::unique_ptr<rtsp_server>(new rtsp_server(cam, param_frame_duration.value(), RTSP_PORT));
|
camera_server = std::unique_ptr<rtsp_server>(new rtsp_server(cam, param_frame_duration.value(), RTSP_PORT));
|
||||||
// Add service to mDNS - rtsp
|
// Add RTSP service to mDNS
|
||||||
|
// HTTP is already set by iotWebConf
|
||||||
MDNS.addService("rtsp", "tcp", 554);
|
MDNS.addService("rtsp", "tcp", 554);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_connected()
|
void on_connected()
|
||||||
{
|
{
|
||||||
log_v("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, param_led_intensity.value());
|
|
||||||
// Start (OTA) Over The Air programming when connected
|
// Start (OTA) Over The Air programming when connected
|
||||||
ArduinoOTA.begin();
|
ArduinoOTA.begin();
|
||||||
// Start the RTSP Server if initializef
|
// Start the RTSP Server if initialized
|
||||||
if (camera_init_result == ESP_OK)
|
if (camera_init_result == ESP_OK)
|
||||||
start_rtsp_server();
|
start_rtsp_server();
|
||||||
|
else
|
||||||
|
log_e("Not starting RTSP server: camera not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_config_saved()
|
void on_config_saved()
|
||||||
@@ -334,8 +387,8 @@ void setup()
|
|||||||
// Disable brownout
|
// Disable brownout
|
||||||
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
|
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
|
||||||
|
|
||||||
|
// LED_BUILTIN (GPIO33) has inverted logic false => LED on
|
||||||
pinMode(LED_BUILTIN, OUTPUT);
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
// Turn LED on (has inverted logic GPIO33) => red LED on => not connected
|
|
||||||
digitalWrite(LED_BUILTIN, false);
|
digitalWrite(LED_BUILTIN, false);
|
||||||
|
|
||||||
pinMode(LED_FLASH, OUTPUT);
|
pinMode(LED_FLASH, OUTPUT);
|
||||||
@@ -347,16 +400,25 @@ void setup()
|
|||||||
Serial.setDebugOutput(true);
|
Serial.setDebugOutput(true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
log_i("CPU Freq: %d Mhz", getCpuFrequencyMhz());
|
log_i("CPU Freq: %d Mhz, %d core(s)", getCpuFrequencyMhz(), ESP.getChipCores());
|
||||||
log_i("Free heap: %d bytes", ESP.getFreeHeap());
|
log_i("Free heap: %d bytes", ESP.getFreeHeap());
|
||||||
|
log_i("SDK version: %s", ESP.getSdkVersion());
|
||||||
log_i("Starting " APP_TITLE "...");
|
log_i("Starting " APP_TITLE "...");
|
||||||
|
|
||||||
|
if (psramFound())
|
||||||
|
{
|
||||||
|
psramInit();
|
||||||
|
log_v("PSRAM found and initialized");
|
||||||
|
}
|
||||||
|
|
||||||
param_group_board.addItem(¶m_board);
|
param_group_board.addItem(¶m_board);
|
||||||
iotWebConf.addParameterGroup(¶m_group_board);
|
iotWebConf.addParameterGroup(¶m_group_board);
|
||||||
|
|
||||||
param_group_camera.addItem(¶m_frame_duration);
|
param_group_camera.addItem(¶m_frame_duration);
|
||||||
param_group_camera.addItem(¶m_frame_size);
|
param_group_camera.addItem(¶m_frame_size);
|
||||||
param_group_camera.addItem(¶m_jpg_quality);
|
param_group_camera.addItem(¶m_jpg_quality);
|
||||||
|
param_group_camera.addItem(¶m_enable_psram);
|
||||||
|
param_group_camera.addItem(¶m_frame_buffers);
|
||||||
param_group_camera.addItem(¶m_brightness);
|
param_group_camera.addItem(¶m_brightness);
|
||||||
param_group_camera.addItem(¶m_contrast);
|
param_group_camera.addItem(¶m_contrast);
|
||||||
param_group_camera.addItem(¶m_saturation);
|
param_group_camera.addItem(¶m_saturation);
|
||||||
@@ -387,13 +449,14 @@ void setup()
|
|||||||
iotWebConf.getApTimeoutParameter()->visible = true;
|
iotWebConf.getApTimeoutParameter()->visible = true;
|
||||||
iotWebConf.setConfigSavedCallback(on_config_saved);
|
iotWebConf.setConfigSavedCallback(on_config_saved);
|
||||||
iotWebConf.setWifiConnectionCallback(on_connected);
|
iotWebConf.setWifiConnectionCallback(on_connected);
|
||||||
|
iotWebConf.setStatusPin(LED_BUILTIN, LOW);
|
||||||
iotWebConf.init();
|
iotWebConf.init();
|
||||||
|
|
||||||
camera_init_result = initialize_camera();
|
camera_init_result = initialize_camera();
|
||||||
if (camera_init_result != ESP_OK)
|
if (camera_init_result == ESP_OK)
|
||||||
log_e("Failed to initialize camera: 0x%0x. Type: %s, frame size: %s, frame rate: %d ms, jpeg quality: %d", camera_init_result, param_board.value(), param_frame_size.value(), param_frame_duration.value(), param_jpg_quality.value());
|
|
||||||
else
|
|
||||||
update_camera_settings();
|
update_camera_settings();
|
||||||
|
else
|
||||||
|
log_e("Failed to initialize camera: 0x%0x. Type: %s, frame size: %s, frame rate: %d ms, jpeg quality: %d", camera_init_result, param_board.value(), param_frame_size.value(), param_frame_duration.value(), param_jpg_quality.value());
|
||||||
|
|
||||||
// Set up required URL handlers on the web server
|
// Set up required URL handlers on the web server
|
||||||
web_server.on("/", HTTP_GET, handle_root);
|
web_server.on("/", HTTP_GET, handle_root);
|
||||||
@@ -402,17 +465,16 @@ void setup()
|
|||||||
web_server.on("/restart", HTTP_GET, handle_restart);
|
web_server.on("/restart", HTTP_GET, handle_restart);
|
||||||
// Camera snapshot
|
// Camera snapshot
|
||||||
web_server.on("/snapshot", HTTP_GET, handle_snapshot);
|
web_server.on("/snapshot", HTTP_GET, handle_snapshot);
|
||||||
|
// Camera stream
|
||||||
|
web_server.on("/stream", HTTP_GET, handle_stream);
|
||||||
// Camera flash light
|
// Camera flash light
|
||||||
web_server.on("/flash", HTTP_GET, handle_flash);
|
web_server.on("/flash", HTTP_GET, handle_flash);
|
||||||
|
|
||||||
// bootstrap
|
|
||||||
web_server.on("/bootstrap.min.css", HTTP_GET, []()
|
|
||||||
{ stream_text_file_gzip(file_data_bootstrap_min_css, sizeof(file_data_bootstrap_min_css), "text/css"); });
|
|
||||||
|
|
||||||
web_server.onNotFound([]()
|
web_server.onNotFound([]()
|
||||||
{ iotWebConf.handleNotFound(); });
|
{ iotWebConf.handleNotFound(); });
|
||||||
|
|
||||||
ArduinoOTA
|
ArduinoOTA
|
||||||
|
.setPassword(OTA_PASSWORD)
|
||||||
.onStart([]()
|
.onStart([]()
|
||||||
{ log_w("Starting OTA update: %s", ArduinoOTA.getCommand() == U_FLASH ? "sketch" : "filesystem"); })
|
{ log_w("Starting OTA update: %s", ArduinoOTA.getCommand() == U_FLASH ? "sketch" : "filesystem"); })
|
||||||
.onEnd([]()
|
.onEnd([]()
|
||||||
@@ -430,7 +492,9 @@ void setup()
|
|||||||
case OTA_END_ERROR: log_e("OTA: End Failed"); break;
|
case OTA_END_ERROR: log_e("OTA: End Failed"); break;
|
||||||
default: log_e("OTA error: %u", error);
|
default: log_e("OTA error: %u", error);
|
||||||
} });
|
} });
|
||||||
ArduinoOTA.setPassword(OTA_PASSWORD);
|
|
||||||
|
// Set flash led intensity
|
||||||
|
analogWrite(LED_FLASH, param_led_intensity.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
@@ -440,4 +504,6 @@ void loop()
|
|||||||
|
|
||||||
if (camera_server)
|
if (camera_server)
|
||||||
camera_server->doLoop();
|
camera_server->doLoop();
|
||||||
|
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user