17 Commits

Author SHA1 Message Date
Rene
5e4fee3624 Update README.md 2023-12-18 23:50:10 +01:00
Rene
7a0259d878 Update README.md 2023-12-18 23:49:50 +01:00
Rene
f68e347c5b Update README.md 2023-10-17 16:12:14 +02:00
Rene
0d0223e8df Update README.md 2023-10-17 16:11:30 +02:00
Rene
45e9a92e3c Update README.md 2023-10-17 16:10:41 +02:00
rzeldent
3e6df1a56b Merge pull request #94 from rzeldent/bugfix/flashurl
Fix space in flashlight URL
2023-09-21 21:05:18 +02:00
Rene Zeldenthuis
a40f6a8ad8 Fix space in flashlight URL 2023-09-21 21:03:12 +02:00
rzeldent
aee7cbddd8 Merge pull request #78 from rzeldent/feature/temperature
- Added temperature
2023-06-16 18:23:35 +02:00
Rene Zeldenthuis
8c263be1a6 - Added temperature
- Added uptime
2023-06-16 18:20:35 +02:00
rzeldent
0dc7abc338 Merge pull request #76 from rzeldent:bugfix/default_settings
Changes to default configuration:
2023-06-08 23:20:28 +02:00
Rene Zeldenthuis
e88c7a6f62 Changes to default configuration:
- Frame duration 200ms
- Frame size VGA (640x480)
- Quality 12/14
2023-06-08 23:14:30 +02:00
Rene Zeldenthuis
a711eec80c Added cores to logging 2023-05-29 13:29:16 +02:00
rzeldent
5f80cabbed Merge pull request #62 from rzeldent/feature/remove_bootstrap
- Added CSS in html
2023-05-05 23:37:22 +08:00
Rene Zeldenthuis
621dbe466e - Added CSS in html
- Embed using platformIO
- new minify (also css)
2023-04-16 17:17:28 +02:00
rzeldent
72482080cf Merge pull request #55 from rzeldent/bugfix/ipv4ipv6
Bugfix/ipv4ipv6
2023-03-26 22:29:46 +02:00
Rene Zeldenthuis
97acc08edf Added logging 2023-03-26 22:26:12 +02:00
Rene Zeldenthuis
86dcf88d4e Added led flashing for connectivity 2023-03-26 21:54:24 +02:00
17 changed files with 368 additions and 461 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.*/*
.*.*
__pycache__/
*.log
.DS_Store
workspace.code-workspace

View File

@@ -1,9 +1,15 @@
# ESP32CAM-RTSP :video_camera:
# ESP32CAM-RTSP
[![Platform IO CI](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml/badge.svg)](https://github.com/rzeldent/esp32cam-rtsp/actions/workflows/main.yml)
Simple [RTSP](https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol), [HTTP JPEG Streamer](https://en.wikipedia.org/wiki/Motion_JPEG) and image server with configuration through the web interface.
> [!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

View File

@@ -1,5 +1,5 @@
. 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 ./html_to_cpp_gzip.py ./html_gzip ./include/html_data_gzip.h
. python3 ./minify.py ./html/index.html ./html/index.min.html
. python3 ./minify.py ./html/restart.html ./html/restart.min.html

View File

@@ -1,6 +1,7 @@
#!/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 ./html_to_cpp_gzip.py ./html_gzip ./include/html_data_gzip.h
python3 -m pip install --upgrade pip setuptools wheel
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

View File

@@ -4,310 +4,271 @@
<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">
<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>
</head>
<body>
<div class="container">
<h1 class="text-center">{{ThingName}}</h1>
<hr>
<h1 class="text-center">{{ThingName}}</h1>
<hr>
{{#ConfigChanged}}
<div class="alert alert-danger" role="alert">
<p>The configuration has been changed.</p>
<p>It is recommended to restart the device.</p>
<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}}
<div class="alert alert-danger">
<h3 class="text-center">
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>
</div>
{{/ConfigChanged}}
</h3>
</div>
{{/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">
<div class="col-sm-6">
<div class="card bg-light mb-3">
<h5 class="card-header">ESP32</h5>
<div class="card-body">
<div class="row">
<div class="col-4">SDK Version:</div>
<div class="col-8">{{SDKVersion}}</div>
</div>
<div class="row">
<div class="col-4">CPU model:</div>
<div class="col-8">{{ChipModel}} rev. {{ChipRevision}}</div>
</div>
<div class="row">
<div class="col-4">CPU speed:</div>
<div class="col-8">{{CpuFreqMHz}} Mhz</div>
</div>
<div class="row">
<div class="col-4">CPU cores:</div>
<div class="col-8">{{CpuCores}}</div>
</div>
<div class="row">
<div class="col-4">RAM size:</div>
<div class="col-8">{{HeapSize}}</div>
</div>
<div class="row">
<div class="col-4">PSRAM size:</div>
<div class="col-8">{{PsRamSize}}</div>
</div>
<div class="row">
<div class="col-4">Flash size:</div>
<div class="col-8">{{FlashSize}}</div>
</div>
</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>
{{#NetworkState.ApMode}}
<div class="mt-4 alert alert-warning" role="alert">
<h4 class="text-center">Not connected to an access point.<br>Consider configuring the access point.</h4>
</div>
{{/NetworkState.ApMode}}
{{#NetworkState.OnLine}}
<div class="mt-4 alert alert-success" role="alert">
<h4 class="text-center">Connected to the access point</h4>
</div>
{{/NetworkState.OnLine}}
</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">JPEG quality:</div>
<div class="col-8">{{JpegQuality}} [1-100]</div>
</div>
<div class="row">
<div class="col-4">Enable PSRAM:</div>
<div class="col-8">{{#EnablePSRAM}}Enabled{{/EnablePSRAM}}{{^EnablePSRAM}}Disabled{{/EnablePSRAM}}</div>
</div>
<div class="row">
<div class="col-4">Number of frame buffers:</div>
<div class="col-8">{{FrameBuffers}}</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}}Auto{{/WhiteBal}}{{^WhiteBal}}Manual{{/WhiteBal}}</div>
</div>
<div class="row">
<div class="col-4">AWB gain:</div>
<div class="col-8">{{#AwbGain}}Auto{{/AwbGain}}{{^AwbGain}}Manual{{/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}}Auto{{/ExposureCtrl}}{{^ExposureCtrl}}Manual{{/ExposureCtrl}}</div>
</div>
<div class="row">
<div class="col-4">Auto exposure control (dsp):</div>
<div class="col-8">{{#Aec2}}Enabled{{/Aec2}}{{^Aec2}}Disabled{{/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}}Auto{{/GainCtrl}}{{^GainCtrl}}Manual{{/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}}Auto{{/Bpc}}{{^Bpc}}Manual{{/Bpc}}</div>
</div>
<div class="row">
<div class="col-4">White pixel correct:</div>
<div class="col-8">{{#Wpc}}Auto{{/Wpc}}{{^Wpc}}Manual{{/Wpc}}</div>
</div>
<div class="row">
<div class="col-4">Gamma correct:</div>
<div class="col-8">{{#RawGma}}Enabled{{/RawGma}}{{^RawGma}}Disabled{{/RawGma}}</div>
</div>
<div class="row">
<div class="col-4">Lens correction:</div>
<div class="col-8">{{#Lenc}}Enabled{{/Lenc}}{{^Lenc}}Disabled{{/Lenc}}</div>
</div>
<div class="row">
<div class="col-4">Horizontal mirror:</div>
<div class="col-8">{{#HMirror}}Mirrored{{/HMirror}}{{^HMirror}}Normal{{/HMirror}}</div>
</div>
<div class="row">
<div class="col-4">Vertical flip:</div>
<div class="col-8">{{#VFlip}}Flipped{{/VFlip}}{{^VFlip}}Normal{{/VFlip}}</div>
</div>
<div class="row">
<div class="col-4">Downsize enable:</div>
<div class="col-8">{{#Dcw}}Enabled{{/Dcw}}{{^Dcw}}Disabled{{/Dcw}}</div>
</div>
<div class="row">
<div class="col-4">Color bar:</div>
<div class="col-8">{{#ColorBar}}Enabled{{/ColorBar}}{{^ColorBar}}Camera{{/ColorBar}}</div>
</div>
{{#CameraInitialized}}
<div class="mt-4 alert alert-success" role="alert">
<h4 class="text-center">Camera was initialized successfully!</h4>
</div>
{{/CameraInitialized}}
{{^CameraInitialized}}
<div class="mt-4 alert alert-danger" role="alert">
<h4 class="text-center">Failed to initialize the camera!</h4>
<p>Result: {{CameraInitResult}} ({{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>
</div>
{{/CameraInitialized}}
</div>
</div>
</div>
</div>
<h2 class="text-center">Diagnostics</h2>
<div class="flex-table">
<div class="row">Uptime:</div>
<div>{{Uptime}}</div>
<div class="row">Chip temperature:</div>
<div>{{Temperature}} &deg;C</div>
<div class="row">RTSP sessions:</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 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>
RTSP camera stream: <a href="rtsp://{{IpV4}}:{{RtspPort}}/mjpeg/1">rtsp://{{IpV4}}:{{RtspPort}}/mjpeg/1</a>
</span>
</div>
<div class="row">
<span>
JPEG Motion stream: <a href="http://{{IpV4}}/stream" target="_blank">http://{{IpV4}}/stream</a>
</span>
</div>
<div class="row">
<span>
Snapshot of the camera: <a href="http://{{IpV4}}/snapshot " target="_blank">http://{{IpV4}}/snapshot</a>
</span>
</div>
<div class="row">
<span>
Intensity of the flash led (0-255): <a href="http://{{IpV4}}/flash?v=0">http://{{IpV4}}/flash?v=0</a>. Authentication is required.
</span>
</div>
<div class="row">
<span>
Restart the camera: <a href="http://{{IpV4}}/restart">http://{{IpV4}}/restart</a>. Authentication is required.
</span>
</div>
</div>
</div>
</div>
<h2 class="text-center">Peripheral</h2>
<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 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">Network</h2>
<div class="flex-table">
<div class="row">Host name:</div>
<div>{{HostName}}</div>
<div class="row">Mac address:</div>
<div>{{MacAddress}}</div>
<div class="row">Wifi mode:</div>
<div>{{WifiMode}}</div>
<div class="row">Access point:</div>
<div>{{AccessPoint}}</div>
<div class="row">Signal strength:</div>
<div>{{SignalStrength}} dbm</div>
<div class="row">IPv4 address:</div>
<div>{{IpV4}}</div>
<div class="row">IPv6 address:</div>
<div>{{IpV6}}</div>
</div>
{{#NetworkState.ApMode}}
<div class="alert alert-warning">
<h3 class="text-center">Not connected to an access point.<br>Consider configuring the access point.</h3>
</div>
{{/NetworkState.ApMode}}
{{#NetworkState.OnLine}}
<div class="alert alert-success">
<h3 class="text-center">Connected to the access point</h3>
</div>
{{/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>
{{#CameraInitialized}}
<div class="alert alert-success">
<h3 class="text-center">Camera was initialized successfully!</h3>
</div>
{{/CameraInitialized}}
{{^CameraInitialized}}
<div class="alert alert-danger">
<h3 class="text-center">Failed to initialize the camera!<br>
Result: {{CameraInitResult}} ({{CameraInitResultText}})<br>
Please check hardware or correct the camera settings and restart.<br><br>
<button type="button" class="btn btn-danger" onclick="location.href='restart'">Restart</button>
</h3>
</div>
{{/CameraInitialized}}
<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>
</body>

1
html/index.min.html Normal file

File diff suppressed because one or more lines are too long

View File

@@ -4,29 +4,42 @@
<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">
<meta http-equiv="refresh" content="1;url=/index.html">
<meta http-equiv="refresh" content="10;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>
</head>
<body>
<div class="container">
<h1 class="text-center">{{ThingName}}</h1>
<hr>
<h1 class="text-center">{{ThingName}}</h1>
<hr>
<div class="jumbotron bg-light">
<h1 class="display-4 text-center">Restart</h1>
<p class="lead text-center">The device is restarting.</p>
<hr class="my-4 ">
<p class="text-center">In some cases, the device requires a hard reset (power cycle).</p>
<p class="text-center">If this page takes longer than a minute, consider performing a 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 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>
</body>
</html>

1
html/restart.min.html Normal file
View 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>

File diff suppressed because one or more lines are too long

View File

@@ -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.')

View File

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

View File

@@ -14,9 +14,9 @@
#define DEFAULT_CAMERA_CONFIG "AI THINKER"
#define DEFAULT_ENABLE_PSRAM psramFound()
#define DEFAULT_BUFFERS (psramFound() ? 2 : 1)
#define DEFAULT_FRAME_DURATION 100
#define DEFAULT_FRAME_SIZE (psramFound() ? "UXGA (1600x1200)" : "SVGA (800x600)")
#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_CONTRAST 0

20
minify.py Normal file
View 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.')

View File

@@ -35,6 +35,10 @@ build_flags =
-mfix-esp32-psram-cache-issue
-D IOTWEBCONF_PASSWORD_LEN=64
board_build.embed_txtfiles =
html/index.min.html
html/restart.min.html
lib_deps =
prampec/IotWebConf @ ^3.2.1
geeksville/Micro-RTSP @ ^0.1.6

View File

@@ -1,5 +1,6 @@
#include <Arduino.h>
#include <ArduinoOTA.h>
#include <esp_wifi.h>
#include <soc/rtc_cntl_reg.h>
#include <IotWebConf.h>
#include <IotWebConfTParameter.h>
@@ -14,10 +15,14 @@
#include <format_duration.h>
#include <format_number.h>
#include <moustache.h>
#include <html_data.h>
#include <html_data_gzip.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_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();
@@ -61,7 +66,9 @@ DNSServer dnsServer;
std::unique_ptr<rtsp_server> camera_server;
// Web server
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
bool config_changed = false;
@@ -92,6 +99,8 @@ void handle_root()
// Wifi Modes
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[] = {
// Config Changed?
@@ -110,6 +119,7 @@ void handle_root()
{"PsRamSize", format_memory(ESP.getPsramSize(), 0)},
// Diagnostics
{"Uptime", String(format_duration(millis() / 1000))},
{"Temperature", String((temprature_sens_read() - 32) / 1.8)},
{"FreeHeap", format_memory(ESP.getFreeHeap())},
{"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())},
{"NumRTSPSessions", camera_server != nullptr ? String(camera_server->num_connected()) : "RTSP server disabled"},
@@ -118,9 +128,9 @@ void handle_root()
{"MacAddress", WiFi.macAddress()},
{"AccessPoint", WiFi.SSID()},
{"SignalStrength", String(WiFi.RSSI())},
{"IpV4", WiFi.localIP().toString()},
{"IpV6", WiFi.localIPv6().toString()},
{"WifiMode", wifi_modes[WiFi.getMode()]},
{"IpV4", ipv4.toString()},
{"IpV6", ipv6.toString()},
{"NetworkState.ApMode", String(iotWebConf.getState() == iotwebconf::NetworkState::ApMode)},
{"NetworkState.OnLine", String(iotWebConf.getState() == iotwebconf::NetworkState::OnLine)},
// Camera
@@ -163,7 +173,7 @@ void handle_root()
{"RtspPort", String(RTSP_PORT)}};
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);
}
@@ -182,7 +192,7 @@ void handle_restart()
{"AppVersion", APP_VERSION},
{"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);
log_v("Restarting... Press refresh to connect again");
sleep(100);
@@ -199,7 +209,7 @@ void handle_snapshot()
}
// Remove old images stored in the frame buffer
auto frame_buffers = psramFound() ? 2 : 1;
auto frame_buffers = param_frame_buffers.value();
while (frame_buffers--)
cam.run();
@@ -229,6 +239,7 @@ void handle_stream()
}
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");
@@ -292,7 +303,7 @@ esp_err_t initialize_camera()
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_location = CAMERA_FB_IN_PSRAM;
@@ -344,18 +355,15 @@ void 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));
// Add service to mDNS - rtsp
// Add RTSP service to mDNS
// HTTP is already set by iotWebConf
MDNS.addService("rtsp", "tcp", 554);
}
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, param_led_intensity.value());
// Start (OTA) Over The Air programming when connected
// Start (OTA) Over The Air programming when connected
ArduinoOTA.begin();
// Start the RTSP Server if initialized
if (camera_init_result == ESP_OK)
@@ -379,8 +387,8 @@ void setup()
// Disable brownout
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
// LED_BUILTIN (GPIO33) has inverted logic false => LED on
pinMode(LED_BUILTIN, OUTPUT);
// Turn LED on (has inverted logic GPIO33) => red LED on => not connected
digitalWrite(LED_BUILTIN, false);
pinMode(LED_FLASH, OUTPUT);
@@ -392,13 +400,16 @@ void setup()
Serial.setDebugOutput(true);
#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("SDK version: %s", ESP.getSdkVersion());
log_i("Starting " APP_TITLE "...");
if (psramFound())
{
psramInit();
log_v("PSRAM found and initialized");
}
param_group_board.addItem(&param_board);
iotWebConf.addParameterGroup(&param_group_board);
@@ -438,13 +449,14 @@ void setup()
iotWebConf.getApTimeoutParameter()->visible = true;
iotWebConf.setConfigSavedCallback(on_config_saved);
iotWebConf.setWifiConnectionCallback(on_connected);
iotWebConf.setStatusPin(LED_BUILTIN, LOW);
iotWebConf.init();
camera_init_result = initialize_camera();
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
if (camera_init_result == ESP_OK)
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
web_server.on("/", HTTP_GET, handle_root);
@@ -455,18 +467,14 @@ void setup()
web_server.on("/snapshot", HTTP_GET, handle_snapshot);
// Camera stream
web_server.on("/stream", HTTP_GET, handle_stream);
// Camera flash light
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([]()
{ iotWebConf.handleNotFound(); });
ArduinoOTA
.setPassword(OTA_PASSWORD)
.onStart([]()
{ log_w("Starting OTA update: %s", ArduinoOTA.getCommand() == U_FLASH ? "sketch" : "filesystem"); })
.onEnd([]()
@@ -484,7 +492,9 @@ void setup()
case OTA_END_ERROR: log_e("OTA: End Failed"); break;
default: log_e("OTA error: %u", error);
} });
ArduinoOTA.setPassword(OTA_PASSWORD);
// Set flash led intensity
analogWrite(LED_FLASH, param_led_intensity.value());
}
void loop()
@@ -494,4 +504,6 @@ void loop()
if (camera_server)
camera_server->doLoop();
yield();
}