18 Commits

Author SHA1 Message Date
10c098ad33 added local copy of Async WebServer 2022-07-15 13:37:28 +02:00
3c3feeb8b5 moved to ESP32 2022-07-15 13:36:29 +02:00
893a57707d Removed RemoteDebugging Library 2022-07-15 13:35:57 +02:00
790261b6b7 modified .gitignore 2022-05-15 23:06:23 +02:00
0b6c432c70 changed upload-params 2022-05-15 22:52:50 +02:00
c88b532978 added ButtonCombo to activate WiFi AP 2022-05-15 22:52:24 +02:00
27b8cb0166 changed IP and deactivated Captive 2022-05-15 22:51:42 +02:00
56872ea856 commands from WebUI no longer CaseSensitive 2022-05-15 22:48:25 +02:00
d3506fd479 disabled WiFi-Client and swaped Displays for BatLo 2022-05-15 16:46:48 +02:00
ba90e98565 EEPROM changes 2022-05-15 16:37:34 +02:00
7b2c853b0d fixed Warning (for Real this time) 2022-04-16 17:36:05 +02:00
ed8f65b92e Low Bat Information Display tuned 2022-04-16 17:14:04 +02:00
69e5249a9d OTA Info on 7Seg instead Serial 2022-04-16 17:13:46 +02:00
b9f3b39684 fixed Warning 2022-04-16 17:13:13 +02:00
63f8c34d97 small Menu adjustment 2022-04-16 16:14:05 +02:00
d99216815a More webUI Adjustments, started with settings-Page 2022-04-16 10:17:06 +02:00
e6b5c0e3f0 WebUI-System copied from ChainLube 2022-04-14 22:48:39 +02:00
9aa266792d massive takeover of Firmware-parts from ChainLube 2022-04-14 22:48:11 +02:00
67 changed files with 16110 additions and 648 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
wifi_credentials.ini

View File

@@ -3,5 +3,8 @@
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View File

@@ -1,151 +0,0 @@
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
.container-fluid {
position: relative;
margin-left: auto;
margin-right: auto;
padding-right: 15px;
padding-left: 15px;
}
.row {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
.col {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
.table-fac-1,
.table-fac-1 > td {
background-color: #ffeeba;
}
.table-fac-2,
.table-fac-2 > td {
background-color: #bac9ff;
}
.table-fac-3,
.table-fac-3 > td {
background-color: #f5c6cb;
}
.table {
border-collapse: collapse !important;
width: 100%;
max-width: 100%;
margin-bottom: 1rem;
}
.table td,
.table th {
padding: 0.75rem;
vertical-align: top;
border-top: 1px solid #eceeef;
text-align: center;
}
.table thead th {
vertical-align: bottom;
border-bottom: 2px solid #eceeef;
}
.table tbody + tbody {
border-top: 2px solid #eceeef;
}
.table .table {
background-color: #fff;
}
label {
-ms-touch-action: manipulation;
touch-action: manipulation;
display: inline-block;
margin-bottom: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-control {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 1rem;
line-height: 1.25;
color: #464a4c;
background-color: #fff;
background-image: none;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.25rem;
-webkit-transition: border-color ease-in-out 0.15s,
-webkit-box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s,
-webkit-box-shadow ease-in-out 0.15s;
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s,
-webkit-box-shadow ease-in-out 0.15s;
}
.form-control:focus {
color: #464a4c;
background-color: #fff;
border-color: #5cb3fd;
outline: 0;
}
.btn {
display: inline-block;
font-weight: 400;
line-height: 1.25;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid transparent;
padding: 0.5rem 1rem;
font-size: 1rem;
border-radius: 0.25rem;
-webkit-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
.btn:focus,
.btn:hover {
text-decoration: none;
-webkit-box-shadow: 0 0 0 4px rgba(216, 63, 2, 0.25);
box-shadow: 0 0 0 4px rgba(216, 63, 2, 0.25);
}
.btn:focus {
outline: 0;
-webkit-box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.25);
box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.25);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

200
data/index.htm Normal file
View File

@@ -0,0 +1,200 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>%DEVICE_NAME%</title>
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="static/css/bootstrap.min.css">
<link rel="stylesheet" href="static/css/custom.css">
<script src="static/js/jquery-3.3.1.min.js"></script>
<script src="static/js/bootstrap.bundle.min.js"></script>
<link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png">
<link rel="manifest" href="static/img/site.webmanifest">
<link rel="mask-icon" href="static/img/safari-pinned-tab.svg" color="#111111">
<link rel="shortcut icon" href="static/img/favicon.ico">
<meta name="msapplication-TileColor" content="#111111">
<meta name="msapplication-config" content="static/img/browserconfig.xml">
<meta name="theme-color" content="#111111">
<script type="text/javascript">
var opacity = 0;
var intervalID = 0;
window.onload = setTimeout(() => { fadeout(); }, 4000);
function fadeout() {
intervalID = setInterval(hide, 200);
}
function hide() {
var responseMessage = document.getElementById("responseMessage");
opacity =
Number(window.getComputedStyle(responseMessage).getPropertyValue("opacity"))
if (opacity > 0) {
opacity = opacity - 0.1;
responseMessage.style.opacity = opacity
}
else {
responseMessage.remove()
clearInterval(intervalID);
}
}
</script>
</head>
<body>
<header class="sticky-top">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="#">
<img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
%DEVICE_NAME%</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse pr-2 mt-2 bg-secondary" id="navbarNavAltMarkup">
<ul class="navbar-nav nav align-items-end">
<li class="nav-item">
<a class="nav-link active" href="#tab_home" data-toggle="tab">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#tab_settings" data-toggle="tab">Settings</a>
</li>
</ul>
</div>
</nav>
</header>
<main class="container-fluid py-3 flex-fill">
<!-- Tabs Content -->
<div class="tab-content">
<!-- Div Tab Home-->
<div class="tab-pane fade show active" id="tab_home" role="tabpanel">
<!-- Jumbotron -->
<div class="jumbotron text-center">
<p class="p-4">
<img src="static/img/logo.png">
</p>
<h3>%DEVICE_NAME%</h3>
</div>
<!-- AlertMessageBox-->
<div id="responseMessage" class="alert alert-%RESP_MESSAGE_TYPE%" %SHOW_RESP_MESSAGE%>
%RESP_MESSAGE%
</div>
<div class="container-fluid pb-5">
<h4>BATTERY: %BATTERY_TYPE%</h4>
<div class="progress">
<div class="progress-bar text-light" role="progressbar" aria-valuenow="%BAT_REMAIN_CAPACITY%"
aria-valuemin="0" aria-valuemax="100" style="width: %BAT_REMAIN_CAPACITY%&#37;">
%BAT_REMAIN_CAPACITY%&#37; / %BAT_VOLTAGE% V
</div>
</div>
</div>
<div class="container-fluid pb-5">
<h4>FACTIONS</h4>
<div class="table-responsive">
<table class="table text-light text-center">
<thead>
<tr>
<th scope="col">Faction</td>
<th scope="col">Time</td>
</tr>
</thead>
<tbody>
<tr>
<td class="align-items-center justify-content-center" scope="col">
<a href="#">
<img src="static/img/logo_fac1.png" class="img-responsive" alt="" />
<div class="desc">
<p class="desc_content">%NAME_FAC_1%</p>
</div>
</a>
</td>
<td class="align-items-center justify-content-center" scope="col">%POINTS_FAC_1%</td>
</tr>
<tr>
<td class="align-items-center justify-content-center" scope="col">
<a href="#">
<img src="static/img/logo_fac2.png" class="img-responsive" alt="" />
<div class="desc">
<p class="desc_content">%NAME_FAC_2%</p>
</div>
</a>
</td>
<td class="align-items-center justify-content-center" scope="col">%POINTS_FAC_2%</td>
</tr>
<tr>
<td class="align-items-center justify-content-center" scope="col">
<a href="#">
<img src="static/img/logo_fac3.png" class="img-responsive" alt="" />
<div class="desc">
<p class="desc_content">%NAME_FAC_3%</p>
</div>
</a>
</td>
<td class="align-items-center justify-content-center" scope="col">%POINTS_FAC_3%</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="container-fluid pb-5" %SHOW_DTC_TABLE%>
<h4>Fehlercodes</h4>
<table class="table text-light">
<tbody>
<tr>
<th class="col-md-4" scope="col">Timestamp</td>
<th class="col-md-4" scope="col">DTC</td>
<th class="col-md-4" scope="col">active</td>
</tr>
%DTC_TABLE%
</tbody>
</table>
</div>
</div>
<!-- Div Tab Home-->
<!-- Div Tab Settings-->
<div class="tab-pane fade" id="tab_settings" role="tabpanel">
<h3>Settings</h3>
<hr>
<form method="POST">
<div class="form-group row">
<label for="commandInput" class="col-sm-2 col-form-label">Command</label>
<div class="col-sm-10">
<input type="text" name="commandInput" class="form-control bg-light" id="commandInput"
placeholder="type command here...">
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button name="cmdsubmit" type="submit" class="btn btn-primary float-right">Apply</button>
</div>
</div>
</form>
</div>
<!-- Div Tab Settings-->
</div>
<!-- Tabs Content -->
</main>
<!-- Footer -->
<footer class="fixed-bottom bg-secondary font-weight-light text-light text-center py-3">
<!-- Copyright -->
<span class=" footer-copyright">
© 2022 <a class="text-reset fw-bold" href="https://hiabuto.de/">Hiabuto Defence Systems</a>
</span>
<!-- Copyright -->
</footer>
<!-- Footer -->
</body>
</html>

View File

@@ -1,100 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>%TITLE% - by hiabuto.de</title>
<meta name="description" content="A Project, designed by hiabuto.de">
<meta name="author" content="Marcel Peterkau">
<link href="css/style.css" rel="stylesheet">
<!--<link href="css/bootstrap.min.css" rel="stylesheet">-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col">
<h3>
%TITLE%
</h3>
<p>Battery Voltage: %BATTERY_VOLTAGE% V</p>
<p>Battery Level: %BATTERY_LEVEL% &percnt;</p>
<table class="table">
<thead>
<tr>
<th>
#
</th>
<th>
Team
</th>
<th>
Points
</th>
<th>
Status
</th>
</tr>
</thead>
<tbody>
<tr class="table-fac-1">
<td>
1
</td>
<td>
%NAME_FAC_1%
</td>
<td>
%POINTS_FAC_1%
</td>
<td>
%STATUS_FAC_1%
</td>
</tr>
<tr class="table-fac-2">
<td>
2
</td>
<td>
%NAME_FAC_2%
</td>
<td>
%POINTS_FAC_2%
</td>
<td>
%STATUS_FAC_2%
</td>
</tr>
<tr class="table-fac-3">
<td>
3
</td>
<td>
%NAME_FAC_3%
</td>
<td>
%POINTS_FAC_3%
</td>
<td>
%STATUS_FAC_3%
</td>
</tr>
</tbody>
</table>
<form action="index.html" method="get" role="form">
<div class="form-group">
<label for="commandInput">Command</label>
<input type="text" class="form-control" id="commandInput" name="command" />
</div>
<button type="submit" class="btn">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>

28
data/post.htm Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>%DEVICE_NAME%</title>
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="static/css/bootstrap.min.css">
<link rel="stylesheet" href="static/css/custom.css">
<script src="static/js/jquery.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
<link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png">
<link rel="manifest" href="static/img/site.webmanifest">
<meta http-equiv="refresh" content="3; url='/index.htm'" />
</head>
<body>
<div class="container" style="display: flex; justify-content: center; align-items: center; height: 100vh">
<div class="alert alert-success">
<strong>Bitte warten!</strong> Änderungen werden übernommen.
</div>
</div>
</body>
</html>

7
data/static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

5739
data/static/css/custom.css Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
data/static/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
data/static/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,345 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M210 2778 c1 -198 2 -221 15 -204 13 18 17 18 41 5 31 -16 41 -36 26
-51 -8 -8 -15 -8 -25 0 -19 16 -49 -22 -53 -69 -3 -30 1 -38 16 -42 26 -7 25
-27 -1 -34 -18 -5 -20 -11 -15 -47 3 -23 6 -48 5 -56 0 -8 -2 -168 -4 -355
l-4 -340 31 -18 c29 -17 31 -16 69 5 22 12 38 27 35 33 -2 7 0 18 5 26 7 11 9
11 9 -3 0 -26 13 -23 93 25 39 24 98 58 130 76 31 18 57 38 58 44 0 7 1 63 1
125 1 87 -2 116 -14 130 -26 29 -30 59 -13 92 8 16 19 30 23 30 8 0 7 212 -2
235 -3 8 -2 15 2 15 10 0 8 62 -2 78 -4 7 -5 12 -1 12 3 0 1 9 -5 20 -6 11
-17 20 -25 20 -8 0 -15 5 -15 11 0 16 -26 39 -45 39 -12 0 -15 6 -10 21 8 26
78 139 86 139 3 0 11 -36 18 -80 13 -79 23 -101 36 -80 4 7 11 8 18 1 14 -11
36 -14 103 -12 l52 1 25 53 c15 28 40 67 56 85 29 31 31 32 51 15 27 -23 25
-37 -10 -100 l-30 -53 75 0 c41 0 74 3 74 8 -3 18 2 32 11 32 6 0 10 -9 10
-20 0 -17 7 -20 39 -20 29 0 41 5 44 18 4 15 5 15 6 0 1 -13 13 -18 54 -20 99
-6 227 3 227 15 0 7 9 23 19 37 l19 24 18 -37 c19 -39 32 -43 118 -37 30 2 60
23 49 34 -13 13 -13 48 -1 44 7 -3 14 -20 15 -38 2 -18 5 -35 7 -37 2 -2 30
-3 62 -1 53 3 57 4 41 19 -18 19 -13 29 15 29 13 0 18 -7 18 -25 0 -33 10 -32
50 10 19 19 38 35 42 35 5 0 16 -16 24 -36 13 -31 18 -35 47 -32 20 2 32 8 32
18 0 8 9 15 20 16 48 2 48 3 34 22 -13 15 -12 16 3 4 14 -11 23 -9 59 17 23
17 46 31 52 31 8 0 539 302 556 316 2 2 -186 4 -418 3 -265 0 -419 -3 -416 -9
4 -6 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 -16 10 -35 10 -24 0 -35 -4 -33 -12
1 -7 -6 -26 -17 -41 l-18 -29 -16 29 c-8 15 -14 34 -13 41 2 9 -21 12 -88 12
-70 0 -89 -3 -84 -12 6 -10 4 -10 -8 0 -23 18 -184 13 -180 -6 1 -8 -3 -12
-10 -9 -7 2 -12 10 -11 16 2 7 -16 11 -52 11 -42 0 -55 -3 -55 -15 0 -19 -34
-19 -50 0 -10 12 -45 14 -193 14 -128 0 -184 -4 -189 -12 -6 -9 -8 -9 -8 1 0
7 -13 12 -30 12 -16 0 -30 -4 -30 -10 0 -5 -5 -10 -11 -10 -5 0 -7 5 -4 10 4
7 -12 10 -44 9 -28 -1 -51 -5 -51 -9 0 -4 10 -19 21 -34 30 -38 39 -87 23
-122 -11 -26 -12 -27 -13 -6 -1 28 -81 108 -91 91 -6 -8 -10 -8 -16 2 -4 7 -3
14 3 16 6 2 -2 17 -18 34 -26 27 -33 29 -104 29 -61 0 -75 -3 -75 -15 0 -9 -7
-27 -16 -41 l-17 -25 -12 23 c-6 13 -8 28 -3 34 14 19 -4 24 -89 24 l-83 0 0
-222z m2395 192 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4
11 -10z m-107 -33 c-2 -11 -9 -12 -28 -5 -24 9 -24 10 -6 24 22 15 40 6 34
-19z m-1928 -52 c0 -5 -11 -23 -25 -40 l-24 -30 -24 26 -25 26 21 48 21 47 28
-33 c15 -19 28 -38 28 -44z m1139 13 c-5 -10 -14 -18 -19 -18 -16 0 -23 49 -9
66 11 14 14 13 25 -8 8 -15 9 -29 3 -40z m186 42 c3 -5 1 -10 -4 -10 -6 0 -11
5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z m-1066 -9 c11 -7 10 -11 -5 -19 -18
-10 -16 -14 19 -47 35 -33 63 -82 53 -92 -2 -2 -25 17 -50 42 -54 52 -72 90
-55 111 13 16 19 17 38 5z m106 -1 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6
2 10 4 10 3 0 8 -4 11 -10z m169 -17 c9 -22 -12 -43 -23 -24 -10 15 -4 41 9
41 4 0 11 -8 14 -17z m391 7 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4
10 3 0 8 -4 11 -10z m465 -30 c0 -13 -5 -18 -15 -14 -8 4 -15 12 -15 20 0 8 7
14 15 14 8 0 15 -9 15 -20z m-1573 -22 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3
-3 4 -12 1 -19z m592 13 c17 -11 7 -41 -14 -41 -9 0 -15 9 -15 25 0 27 6 30
29 16z m881 -1 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10
-4 10 -10z m-1541 -31 c33 -32 37 -44 21 -54 -6 -3 -10 4 -10 17 l0 23 -18
-23 -18 -24 -18 27 c-16 25 -14 65 4 65 5 0 22 -14 39 -31z m1131 11 c0 -5 -4
-10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m-300 -20 c0
-5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m250
-10 c0 -11 -4 -20 -10 -20 -5 0 -10 9 -10 20 0 11 5 20 10 20 6 0 10 -9 10
-20z m116 -32 l2 -42 -19 23 c-14 17 -16 27 -9 42 17 30 25 22 26 -23z m179
22 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z
m-891 -16 c4 -9 1 -23 -6 -31 -10 -12 -13 -12 -20 1 -9 15 -1 46 12 46 4 0 11
-7 14 -16z m-106 -35 c-2 -6 -8 -10 -13 -10 -5 0 -11 4 -13 10 -2 6 4 11 13
11 9 0 15 -5 13 -11z m719 -44 c-4 -53 4 -77 18 -55 10 16 25 1 25 -24 0 -27
-44 -71 -60 -61 -6 4 -10 17 -8 30 1 12 -4 35 -12 49 -13 25 -12 31 9 72 13
24 25 44 27 44 3 0 3 -25 1 -55z m-167 29 c0 -8 -5 -12 -10 -9 -6 4 -8 11 -5
16 9 14 15 11 15 -7z m560 -30 c0 -8 -4 -14 -10 -14 -5 0 -10 9 -10 21 0 11 5
17 10 14 6 -3 10 -13 10 -21z m270 -46 c0 -23 -12 -23 -34 1 -12 13 -15 27
-11 41 l7 21 19 -22 c10 -12 19 -30 19 -41z m-1830 18 c-13 -13 -35 7 -25 24
5 8 11 8 21 -1 10 -8 12 -15 4 -23z m968 4 c9 -16 23 -30 30 -30 13 0 15 -10
6 -34 -6 -15 -8 -15 -19 1 -11 15 -13 15 -20 -6 -5 -18 -9 -20 -21 -10 -29 24
-36 48 -22 79 16 38 25 38 46 0z m-362 -88 c-6 -5 -56 44 -56 54 0 3 8 16 17
31 l17 26 14 -53 c7 -29 11 -55 8 -58z m257 72 c-7 -19 -23 -7 -23 18 0 17 3
18 14 8 8 -6 12 -18 9 -26z m-673 16 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3
6 -1 10 4 10 6 0 11 -4 11 -10z m1060 0 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10
-3 6 -1 10 4 10 6 0 11 -4 11 -10z m58 -35 c-8 -17 -15 -22 -21 -16 -7 7 -6
15 3 26 20 24 31 18 18 -10z m606 9 c3 -8 2 -12 -4 -9 -6 3 -10 10 -10 16 0
14 7 11 14 -7z m-1469 -14 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4
10 3 0 8 -4 11 -10z m-395 -21 c0 -5 -4 -9 -10 -9 -5 0 -10 7 -10 16 0 8 5 12
10 9 6 -3 10 -10 10 -16z m1044 5 c3 -8 2 -12 -4 -9 -6 3 -10 10 -10 16 0 14
7 11 14 -7z m-602 -36 c-9 -9 -12 -7 -12 12 0 19 3 21 12 12 9 -9 9 -15 0 -24z
m-432 8 c0 -2 3 -12 7 -21 5 -14 2 -16 -18 -10 -31 9 -32 11 -24 24 6 10 35
16 35 7z m111 -68 c24 -29 23 -37 -5 -71 l-22 -28 -2 61 c0 33 1 60 4 60 3 0
14 -10 25 -22z m73 -14 c3 -8 2 -12 -4 -9 -6 3 -10 10 -10 16 0 14 7 11 14 -7z
m-185 -94 c17 -22 31 -44 31 -49 0 -6 4 -11 10 -11 5 0 -2 -12 -15 -26 -14
-15 -25 -31 -25 -35 0 -5 -7 -6 -15 -3 -8 3 -13 9 -12 12 1 4 -4 22 -12 40
-10 24 -11 35 -2 46 8 10 6 19 -9 39 -25 31 -25 36 -1 30 11 -3 33 -22 50 -43z
m150 -30 c0 -44 0 -45 -14 -19 -8 14 -13 39 -11 55 5 41 5 41 16 24 5 -8 10
-35 9 -60z m-54 20 c-3 -5 -11 -10 -16 -10 -6 0 -7 5 -4 10 3 6 11 10 16 10 6
0 7 -4 4 -10z m35 -115 c11 7 29 -26 23 -42 -2 -6 1 -14 6 -18 21 -12 11 -25
-39 -49 -44 -21 -70 -21 -56 2 2 4 8 35 11 67 9 74 13 82 32 56 8 -12 18 -19
23 -16z m-80 -35 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0
10 -4 10 -10z m237 -17 c8 -12 12 -27 8 -33 -8 -13 -65 26 -65 45 0 22 41 13
57 -12z m-56 -33 c18 -10 26 -49 13 -63 -6 -5 -64 55 -64 67 0 9 29 7 51 -4z
m-78 -66 c-4 -11 -9 -12 -19 -4 -7 7 -10 18 -7 26 4 11 9 12 19 4 7 -7 10 -18
7 -26z m49 -46 c-15 -15 -27 14 -17 40 6 15 8 15 18 -6 7 -16 7 -26 -1 -34z
m87 -90 c5 -16 6 -28 1 -28 -17 0 -60 63 -60 88 l0 27 25 -30 c13 -16 29 -42
34 -57z m-235 -89 c-10 -16 -24 -9 -24 12 0 18 2 19 15 9 8 -7 12 -16 9 -21z
m-37 -65 c-1 -2 -13 6 -27 16 -23 17 -23 20 -10 45 l14 26 13 -43 c6 -23 11
-43 10 -44z m284 23 c-8 -8 -11 -3 -11 19 1 25 2 27 11 11 8 -14 8 -22 0 -30z
m-201 -8 c0 -6 -4 -7 -10 -4 -5 3 -10 11 -10 16 0 6 5 7 10 4 6 -3 10 -11 10
-16z m45 -59 c3 -11 11 -20 17 -20 7 0 7 -4 0 -13 -6 -7 -9 -23 -6 -34 3 -12
1 -30 -4 -40 -10 -17 -11 -17 -17 2 -11 33 -20 48 -30 46 -18 -4 -23 43 -7 62
20 23 39 22 47 -3z m167 -37 c-4 -24 -6 -25 -17 -9 -7 9 -11 26 -8 37 8 30 31
5 25 -28z m-102 -5 c0 -19 -2 -20 -10 -8 -13 19 -13 30 0 30 6 0 10 -10 10
-22z m-257 -49 c3 -14 1 -32 -3 -39 -7 -11 -12 -7 -21 13 -9 21 -9 30 1 42 15
19 17 17 23 -16z m157 -59 c0 -25 -4 -30 -25 -30 -31 0 -31 7 -3 37 27 29 28
29 28 -7z"/>
<path d="M2695 2824 c-44 -25 -82 -48 -85 -52 -3 -4 -11 -8 -19 -10 -7 -1 -38
-19 -70 -39 -31 -20 -60 -38 -66 -39 -5 -1 -57 -29 -113 -63 -57 -34 -118 -70
-135 -79 -18 -9 -78 -44 -134 -76 l-101 -59 18 -35 c14 -25 16 -40 9 -53 -23
-43 -42 -35 -64 26 l-13 38 -24 -24 c-13 -13 -33 -24 -43 -24 -13 0 -20 -7
-21 -20 -2 -37 -5 -40 -17 -23 -12 14 -25 9 -140 -55 l-127 -72 2 -47 c1 -55
-23 -98 -32 -58 -7 31 -26 36 -27 8 -1 -60 -3 -68 -21 -68 -26 0 -44 41 -26
58 8 7 14 22 14 32 0 19 -4 18 -57 -11 -32 -18 -64 -39 -70 -47 -7 -9 -13 -12
-13 -8 0 4 -13 0 -29 -10 -18 -11 -31 -29 -37 -53 -6 -25 -15 -36 -26 -35 -11
1 -21 -10 -29 -33 l-11 -35 -27 26 c-30 28 -53 33 -69 13 -12 -14 11 -52 31
-52 7 0 13 -12 15 -27 3 -22 -1 -28 -16 -28 -20 0 -39 16 -42 35 -3 23 -12 55
-17 55 -13 0 -133 -76 -133 -85 0 -6 -10 -15 -22 -20 -12 -6 -25 -21 -29 -35
-4 -14 -7 -18 -8 -8 -2 27 -19 31 -51 11 -28 -16 -35 -26 -31 -47 1 -4 -9 -6
-23 -2 -14 3 -30 1 -38 -6 -8 -7 -49 -31 -92 -53 -43 -22 -89 -50 -102 -62
-13 -12 -24 -20 -24 -17 0 3 -8 1 -17 -5 -10 -5 -18 -19 -18 -32 0 -12 -6 -24
-12 -26 -7 -3 -13 2 -13 11 0 21 -4 20 -61 -10 -40 -22 -48 -31 -43 -49 4 -17
-6 -31 -47 -66 -28 -24 -60 -50 -70 -57 -16 -11 -19 -27 -19 -108 0 -81 3 -96
18 -101 64 -23 134 -105 116 -135 -4 -6 4 -4 17 3 26 13 253 144 309 178 19
11 46 26 60 33 14 7 27 15 30 18 3 3 26 16 52 30 83 43 83 44 59 83 -11 19
-21 45 -21 58 0 24 37 95 45 87 10 -10 42 -118 35 -118 -5 0 -13 -14 -19 -30
l-10 -31 42 26 c23 14 95 55 160 92 109 63 117 70 112 94 -3 14 -2 36 4 50 l9
24 7 -30 c13 -58 12 -57 57 -29 24 15 45 28 47 30 2 2 -4 20 -15 39 -17 31
-17 37 -4 45 22 14 27 12 27 -7 0 -10 1 -31 2 -48 2 -29 2 -30 19 -10 9 11 23
20 30 21 29 2 33 5 30 22 -2 13 3 17 25 15 34 -2 34 7 1 38 -42 39 -55 71 -43
112 5 19 9 39 8 44 -7 40 26 44 70 8 24 -21 28 -30 22 -47 -6 -16 0 -33 22
-64 l30 -43 36 24 c20 14 52 32 71 42 19 9 63 34 98 55 34 21 66 38 71 38 4 0
5 17 2 38 -4 20 -4 55 0 77 11 60 1 99 -38 145 -31 36 -35 46 -29 78 6 40 28
62 62 62 18 0 31 -21 80 -130 33 -71 65 -130 72 -130 6 0 12 -9 12 -20 0 -23
8 -25 32 -6 14 10 15 15 5 26 -10 10 -8 10 10 1 18 -9 24 -8 32 7 7 12 6 25
-2 39 -47 81 -48 83 -34 83 25 0 65 -39 72 -70 7 -33 8 -33 107 23 36 21 69
45 72 53 3 8 11 12 17 9 5 -4 29 6 52 21 23 16 44 29 47 29 3 0 28 14 55 29
28 16 67 39 88 51 20 12 35 25 32 28 -3 4 -2 5 1 2 9 -6 84 34 84 45 0 6 7 10
16 10 8 0 35 12 60 26 36 21 44 31 44 54 0 16 -11 42 -25 59 l-26 30 23 3 c20
3 23 10 26 50 2 38 0 47 -11 42 -9 -3 -16 3 -19 14 -6 23 9 55 23 47 5 -4 9
38 9 104 0 61 -3 111 -7 111 -5 0 -44 -21 -88 -46z m33 -44 c12 -12 22 -23 22
-25 0 -11 -24 -55 -30 -55 -3 0 -11 23 -16 50 -12 55 -8 60 24 30z m42 -70 c0
-5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m-150
-52 c0 -16 -3 -19 -11 -11 -6 6 -8 16 -5 22 11 17 16 13 16 -11z m-97 -14 c-7
-18 -19 -18 -26 0 -7 18 1 26 18 19 8 -2 11 -11 8 -19z m15 -100 c12 -22 -9
-79 -33 -88 -10 -4 -13 12 -13 70 0 72 1 75 18 57 9 -11 22 -28 28 -39z m-316
-26 c-7 -7 -12 -8 -12 -2 0 6 3 14 7 17 3 4 9 5 12 2 2 -3 -1 -11 -7 -17z m20
-93 c0 -5 -5 -11 -11 -13 -6 -2 -11 4 -11 13 0 9 5 15 11 13 6 -2 11 -8 11
-13z m-212 -20 c0 -8 -2 -15 -4 -15 -2 0 -6 7 -10 15 -3 8 -1 15 4 15 6 0 10
-7 10 -15z m173 -31 c22 -22 24 -29 15 -50 -13 -27 -25 -30 -51 -10 -14 11
-17 22 -12 50 9 43 14 44 48 10z m-113 -19 c0 -9 -6 -12 -16 -8 -13 5 -13 7
-2 14 17 11 18 11 18 -6z m48 -25 c-1 -16 -6 -30 -10 -30 -9 0 -10 22 -2 44
10 25 15 19 12 -14z m182 -20 c13 -31 6 -100 -10 -100 -14 0 -40 43 -40 67 0
31 18 75 29 69 4 -3 14 -19 21 -36z m-390 -20 c0 -5 -2 -10 -4 -10 -3 0 -8 5
-11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m66 -57 c-10 -11 -25 18 -18 35 4
13 8 11 15 -6 6 -12 7 -25 3 -29z m-226 7 c0 -5 -4 -10 -10 -10 -5 0 -10 5
-10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m483 -47 c-5 -12 -7 -12 -14 -1 -5 7
-9 19 -9 27 0 11 3 11 14 1 8 -6 12 -18 9 -27z m207 16 c0 -5 -4 -9 -10 -9 -5
0 -10 7 -10 16 0 8 5 12 10 9 6 -3 10 -10 10 -16z m-123 -11 c-3 -8 -6 -5 -6
6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m-617 -15 c0 -21 -3 -24 -9 -14 -5 8 -7
20 -4 28 8 21 13 15 13 -14z m383 -22 c3 -11 2 -22 -1 -25 -9 -10 -31 15 -25
30 7 20 20 17 26 -5z m-113 -27 c0 -8 -4 -12 -10 -9 -5 3 -10 10 -10 16 0 5 5
9 10 9 6 0 10 -7 10 -16z m-273 -66 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4
-12 1 -19z m296 -22 c3 -8 0 -22 -7 -32 -13 -17 -14 -16 -20 4 -11 34 15 60
27 28z m-643 -46 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0
11 -4 11 -10z m178 -23 c13 -15 -1 -42 -17 -33 -13 8 -15 46 -2 46 5 0 13 -6
19 -13z m93 -20 c-1 -12 -15 -9 -19 4 -3 6 1 10 8 8 6 -3 11 -8 11 -12z m108
-29 c5 -13 16 -39 25 -59 l15 -37 -27 3 c-35 4 -72 16 -72 23 0 7 43 92 47 92
2 0 7 -10 12 -22z m-159 -29 c0 -18 -18 -9 -23 12 -4 16 -3 18 9 8 8 -6 14
-15 14 -20z m-95 11 c11 -17 -5 -32 -21 -19 -7 6 -11 15 -8 20 7 12 21 11 29
-1z m-128 -22 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m-31 -58
c2 -35 -6 -38 -23 -10 -11 16 -10 21 5 29 9 6 17 11 17 11 0 0 1 -13 1 -30z
m-306 -26 c0 -8 -4 -12 -10 -9 -5 3 -10 13 -10 21 0 8 5 12 10 9 6 -3 10 -13
10 -21z m573 -21 c-3 -7 -9 -13 -13 -13 -14 0 -18 24 -7 45 10 19 10 19 17 0
4 -11 5 -26 3 -32z m-337 -56 c-4 -18 1 -38 12 -55 l17 -27 -33 23 c-40 29
-64 72 -51 93 5 8 9 25 9 38 0 24 1 23 26 -10 18 -25 24 -42 20 -62z m124 57
c0 -24 -17 -15 -22 11 -4 20 -3 23 8 14 8 -6 14 -18 14 -25z m310 -8 c-15 -15
-32 7 -24 29 7 17 8 17 21 -1 9 -12 10 -21 3 -28z m-725 -12 c-3 -29 -2 -34 9
-25 11 9 15 5 18 -23 4 -33 3 -34 -13 -20 -36 33 -49 104 -19 104 6 0 8 -15 5
-36z m107 14 c6 -17 -1 -38 -13 -38 -5 0 -9 11 -9 25 0 26 14 34 22 13z m64
-53 c4 -8 1 -22 -6 -30 -9 -11 -9 -22 1 -49 11 -31 10 -41 -5 -72 l-17 -37
-24 34 c-14 18 -25 43 -25 55 0 24 30 84 43 84 4 0 7 7 7 15 0 20 19 19 26 0z
m189 6 c3 -5 1 -12 -4 -15 -5 -3 -11 1 -15 9 -6 16 9 21 19 6z m-515 -31 c0
-5 -5 -10 -11 -10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z m-45 -40
c7 -23 -2 -43 -16 -35 -12 8 -12 55 0 55 5 0 12 -9 16 -20z m205 -15 c0 -18
-4 -23 -15 -19 -15 6 -20 35 -9 47 12 11 24 -3 24 -28z m-90 -32 l-1 -58 -19
24 c-23 27 -25 44 -8 71 21 33 28 23 28 -37z m-231 -20 c1 -26 0 -45 -1 -42
-2 2 -11 13 -20 25 -16 18 -16 22 -2 42 8 12 16 22 18 22 1 0 4 -21 5 -47z
m290 1 c21 -27 36 -63 28 -70 -10 -10 -39 10 -52 37 -29 56 -13 79 24 33z m86
-4 c-5 -8 -11 -8 -17 -2 -6 6 -7 16 -3 22 5 8 11 8 17 2 6 -6 7 -16 3 -22z
m163 -52 c-7 -19 -21 -12 -23 9 -1 18 1 20 12 11 8 -6 12 -15 11 -20z m-113
-8 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z m-42
-46 c-4 -11 -9 -12 -19 -4 -7 7 -10 18 -7 26 4 11 9 12 19 4 7 -7 10 -18 7
-26z m-453 -28 c0 -6 -8 -20 -16 -31 l-15 -20 -15 20 c-8 11 -20 20 -27 20 -6
0 -13 8 -15 19 -2 11 0 16 7 12 5 -4 15 3 21 15 l12 21 24 -23 c13 -12 24 -27
24 -33z m344 9 c-6 -15 -8 -15 -16 -2 -4 8 -5 23 -2 32 6 15 8 15 16 2 4 -8 5
-23 2 -32z m-152 -44 c-5 -3 -12 -13 -16 -21 -3 -8 -4 -4 -2 9 3 14 1 30 -4
36 -6 7 -3 20 8 36 l17 24 3 -39 c2 -21 -1 -42 -6 -45z m308 -1 c0 -5 -2 -10
-4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m-655 -17 c13 -12
19 -13 24 -4 14 23 6 -53 -8 -79 -13 -23 -14 -23 -21 -5 -10 27 -21 105 -16
105 3 0 12 -7 21 -17z m239 -19 c-6 -3 -10 -18 -8 -34 4 -32 -6 -39 -24 -17
-13 15 11 58 31 56 8 0 9 -2 1 -5z m50 -20 c3 -8 2 -12 -4 -9 -6 3 -10 10 -10
16 0 14 7 11 14 -7z m-71 -86 c-19 -33 -40 -39 -35 -10 4 18 29 42 45 42 5 0
1 -14 -10 -32z m-198 -102 c7 3 27 -7 44 -23 l31 -28 -26 -23 -27 -23 6 30 c6
28 5 30 -16 20 -19 -9 -22 -17 -19 -47 l3 -37 -25 40 c-25 40 -26 65 -3 94 7
8 13 9 15 3 2 -6 9 -9 17 -6z m-125 -46 c0 -22 -16 -35 -25 -20 -9 14 4 52 16
45 5 -4 9 -15 9 -25z"/>
<path d="M2720 2219 c-68 -38 -62 -31 -62 -73 0 -41 -17 -47 -46 -16 l-19 20
-24 -22 c-14 -13 -29 -23 -34 -23 -22 0 -185 -101 -181 -112 3 -7 -1 -10 -9
-6 -11 4 -109 -42 -133 -62 -1 -1 7 -23 19 -49 31 -70 24 -211 -12 -250 -9
-11 -29 34 -29 67 0 11 -9 31 -21 43 -14 15 -19 29 -14 43 3 12 11 19 16 15 5
-3 7 -9 5 -13 -3 -4 0 -13 5 -21 7 -11 9 -5 8 20 -1 19 1 59 5 88 l8 53 -24
-14 c-24 -15 -366 -215 -428 -249 -221 -125 -349 -202 -344 -209 3 -5 2 -14
-2 -21 -5 -8 -9 -7 -13 5 -6 15 -9 15 -36 -3 -19 -13 -24 -20 -14 -20 31 0 40
-24 20 -54 l-19 -28 -12 36 c-13 41 -21 43 -58 18 -15 -10 -35 -22 -45 -25
-11 -4 -21 -16 -24 -28 -4 -14 -8 -17 -13 -8 -6 8 -25 1 -71 -26 -77 -46 -100
-65 -93 -76 3 -5 0 -9 -5 -9 -6 0 -11 5 -11 11 0 5 -5 7 -10 4 -6 -4 -8 -11
-5 -16 11 -16 -5 -31 -21 -18 -11 9 -54 -12 -203 -97 -180 -103 -191 -111
-191 -139 -1 -25 -2 -27 -11 -12 -14 24 -24 22 -83 -15 -28 -17 -67 -40 -86
-50 -129 -73 -180 -103 -180 -108 0 -4 17 -16 38 -28 l39 -22 13 25 c16 29 27
32 27 7 0 -51 5 -61 39 -81 19 -12 36 -21 37 -21 1 0 11 15 21 34 13 24 23 33
34 29 11 -5 14 -2 9 10 -3 9 -3 17 2 17 8 0 12 -18 28 -118 5 -29 15 -39 87
-80 44 -26 84 -46 89 -44 4 1 7 -3 7 -8 0 -6 12 -16 28 -23 15 -6 49 -27 77
-44 27 -18 52 -33 56 -33 3 0 14 18 24 40 10 22 22 40 27 40 18 0 47 -82 38
-106 -8 -20 -4 -26 26 -45 34 -21 36 -21 49 -3 13 18 14 18 34 0 12 -11 20
-28 19 -38 -2 -14 47 -47 212 -146 118 -71 216 -128 218 -126 2 1 -2 11 -9 22
-11 17 -10 24 2 38 13 16 14 14 19 -19 l5 -36 74 44 c40 25 74 45 76 45 1 0
29 16 61 35 50 30 59 40 60 66 1 16 10 39 20 50 18 20 18 20 31 -6 8 -14 16
-25 19 -25 3 0 25 11 50 24 l45 24 -25 18 c-19 15 -24 25 -19 46 6 30 22 35
55 17 15 -8 19 -17 14 -39 -5 -28 -2 -27 124 48 71 42 130 83 130 90 0 6 10
12 22 12 16 0 85 36 127 67 2 1 -2 9 -8 17 -7 8 -9 20 -5 27 5 8 12 5 23 -9
16 -21 17 -21 209 94 l192 116 0 244 c0 134 -3 244 -6 244 -3 0 -46 -24 -97
-54 -51 -29 -112 -65 -137 -78 -119 -66 -220 -130 -216 -137 3 -4 1 -13 -4
-21 -7 -12 -10 -12 -15 2 -6 16 -9 16 -30 2 -13 -8 -29 -28 -35 -45 -11 -26
-14 -28 -25 -12 -11 15 -20 12 -96 -32 -134 -78 -127 -70 -85 -97 46 -30 60
-64 49 -119 l-9 -44 -44 2 c-25 1 -47 -3 -51 -9 -15 -25 21 -68 78 -93 52 -23
55 -27 48 -52 -13 -56 -27 -59 -94 -19 -34 20 -64 36 -67 36 -21 0 -64 77 -64
114 0 7 19 19 43 27 61 23 62 50 1 88 l-48 29 -28 -19 c-15 -11 -28 -26 -28
-34 0 -7 -9 -23 -20 -36 l-19 -24 -1 23 c0 28 -3 27 -67 -7 -52 -28 -52 -28
-33 -46 22 -20 26 -55 8 -73 -20 -20 -28 -14 -28 21 0 59 -11 62 -78 21 -34
-20 -62 -38 -62 -41 0 -2 18 -31 40 -63 22 -32 40 -61 40 -65 0 -3 -17 5 -38
18 -20 13 -45 26 -55 30 -9 4 -26 22 -37 41 -16 26 -66 61 -215 150 -107 64
-195 118 -195 120 0 2 56 36 125 75 69 40 125 76 125 80 0 16 -46 47 -68 45
-16 -1 -21 3 -18 12 3 8 1 14 -4 14 -6 0 -10 -6 -10 -12 0 -18 -20 13 -20 31
0 10 3 11 14 2 11 -9 15 -5 18 24 2 19 0 41 -4 48 -6 9 -8 8 -8 -3 -1 -12 -3
-11 -14 3 -8 10 -11 27 -8 39 5 20 6 20 18 3 8 -10 16 -16 18 -14 10 9 8 86
-3 129 -16 66 -14 79 7 52 28 -37 101 -47 160 -23 72 29 75 29 100 -1 31 -36
23 -58 -23 -59 -19 -1 -39 -3 -45 -4 -5 -1 -14 -3 -18 -4 -12 -1 -14 -93 -3
-109 4 -8 24 -17 42 -21 26 -5 35 -13 39 -33 l5 -26 125 73 c92 54 128 80 137
101 8 19 12 23 13 12 0 -10 3 -18 6 -18 16 0 143 78 173 106 36 34 38 43 40
157 1 44 3 47 20 38 20 -11 39 -54 48 -106 3 -16 6 -31 7 -32 1 -1 59 32 129
72 70 41 146 85 170 98 113 64 167 99 167 107 0 6 7 10 16 10 27 0 75 33 69
48 -3 8 1 23 9 34 14 20 14 20 21 -1 3 -12 11 -21 17 -21 13 0 128 69 128 77
0 4 -3 3 -6 0 -8 -8 -39 30 -48 59 -4 12 -2 34 4 49 10 27 11 27 30 10 20 -18
20 -17 20 65 0 72 -2 81 -13 66 -13 -18 -15 -17 -34 8 -12 14 -28 26 -38 26
-9 0 -13 5 -10 10 4 6 11 8 16 5 5 -4 9 -2 9 3 0 19 33 22 51 6 18 -17 19 -14
19 79 0 54 -3 97 -7 97 -5 0 -33 -14 -63 -31z m-42 -220 c-2 -6 -8 -10 -13
-10 -5 0 -11 4 -13 10 -2 6 4 11 13 11 9 0 15 -5 13 -11z m-246 -46 c20 -18
24 -58 8 -68 -16 -10 -60 23 -55 40 3 8 7 22 10 30 6 19 14 19 37 -2z m248
-42 c0 -3 -8 -17 -17 -31 l-17 -24 -12 35 c-18 48 -17 49 16 38 17 -6 30 -14
30 -18z m-317 -36 c-7 -21 -13 -19 -13 6 0 11 4 18 10 14 5 -3 7 -12 3 -20z
m-183 -9 c0 -11 -19 -15 -25 -6 -3 5 1 10 9 10 9 0 16 -2 16 -4z m450 -40 c-8
-8 -13 -7 -20 4 -12 20 -1 33 16 19 11 -9 12 -15 4 -23z m-500 -6 c0 -10 -5
-22 -11 -25 -6 -5 -4 -11 6 -19 15 -11 15 -14 -4 -36 l-20 -25 -2 30 c-2 17
-3 39 -4 50 -2 27 14 57 26 50 5 -4 9 -15 9 -25z m-10 -120 c0 -5 -2 -10 -4
-10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m-215 -10 c3 -5 1
-10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z m152 -36 c7 3
15 1 17 -3 3 -5 18 -11 34 -14 24 -5 28 -11 30 -44 1 -21 5 -55 8 -76 5 -37 5
-38 -18 -26 -40 21 -128 82 -128 88 -1 3 -7 31 -14 62 l-14 57 36 -24 c20 -14
42 -22 49 -20z m433 6 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10
6 0 10 -4 10 -10z m-690 -93 c0 -5 -5 -5 -10 -2 -6 4 -8 10 -5 15 3 5 -4 11
-15 15 -22 7 -26 35 -8 53 10 10 15 3 25 -30 7 -24 13 -47 13 -51z m121 -25
c15 -27 15 -31 -1 -61 l-16 -33 -17 26 c-9 14 -17 31 -17 38 0 9 -3 9 -10 -2
-5 -8 -10 -11 -10 -7 0 12 40 67 48 67 4 0 14 -13 23 -28z m270 -19 c-9 -17
-10 -17 -16 0 -4 9 -4 25 0 35 6 16 7 16 16 0 7 -12 7 -24 0 -35z m-505 1 c17
-16 24 -32 21 -45 -3 -10 0 -19 6 -20 7 0 2 -4 -11 -10 -25 -10 -72 13 -72 35
0 12 23 66 28 66 2 0 14 -12 28 -26z m73 -8 c7 -8 8 -17 3 -20 -6 -3 -12 3
-15 14 -6 24 -4 25 12 6z m491 -36 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6
-1 10 4 10 6 0 11 -4 11 -10z m-746 -36 c3 -8 2 -12 -4 -9 -6 3 -10 10 -10 16
0 14 7 11 14 -7z m-42 -56 c-9 -9 -12 -7 -12 12 0 19 3 21 12 12 9 -9 9 -15 0
-24z m405 10 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m-457 -59
c0 -10 -4 -19 -10 -19 -5 0 -10 12 -10 26 0 14 4 23 10 19 6 -3 10 -15 10 -26z
m203 16 c4 -8 2 -17 -2 -20 -5 -2 -11 4 -14 15 -6 23 8 27 16 5z m87 -84 c0
-11 5 -21 11 -23 5 -2 7 -11 3 -21 -5 -14 -13 -17 -33 -11 -14 4 -37 8 -51 9
-16 1 -26 9 -28 21 -3 15 -1 16 9 7 9 -10 18 -7 41 12 35 30 48 31 48 6z
m-485 -70 c-10 -16 -25 -3 -25 22 0 20 1 20 16 5 9 -9 13 -21 9 -27z m407 -88
c2 -3 -9 -7 -24 -9 -23 -2 -28 1 -29 19 0 12 -2 30 -4 40 -2 12 5 8 25 -13 16
-17 30 -34 32 -37z m-572 41 c0 -8 -5 -12 -10 -9 -6 4 -8 11 -5 16 9 14 15 11
15 -7z m-157 -46 c6 -42 -2 -49 -21 -18 -9 15 -10 26 -1 41 13 25 15 24 22
-23z m47 32 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4
10 -10z m590 -36 c0 -8 -4 -14 -10 -14 -5 0 -10 9 -10 21 0 11 5 17 10 14 6
-3 10 -13 10 -21z m-537 -25 c-5 -5 -9 3 -11 19 -1 24 0 25 9 9 7 -11 7 -23 2
-28z m107 20 c0 -11 -4 -18 -10 -14 -5 3 -7 12 -3 20 7 21 13 19 13 -6z m1585
-32 c0 -26 -20 -25 -23 2 -3 16 1 22 10 19 7 -3 13 -12 13 -21z m-1897 -43 c1
-14 6 -33 11 -42 9 -18 4 -42 -8 -42 -10 0 -31 46 -31 68 0 14 17 42 25 42 0
0 2 -11 3 -26z m57 7 c3 -5 1 -12 -5 -16 -5 -3 -10 1 -10 9 0 18 6 21 15 7z
m187 -8 c10 -9 18 -23 18 -32 -1 -19 -29 -72 -30 -55 0 7 -9 14 -20 17 -14 3
-20 14 -20 32 0 19 4 26 14 22 10 -4 12 0 9 14 -6 23 4 24 29 2z m73 -32 c3
-5 1 -12 -5 -16 -5 -3 -10 1 -10 9 0 18 6 21 15 7z m1385 -67 c0 -9 -5 -12
-12 -8 -27 15 -38 27 -32 35 8 13 44 -9 44 -27z m-1552 -8 c7 1 8 0 3 -2 -6
-3 -8 -11 -6 -17 17 -56 15 -67 -7 -67 -48 1 -73 50 -52 105 6 14 9 14 28 -3
11 -11 27 -18 34 -16z m109 2 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1
-19z m105 -62 c-16 -30 -17 -30 -20 -8 -3 18 -7 21 -17 12 -10 -8 -14 -4 -18
16 -3 18 -1 22 6 15 18 -18 24 -12 19 22 l-4 32 25 -29 c25 -29 25 -30 9 -60z
m-515 58 c10 -28 -13 -37 -25 -9 -10 21 -9 25 4 25 8 0 17 -7 21 -16z m373
-49 c0 -8 -4 -17 -10 -20 -6 -4 -10 5 -10 20 0 15 4 24 10 20 6 -3 10 -12 10
-20z m-320 -16 c0 -24 -12 -24 -27 0 -10 15 -10 21 0 24 18 6 27 -2 27 -24z
m-110 -5 c0 -8 -5 -12 -10 -9 -6 4 -8 11 -5 16 9 14 15 11 15 -7z m220 12 c0
-3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5 11 10 11 6 0 10 -2 10 -4z m1790
-38 c0 -30 0 -31 -20 -13 -19 17 -19 19 -2 31 23 18 22 18 22 -18z m-1950 -34
c0 -8 -5 -12 -10 -9 -6 4 -8 11 -5 16 9 14 15 11 15 -7z m100 6 c0 -5 -2 -10
-4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m301 -16 c9 -11 8
-21 -1 -44 -16 -39 -26 -38 -33 3 -3 17 -8 40 -12 49 -5 13 -2 15 14 11 11 -3
26 -11 32 -19z m1259 -51 c0 -30 -20 -73 -35 -73 -6 0 -19 15 -27 34 -16 32
-16 35 9 71 l26 38 13 -24 c7 -13 13 -34 14 -46z m-1873 35 c-3 -8 -6 -5 -6 6
-1 11 2 17 5 13 3 -3 4 -12 1 -19z m2082 -63 c1 -50 -12 -59 -41 -28 -28 31
-29 47 -2 88 l18 28 13 -29 c7 -16 12 -42 12 -59z m-1686 38 c7 -43 3 -53 -24
-53 -10 0 -28 -9 -39 -20 -11 -11 -23 -20 -27 -20 -5 0 -9 -11 -10 -25 -1 -14
-8 -34 -16 -46 -14 -20 -16 -18 -27 38 -6 32 -12 64 -13 72 -2 14 115 89 139
90 6 1 14 -15 17 -36z m95 9 c14 -9 16 -159 1 -167 -9 -6 -59 56 -59 75 0 7 9
28 21 46 11 19 18 38 14 44 -7 12 5 13 23 2z m-535 -39 c-6 -14 -8 -14 -14 3
-12 30 -10 37 5 24 8 -6 12 -19 9 -27z m777 -88 c-6 -19 -13 -35 -15 -35 -3 0
-15 16 -29 36 -24 35 -24 38 -9 70 l16 34 24 -36 c22 -31 23 -39 13 -69z
m-313 53 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m385 -50 c-13
-13 -26 8 -17 30 6 16 7 16 17 -1 7 -13 7 -22 0 -29z m908 2 c0 -5 -2 -10 -4
-10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m-845 -53 c17 -33 17
-35 -1 -65 -10 -18 -21 -32 -24 -31 -3 0 -15 13 -28 30 l-24 29 22 35 c12 19
25 35 29 35 4 0 16 -15 26 -33z m-265 -59 l0 -32 -16 23 c-14 19 -14 25 -3 32
19 12 19 13 19 -23z m-220 -19 c0 -6 -4 -7 -10 -4 -5 3 -10 11 -10 16 0 6 5 7
10 4 6 -3 10 -11 10 -16z m436 -96 c-11 -12 -27 13 -20 32 7 17 8 17 17 -3 6
-12 7 -25 3 -29z m-52 -44 c-8 -13 -24 -1 -24 18 0 13 3 13 15 3 8 -7 12 -16
9 -21z m196 16 c0 -8 -4 -15 -10 -15 -5 0 -7 7 -4 15 4 8 8 15 10 15 2 0 4 -7
4 -15z m-93 -61 c17 -4 -4 -54 -23 -54 -9 0 -34 48 -34 66 0 2 34 -4 57 -12z
m-57 -20 c0 -8 -4 -14 -10 -14 -5 0 -10 9 -10 21 0 11 5 17 10 14 6 -3 10 -13
10 -21z m60 -60 c0 -8 -5 -12 -10 -9 -6 4 -8 11 -5 16 9 14 15 11 15 -7z m103
-40 c18 -24 18 -25 -7 -55 l-26 -31 0 26 c0 14 0 27 0 29 -1 1 -5 19 -9 39 -8
32 -7 35 8 26 9 -5 24 -21 34 -34z m364 14 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13
3 -3 4 -12 1 -19z m-247 -69 c0 -20 -15 -26 -24 -10 -8 13 4 42 15 35 5 -3 9
-14 9 -25z m-63 -64 c0 -8 -4 -12 -9 -9 -5 3 -6 10 -3 15 9 13 12 11 12 -6z"/>
<path d="M210 982 c0 -45 3 -82 6 -82 5 0 68 35 83 46 3 2 -42 62 -81 109 -4
5 -8 -28 -8 -73z"/>
<path d="M1440 1001 c-7 -15 -7 -21 0 -21 12 0 25 28 17 36 -3 3 -10 -4 -17
-15z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

File diff suppressed because one or more lines are too long

2
data/static/js/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

8
git_rev_macro.py Normal file
View File

@@ -0,0 +1,8 @@
import subprocess
revision = (
subprocess.check_output(["git", "rev-parse", "--short=10", "HEAD"])
.strip()
.decode("utf-8")
)
print("-DGIT_REV='\"%s\"'" % revision)

2
lib/ESP Async WebServer/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode
.DS_Store

View File

@@ -0,0 +1 @@
{"type": "library", "name": "ESP Async WebServer", "version": "1.2.3", "spec": {"owner": "me-no-dev", "id": 306, "name": "ESP Async WebServer", "requirements": null, "uri": null}}

View File

@@ -0,0 +1,46 @@
sudo: false
language: python
os:
- linux
git:
depth: false
stages:
- build
jobs:
include:
- name: "Build Arduino ESP32"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32
- name: "Build Arduino ESP8266"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266
- name: "Build Platformio ESP32"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 1 1
- name: "Build Platformio ESP8266"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 1 1
notifications:
email:
on_success: change
on_failure: change
webhooks:
urls:
- https://webhooks.gitter.im/e/60e65d0c78ea0a920347
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -0,0 +1,17 @@
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -0,0 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src
CXXFLAGS += -fno-rtti

View File

@@ -0,0 +1,3 @@
JsonArray KEYWORD1
add KEYWORD2
createArray KEYWORD3

View File

@@ -0,0 +1,33 @@
{
"name":"ESP Async WebServer",
"description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32",
"keywords":"http,async,websocket,webserver",
"authors":
{
"name": "Hristo Gochkov",
"maintainer": true
},
"repository":
{
"type": "git",
"url": "https://github.com/me-no-dev/ESPAsyncWebServer.git"
},
"version": "1.2.3",
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": ["espressif8266", "espressif32"],
"dependencies": [
{
"name": "ESPAsyncTCP",
"platforms": "espressif8266"
},
{
"name": "AsyncTCP",
"platforms": "espressif32"
},
{
"name": "Hash",
"platforms": "espressif8266"
}
]
}

View File

@@ -0,0 +1,9 @@
name=ESP Async WebServer
version=1.2.3
author=Me-No-Dev
maintainer=Me-No-Dev
sentence=Async Web Server for ESP8266 and ESP31B
paragraph=Async Web Server for ESP8266 and ESP31B
category=Other
url=https://github.com/me-no-dev/ESPAsyncWebServer
architectures=*

View File

@@ -0,0 +1,363 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "AsyncEventSource.h"
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
String ev = "";
if(reconnect){
ev += "retry: ";
ev += String(reconnect);
ev += "\r\n";
}
if(id){
ev += "id: ";
ev += String(id);
ev += "\r\n";
}
if(event != NULL){
ev += "event: ";
ev += String(event);
ev += "\r\n";
}
if(message != NULL){
size_t messageLen = strlen(message);
char * lineStart = (char *)message;
char * lineEnd;
do {
char * nextN = strchr(lineStart, '\n');
char * nextR = strchr(lineStart, '\r');
if(nextN == NULL && nextR == NULL){
size_t llen = ((char *)message + messageLen) - lineStart;
char * ldata = (char *)malloc(llen+1);
if(ldata != NULL){
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += "data: ";
ev += ldata;
ev += "\r\n\r\n";
free(ldata);
}
lineStart = (char *)message + messageLen;
} else {
char * nextLine = NULL;
if(nextN != NULL && nextR != NULL){
if(nextR < nextN){
lineEnd = nextR;
if(nextN == (nextR + 1))
nextLine = nextN + 1;
else
nextLine = nextR + 1;
} else {
lineEnd = nextN;
if(nextR == (nextN + 1))
nextLine = nextR + 1;
else
nextLine = nextN + 1;
}
} else if(nextN != NULL){
lineEnd = nextN;
nextLine = nextN + 1;
} else {
lineEnd = nextR;
nextLine = nextR + 1;
}
size_t llen = lineEnd - lineStart;
char * ldata = (char *)malloc(llen+1);
if(ldata != NULL){
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += "data: ";
ev += ldata;
ev += "\r\n";
free(ldata);
}
lineStart = nextLine;
if(lineStart == ((char *)message + messageLen))
ev += "\r\n";
}
} while(lineStart < ((char *)message + messageLen));
}
return ev;
}
// Message
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
: _data(nullptr), _len(len), _sent(0), _acked(0)
{
_data = (uint8_t*)malloc(_len+1);
if(_data == nullptr){
_len = 0;
} else {
memcpy(_data, data, len);
_data[_len] = 0;
}
}
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
if(_data != NULL)
free(_data);
}
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
// If the whole message is now acked...
if(_acked + len > _len){
// Return the number of extra bytes acked (they will be carried on to the next message)
const size_t extra = _acked + len - _len;
_acked = _len;
return extra;
}
// Return that no extra bytes left.
_acked += len;
return 0;
}
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
const size_t len = _len - _sent;
if(client->space() < len){
return 0;
}
size_t sent = client->add((const char *)_data, len);
if(client->canSend())
client->send();
_sent += sent;
return sent;
}
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
{
_client = request->client();
_server = server;
_lastId = 0;
if(request->hasHeader("Last-Event-ID"))
_lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str());
_client->setRxTimeout(0);
_client->onError(NULL, NULL);
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
_client->onPoll([](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
_client->onData(NULL, NULL);
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
_server->_addClient(this);
delete request;
}
AsyncEventSourceClient::~AsyncEventSourceClient(){
_messageQueue.free();
close();
}
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
if(dataMessage == NULL)
return;
if(!connected()){
delete dataMessage;
return;
}
_messageQueue.add(dataMessage);
_runQueue();
}
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
while(len && !_messageQueue.isEmpty()){
len = _messageQueue.front()->ack(len, time);
if(_messageQueue.front()->finished())
_messageQueue.remove(_messageQueue.front());
}
_runQueue();
}
void AsyncEventSourceClient::_onPoll(){
if(!_messageQueue.isEmpty()){
_runQueue();
}
}
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
_client->close(true);
}
void AsyncEventSourceClient::_onDisconnect(){
_client = NULL;
_server->_handleDisconnect(this);
}
void AsyncEventSourceClient::close(){
if(_client != NULL)
_client->close();
}
void AsyncEventSourceClient::write(const char * message, size_t len){
_queueMessage(new AsyncEventSourceMessage(message, len));
}
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
String ev = generateEventMessage(message, event, id, reconnect);
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
}
void AsyncEventSourceClient::_runQueue(){
while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
_messageQueue.remove(_messageQueue.front());
}
for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
{
if(!(*i)->sent())
(*i)->send(_client);
}
}
// Handler
AsyncEventSource::AsyncEventSource(const String& url)
: _url(url)
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
, _connectcb(NULL)
{}
AsyncEventSource::~AsyncEventSource(){
close();
}
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
_connectcb = cb;
}
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
/*char * temp = (char *)malloc(2054);
if(temp != NULL){
memset(temp+1,' ',2048);
temp[0] = ':';
temp[2049] = '\r';
temp[2050] = '\n';
temp[2051] = '\r';
temp[2052] = '\n';
temp[2053] = 0;
client->write((const char *)temp, 2053);
free(temp);
}*/
_clients.add(client);
if(_connectcb)
_connectcb(client);
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
_clients.remove(client);
}
void AsyncEventSource::close(){
for(const auto &c: _clients){
if(c->connected())
c->close();
}
}
// pmb fix
size_t AsyncEventSource::avgPacketsWaiting() const {
if(_clients.isEmpty())
return 0;
size_t aql=0;
uint32_t nConnectedClients=0;
for(const auto &c: _clients){
if(c->connected()) {
aql+=c->packetsWaiting();
++nConnectedClients;
}
}
// return aql / nConnectedClients;
return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
}
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
String ev = generateEventMessage(message, event, id, reconnect);
for(const auto &c: _clients){
if(c->connected()) {
c->write(ev.c_str(), ev.length());
}
}
}
size_t AsyncEventSource::count() const {
return _clients.count_if([](AsyncEventSourceClient *c){
return c->connected();
});
}
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
return false;
}
request->addInterestingHeader("Last-Event-ID");
return true;
}
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication();
request->send(new AsyncEventSourceResponse(this));
}
// Response
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
_server = server;
_code = 200;
_contentType = "text/event-stream";
_sendContentLength = false;
addHeader("Cache-Control", "no-cache");
addHeader("Connection","keep-alive");
}
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
String out = _assembleHead(request->version());
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
if(len){
new AsyncEventSourceClient(request, _server);
}
return 0;
}

View File

@@ -0,0 +1,116 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCEVENTSOURCE_H_
#define ASYNCEVENTSOURCE_H_
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#else
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
class AsyncEventSource;
class AsyncEventSourceResponse;
class AsyncEventSourceClient;
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
class AsyncEventSourceMessage {
private:
uint8_t * _data;
size_t _len;
size_t _sent;
//size_t _ack;
size_t _acked;
public:
AsyncEventSourceMessage(const char * data, size_t len);
~AsyncEventSourceMessage();
size_t ack(size_t len, uint32_t time __attribute__((unused)));
size_t send(AsyncClient *client);
bool finished(){ return _acked == _len; }
bool sent() { return _sent == _len; }
};
class AsyncEventSourceClient {
private:
AsyncClient *_client;
AsyncEventSource *_server;
uint32_t _lastId;
LinkedList<AsyncEventSourceMessage *> _messageQueue;
void _queueMessage(AsyncEventSourceMessage *dataMessage);
void _runQueue();
public:
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient();
AsyncClient* client(){ return _client; }
void close();
void write(const char * message, size_t len);
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
bool connected() const { return (_client != NULL) && _client->connected(); }
uint32_t lastId() const { return _lastId; }
size_t packetsWaiting() const { return _messageQueue.length(); }
//system callbacks (do not call)
void _onAck(size_t len, uint32_t time);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
};
class AsyncEventSource: public AsyncWebHandler {
private:
String _url;
LinkedList<AsyncEventSourceClient *> _clients;
ArEventHandlerFunction _connectcb;
public:
AsyncEventSource(const String& url);
~AsyncEventSource();
const char * url() const { return _url.c_str(); }
void close();
void onConnect(ArEventHandlerFunction cb);
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
size_t count() const; //number clinets connected
size_t avgPacketsWaiting() const;
//system callbacks (do not call)
void _addClient(AsyncEventSourceClient * client);
void _handleDisconnect(AsyncEventSourceClient * client);
virtual bool canHandle(AsyncWebServerRequest *request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final;
};
class AsyncEventSourceResponse: public AsyncWebServerResponse {
private:
String _content;
AsyncEventSource *_server;
public:
AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
};
#endif /* ASYNCEVENTSOURCE_H_ */

View File

@@ -0,0 +1,252 @@
// AsyncJson.h
/*
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
Example of callback in use
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
root["key1"] = "key number one";
JsonObject& nested = root.createNestedObject("nested");
nested["key1"] = "key number one";
response->setLength();
request->send(response);
});
--------------------
Async Request to use with ArduinoJson and AsyncWebServer
Written by Arsène von Wyss (avonwyss)
Example
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject& jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
*/
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <Print.h>
#if ARDUINOJSON_VERSION_MAJOR == 5
#define ARDUINOJSON_5_COMPATIBILITY
#else
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif
constexpr const char* JSON_MIMETYPE = "application/json";
/*
* Json Response
* */
class ChunkPrint : public Print {
private:
uint8_t* _destination;
size_t _to_skip;
size_t _to_write;
size_t _pos;
public:
ChunkPrint(uint8_t* destination, size_t from, size_t len)
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
virtual ~ChunkPrint(){}
size_t write(uint8_t c){
if (_to_skip > 0) {
_to_skip--;
return 1;
} else if (_to_write > 0) {
_to_write--;
_destination[_pos++] = c;
return 1;
}
return 0;
}
size_t write(const uint8_t *buffer, size_t size)
{
return this->Print::write(buffer, size);
}
};
class AsyncJsonResponse: public AsyncAbstractResponse {
protected:
#ifdef ARDUINOJSON_5_COMPATIBILITY
DynamicJsonBuffer _jsonBuffer;
#else
DynamicJsonDocument _jsonBuffer;
#endif
JsonVariant _root;
bool _isValid;
public:
#ifdef ARDUINOJSON_5_COMPATIBILITY
AsyncJsonResponse(bool isArray=false): _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if(isArray)
_root = _jsonBuffer.createArray();
else
_root = _jsonBuffer.createObject();
}
#else
AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if(isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
#endif
~AsyncJsonResponse() {}
JsonVariant & getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
size_t setLength() {
#ifdef ARDUINOJSON_5_COMPATIBILITY
_contentLength = _root.measureLength();
#else
_contentLength = measureJson(_root);
#endif
if (_contentLength) { _isValid = true; }
return _contentLength;
}
size_t getSize() { return _jsonBuffer.size(); }
size_t _fillBuffer(uint8_t *data, size_t len){
ChunkPrint dest(data, _sentLength, len);
#ifdef ARDUINOJSON_5_COMPATIBILITY
_root.printTo( dest ) ;
#else
serializeJson(_root, dest);
#endif
return len;
}
};
class PrettyAsyncJsonResponse: public AsyncJsonResponse {
public:
#ifdef ARDUINOJSON_5_COMPATIBILITY
PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {}
#else
PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
#endif
size_t setLength () {
#ifdef ARDUINOJSON_5_COMPATIBILITY
_contentLength = _root.measurePrettyLength ();
#else
_contentLength = measureJsonPretty(_root);
#endif
if (_contentLength) {_isValid = true;}
return _contentLength;
}
size_t _fillBuffer (uint8_t *data, size_t len) {
ChunkPrint dest (data, _sentLength, len);
#ifdef ARDUINOJSON_5_COMPATIBILITY
_root.prettyPrintTo (dest);
#else
serializeJsonPretty(_root, dest);
#endif
return len;
}
};
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
class AsyncCallbackJsonWebHandler: public AsyncWebHandler {
private:
protected:
const String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#ifndef ARDUINOJSON_5_COMPATIBILITY
const size_t maxJsonBufferSize;
#endif
size_t _maxContentLength;
public:
#ifdef ARDUINOJSON_5_COMPATIBILITY
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#else
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE)
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#endif
void setMethod(WebRequestMethodComposite method){ _method = method; }
void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; }
void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; }
virtual bool canHandle(AsyncWebServerRequest *request) override final{
if(!_onRequest)
return false;
if(!(_method & request->method()))
return false;
if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
return false;
if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) )
return false;
request->addInterestingHeader("ANY");
return true;
}
virtual void handleRequest(AsyncWebServerRequest *request) override final {
if(_onRequest) {
if (request->_tempObject != NULL) {
#ifdef ARDUINOJSON_5_COMPATIBILITY
DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
if (json.success()) {
#else
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
if(!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
_onRequest(request, json);
return;
}
}
request->send(_contentLength > _maxContentLength ? 413 : 400);
} else {
request->send(500);
}
}
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
}
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
}
if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
}
}
}
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSOCKET_H_
#define ASYNCWEBSOCKET_H_
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#define WS_MAX_QUEUED_MESSAGES 32
#else
#include <ESPAsyncTCP.h>
#define WS_MAX_QUEUED_MESSAGES 8
#endif
#include <ESPAsyncWebServer.h>
#include "AsyncWebSynchronization.h"
#ifdef ESP8266
#include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h>
#endif
#endif
#ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8
#else
#define DEFAULT_MAX_WS_CLIENTS 4
#endif
class AsyncWebSocket;
class AsyncWebSocketResponse;
class AsyncWebSocketClient;
class AsyncWebSocketControl;
typedef struct {
/** Message type as defined by enum AwsFrameType.
* Note: Applications will only see WS_TEXT and WS_BINARY.
* All other types are handled by the library. */
uint8_t message_opcode;
/** Frame number of a fragmented message. */
uint32_t num;
/** Is this the last frame in a fragmented message ?*/
uint8_t final;
/** Is this frame masked? */
uint8_t masked;
/** Message type as defined by enum AwsFrameType.
* This value is the same as message_opcode for non-fragmented
* messages, but may also be WS_CONTINUATION in a fragmented message. */
uint8_t opcode;
/** Length of the current frame.
* This equals the total length of the message if num == 0 && final == true */
uint64_t len;
/** Mask key */
uint8_t mask[4];
/** Offset of the data inside the current frame. */
uint64_t index;
} AwsFrameInfo;
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
class AsyncWebSocketMessageBuffer {
private:
uint8_t * _data;
size_t _len;
bool _lock;
uint32_t _count;
public:
AsyncWebSocketMessageBuffer();
AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &);
AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&);
~AsyncWebSocketMessageBuffer();
void operator ++(int i) { _count++; }
void operator --(int i) { if (_count > 0) { _count--; } ; }
bool reserve(size_t size);
void lock() { _lock = true; }
void unlock() { _lock = false; }
uint8_t * get() { return _data; }
size_t length() { return _len; }
uint32_t count() { return _count; }
bool canDelete() { return (!_count && !_lock); }
friend AsyncWebSocket;
};
class AsyncWebSocketMessage {
protected:
uint8_t _opcode;
bool _mask;
AwsMessageStatus _status;
public:
AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){}
virtual ~AsyncWebSocketMessage(){}
virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){}
virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; }
virtual bool finished(){ return _status != WS_MSG_SENDING; }
virtual bool betweenFrames() const { return false; }
};
class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
private:
size_t _len;
size_t _sent;
size_t _ack;
size_t _acked;
uint8_t * _data;
public:
AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false);
AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false);
virtual ~AsyncWebSocketBasicMessage() override;
virtual bool betweenFrames() const override { return _acked == _ack; }
virtual void ack(size_t len, uint32_t time) override ;
virtual size_t send(AsyncClient *client) override ;
};
class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
private:
uint8_t * _data;
size_t _len;
size_t _sent;
size_t _ack;
size_t _acked;
AsyncWebSocketMessageBuffer * _WSbuffer;
public:
AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false);
virtual ~AsyncWebSocketMultiMessage() override;
virtual bool betweenFrames() const override { return _acked == _ack; }
virtual void ack(size_t len, uint32_t time) override ;
virtual size_t send(AsyncClient *client) override ;
};
class AsyncWebSocketClient {
private:
AsyncClient *_client;
AsyncWebSocket *_server;
uint32_t _clientId;
AwsClientStatus _status;
LinkedList<AsyncWebSocketControl *> _controlQueue;
LinkedList<AsyncWebSocketMessage *> _messageQueue;
uint8_t _pstate;
AwsFrameInfo _pinfo;
uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod;
void _queueMessage(AsyncWebSocketMessage *dataMessage);
void _queueControl(AsyncWebSocketControl *controlMessage);
void _runQueue();
public:
void *_tempObject;
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
~AsyncWebSocketClient();
//client id increments for the given server
uint32_t id(){ return _clientId; }
AwsClientStatus status(){ return _status; }
AsyncClient* client(){ return _client; }
AsyncWebSocket *server(){ return _server; }
AwsFrameInfo const &pinfo() const { return _pinfo; }
IPAddress remoteIP();
uint16_t remotePort();
//control frames
void close(uint16_t code=0, const char * message=NULL);
void ping(uint8_t *data=NULL, size_t len=0);
//set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds){
_keepAlivePeriod = seconds * 1000;
}
uint16_t keepAlivePeriod(){
return (uint16_t)(_keepAlivePeriod / 1000);
}
//data packets
void message(AsyncWebSocketMessage *message){ _queueMessage(message); }
bool queueIsFull();
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
#ifndef ESP32
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
#endif
void text(const char * message, size_t len);
void text(const char * message);
void text(uint8_t * message, size_t len);
void text(char * message);
void text(const String &message);
void text(const __FlashStringHelper *data);
void text(AsyncWebSocketMessageBuffer *buffer);
void binary(const char * message, size_t len);
void binary(const char * message);
void binary(uint8_t * message, size_t len);
void binary(char * message);
void binary(const String &message);
void binary(const __FlashStringHelper *data, size_t len);
void binary(AsyncWebSocketMessageBuffer *buffer);
bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; }
//system callbacks (do not call)
void _onAck(size_t len, uint32_t time);
void _onError(int8_t);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
void _onData(void *pbuf, size_t plen);
};
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
//WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket: public AsyncWebHandler {
public:
typedef LinkedList<AsyncWebSocketClient *> AsyncWebSocketClientLinkedList;
private:
String _url;
AsyncWebSocketClientLinkedList _clients;
uint32_t _cNextId;
AwsEventHandler _eventHandler;
bool _enabled;
AsyncWebLock _lock;
public:
AsyncWebSocket(const String& url);
~AsyncWebSocket();
const char * url() const { return _url.c_str(); }
void enable(bool e){ _enabled = e; }
bool enabled() const { return _enabled; }
bool availableForWriteAll();
bool availableForWrite(uint32_t id);
size_t count() const;
AsyncWebSocketClient * client(uint32_t id);
bool hasClient(uint32_t id){ return client(id) != NULL; }
void close(uint32_t id, uint16_t code=0, const char * message=NULL);
void closeAll(uint16_t code=0, const char * message=NULL);
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);
void pingAll(uint8_t *data=NULL, size_t len=0); // done
void text(uint32_t id, const char * message, size_t len);
void text(uint32_t id, const char * message);
void text(uint32_t id, uint8_t * message, size_t len);
void text(uint32_t id, char * message);
void text(uint32_t id, const String &message);
void text(uint32_t id, const __FlashStringHelper *message);
void textAll(const char * message, size_t len);
void textAll(const char * message);
void textAll(uint8_t * message, size_t len);
void textAll(char * message);
void textAll(const String &message);
void textAll(const __FlashStringHelper *message); // need to convert
void textAll(AsyncWebSocketMessageBuffer * buffer);
void binary(uint32_t id, const char * message, size_t len);
void binary(uint32_t id, const char * message);
void binary(uint32_t id, uint8_t * message, size_t len);
void binary(uint32_t id, char * message);
void binary(uint32_t id, const String &message);
void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
void binaryAll(const char * message, size_t len);
void binaryAll(const char * message);
void binaryAll(uint8_t * message, size_t len);
void binaryAll(char * message);
void binaryAll(const String &message);
void binaryAll(const __FlashStringHelper *message, size_t len);
void binaryAll(AsyncWebSocketMessageBuffer * buffer);
void message(uint32_t id, AsyncWebSocketMessage *message);
void messageAll(AsyncWebSocketMultiMessage *message);
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
#ifndef ESP32
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
#endif
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
//event listener
void onEvent(AwsEventHandler handler){
_eventHandler = handler;
}
//system callbacks (do not call)
uint32_t _getNextId(){ return _cNextId++; }
void _addClient(AsyncWebSocketClient * client);
void _handleDisconnect(AsyncWebSocketClient * client);
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
virtual bool canHandle(AsyncWebServerRequest *request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final;
// messagebuffer functions/objects.
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
LinkedList<AsyncWebSocketMessageBuffer *> _buffers;
void _cleanBuffers();
AsyncWebSocketClientLinkedList getClients() const;
};
//WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse: public AsyncWebServerResponse {
private:
String _content;
AsyncWebSocket *_server;
public:
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
};
#endif /* ASYNCWEBSOCKET_H_ */

View File

@@ -0,0 +1,87 @@
#ifndef ASYNCWEBSYNCHRONIZATION_H_
#define ASYNCWEBSYNCHRONIZATION_H_
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
#include <ESPAsyncWebServer.h>
#ifdef ESP32
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
class AsyncWebLock
{
private:
SemaphoreHandle_t _lock;
mutable void *_lockedBy;
public:
AsyncWebLock() {
_lock = xSemaphoreCreateBinary();
_lockedBy = NULL;
xSemaphoreGive(_lock);
}
~AsyncWebLock() {
vSemaphoreDelete(_lock);
}
bool lock() const {
extern void *pxCurrentTCB;
if (_lockedBy != pxCurrentTCB) {
xSemaphoreTake(_lock, portMAX_DELAY);
_lockedBy = pxCurrentTCB;
return true;
}
return false;
}
void unlock() const {
_lockedBy = NULL;
xSemaphoreGive(_lock);
}
};
#else
// This is the 8266 version of the Sync Lock which is currently unimplemented
class AsyncWebLock
{
public:
AsyncWebLock() {
}
~AsyncWebLock() {
}
bool lock() const {
return false;
}
void unlock() const {
}
};
#endif
class AsyncWebLockGuard
{
private:
const AsyncWebLock *_lock;
public:
AsyncWebLockGuard(const AsyncWebLock &l) {
if (l.lock()) {
_lock = &l;
} else {
_lock = NULL;
}
}
~AsyncWebLockGuard() {
if (_lock) {
_lock->unlock();
}
}
};
#endif // ASYNCWEBSYNCHRONIZATION_H_

View File

@@ -0,0 +1,465 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _ESPAsyncWebServer_H_
#define _ESPAsyncWebServer_H_
#include "Arduino.h"
#include <functional>
#include "FS.h"
#include "StringArray.h"
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#else
#error Platform not supported
#endif
#define DEBUGF(...) //Serial.printf(__VA_ARGS__)
class AsyncWebServer;
class AsyncWebServerRequest;
class AsyncWebServerResponse;
class AsyncWebHeader;
class AsyncWebParameter;
class AsyncWebRewrite;
class AsyncWebHandler;
class AsyncStaticWebHandler;
class AsyncCallbackWebHandler;
class AsyncResponseStream;
#ifndef WEBSERVER_H
typedef enum {
HTTP_GET = 0b00000001,
HTTP_POST = 0b00000010,
HTTP_DELETE = 0b00000100,
HTTP_PUT = 0b00001000,
HTTP_PATCH = 0b00010000,
HTTP_HEAD = 0b00100000,
HTTP_OPTIONS = 0b01000000,
HTTP_ANY = 0b01111111,
} WebRequestMethod;
#endif
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
typedef uint8_t WebRequestMethodComposite;
typedef std::function<void(void)> ArDisconnectHandler;
/*
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
* */
class AsyncWebParameter {
private:
String _name;
String _value;
size_t _size;
bool _isForm;
bool _isFile;
public:
AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
const String& name() const { return _name; }
const String& value() const { return _value; }
size_t size() const { return _size; }
bool isPost() const { return _isForm; }
bool isFile() const { return _isFile; }
};
/*
* HEADER :: Chainable object to hold the headers
* */
class AsyncWebHeader {
private:
String _name;
String _value;
public:
AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){}
AsyncWebHeader(const String& data): _name(), _value(){
if(!data) return;
int index = data.indexOf(':');
if (index < 0) return;
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
~AsyncWebHeader(){}
const String& name() const { return _name; }
const String& value() const { return _value; }
String toString() const { return String(_name+": "+_value+"\r\n"); }
};
/*
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
* */
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType;
typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
typedef std::function<String(const String&)> AwsTemplateProcessor;
class AsyncWebServerRequest {
using File = fs::File;
using FS = fs::FS;
friend class AsyncWebServer;
friend class AsyncCallbackWebHandler;
private:
AsyncClient* _client;
AsyncWebServer* _server;
AsyncWebHandler* _handler;
AsyncWebServerResponse* _response;
StringArray _interestingHeaders;
ArDisconnectHandler _onDisconnectfn;
String _temp;
uint8_t _parseState;
uint8_t _version;
WebRequestMethodComposite _method;
String _url;
String _host;
String _contentType;
String _boundary;
String _authorization;
RequestedConnectionType _reqconntype;
void _removeNotInterestingHeaders();
bool _isDigest;
bool _isMultipart;
bool _isPlainPost;
bool _expectingContinue;
size_t _contentLength;
size_t _parsedLength;
LinkedList<AsyncWebHeader *> _headers;
LinkedList<AsyncWebParameter *> _params;
LinkedList<String *> _pathParams;
uint8_t _multiParseState;
uint8_t _boundaryPosition;
size_t _itemStartIndex;
size_t _itemSize;
String _itemName;
String _itemFilename;
String _itemType;
String _itemValue;
uint8_t *_itemBuffer;
size_t _itemBufferIndex;
bool _itemIsFile;
void _onPoll();
void _onAck(size_t len, uint32_t time);
void _onError(int8_t error);
void _onTimeout(uint32_t time);
void _onDisconnect();
void _onData(void *buf, size_t len);
void _addParam(AsyncWebParameter*);
void _addPathParam(const char *param);
bool _parseReqHead();
bool _parseReqHeader();
void _parseLine();
void _parsePlainPostChar(uint8_t data);
void _parseMultipartPostByte(uint8_t data, bool last);
void _addGetParams(const String& params);
void _handleUploadStart();
void _handleUploadByte(uint8_t data, bool last);
void _handleUploadEnd();
public:
File _tempFile;
void *_tempObject;
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
~AsyncWebServerRequest();
AsyncClient* client(){ return _client; }
uint8_t version() const { return _version; }
WebRequestMethodComposite method() const { return _method; }
const String& url() const { return _url; }
const String& host() const { return _host; }
const String& contentType() const { return _contentType; }
size_t contentLength() const { return _contentLength; }
bool multipart() const { return _isMultipart; }
const char * methodToString() const;
const char * requestedConnTypeToString() const;
RequestedConnectionType requestedConnType() const { return _reqconntype; }
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
void onDisconnect (ArDisconnectHandler fn);
//hash is the string representation of:
// base64(user:pass) for basic or
// user:realm:md5(user:realm:pass) for digest
bool authenticate(const char * hash);
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
void setHandler(AsyncWebHandler *handler){ _handler = handler; }
void addInterestingHeader(const String& name);
void redirect(const String& url);
void send(AsyncWebServerResponse *response);
void send(int code, const String& contentType=String(), const String& content=String());
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String());
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460);
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
size_t headers() const; // get header count
bool hasHeader(const String& name) const; // check if header exists
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
AsyncWebHeader* getHeader(const String& name) const;
AsyncWebHeader* getHeader(const __FlashStringHelper * data) const;
AsyncWebHeader* getHeader(size_t num) const;
size_t params() const; // get arguments count
bool hasParam(const String& name, bool post=false, bool file=false) const;
bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const;
AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const;
AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const;
AsyncWebParameter* getParam(size_t num) const;
size_t args() const { return params(); } // get arguments count
const String& arg(const String& name) const; // get request argument value by name
const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
const String& arg(size_t i) const; // get request argument value by number
const String& argName(size_t i) const; // get request argument name by number
bool hasArg(const char* name) const; // check if argument exists
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
const String& pathArg(size_t i) const;
const String& header(const char* name) const;// get request header value by name
const String& header(const __FlashStringHelper * data) const;// get request header value by F(name)
const String& header(size_t i) const; // get request header value by number
const String& headerName(size_t i) const; // get request header name by number
String urlDecode(const String& text) const;
};
/*
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
* */
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction;
bool ON_STA_FILTER(AsyncWebServerRequest *request);
bool ON_AP_FILTER(AsyncWebServerRequest *request);
/*
* REWRITE :: One instance can be handle any Request (done by the Server)
* */
class AsyncWebRewrite {
protected:
String _from;
String _toUrl;
String _params;
ArRequestFilterFunction _filter;
public:
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){
int index = _toUrl.indexOf('?');
if (index > 0) {
_params = _toUrl.substring(index +1);
_toUrl = _toUrl.substring(0, index);
}
}
virtual ~AsyncWebRewrite(){}
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); }
const String& from(void) const { return _from; }
const String& toUrl(void) const { return _toUrl; }
const String& params(void) const { return _params; }
virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); }
};
/*
* HANDLER :: One instance can be attached to any Request (done by the Server)
* */
class AsyncWebHandler {
protected:
ArRequestFilterFunction _filter;
String _username;
String _password;
public:
AsyncWebHandler():_username(""), _password(""){}
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; };
bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); }
virtual ~AsyncWebHandler(){}
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){
return false;
}
virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){}
virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){}
virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){}
virtual bool isRequestHandlerTrivial(){return true;}
};
/*
* RESPONSE :: One instance is created for each Request (attached by the Handler)
* */
typedef enum {
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED
} WebResponseState;
class AsyncWebServerResponse {
protected:
int _code;
LinkedList<AsyncWebHeader *> _headers;
String _contentType;
size_t _contentLength;
bool _sendContentLength;
bool _chunked;
size_t _headLength;
size_t _sentLength;
size_t _ackedLength;
size_t _writtenLength;
WebResponseState _state;
const char* _responseCodeToString(int code);
public:
AsyncWebServerResponse();
virtual ~AsyncWebServerResponse();
virtual void setCode(int code);
virtual void setContentLength(size_t len);
virtual void setContentType(const String& type);
virtual void addHeader(const String& name, const String& value);
virtual String _assembleHead(uint8_t version);
virtual bool _started() const;
virtual bool _finished() const;
virtual bool _failed() const;
virtual bool _sourceValid() const;
virtual void _respond(AsyncWebServerRequest *request);
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
};
/*
* SERVER :: One instance
* */
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)> ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
class AsyncWebServer {
protected:
AsyncServer _server;
LinkedList<AsyncWebRewrite*> _rewrites;
LinkedList<AsyncWebHandler*> _handlers;
AsyncCallbackWebHandler* _catchAllHandler;
public:
AsyncWebServer(uint16_t port);
~AsyncWebServer();
void begin();
void end();
#if ASYNC_TCP_SSL_ENABLED
void onSslFileRequest(AcSSlFileHandler cb, void* arg);
void beginSecure(const char *cert, const char *private_key_file, const char *password);
#endif
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
bool removeRewrite(AsyncWebRewrite* rewrite);
AsyncWebRewrite& rewrite(const char* from, const char* to);
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
bool removeHandler(AsyncWebHandler* handler);
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
void _handleDisconnect(AsyncWebServerRequest *request);
void _attachHandler(AsyncWebServerRequest *request);
void _rewriteRequest(AsyncWebServerRequest *request);
};
class DefaultHeaders {
using headers_t = LinkedList<AsyncWebHeader *>;
headers_t _headers;
DefaultHeaders()
:_headers(headers_t([](AsyncWebHeader *h){ delete h; }))
{}
public:
using ConstIterator = headers_t::ConstIterator;
void addHeader(const String& name, const String& value){
_headers.add(new AsyncWebHeader(name, value));
}
ConstIterator begin() const { return _headers.begin(); }
ConstIterator end() const { return _headers.end(); }
DefaultHeaders(DefaultHeaders const &) = delete;
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
static DefaultHeaders &Instance() {
static DefaultHeaders instance;
return instance;
}
};
#include "WebResponseImpl.h"
#include "WebHandlerImpl.h"
#include "AsyncWebSocket.h"
#include "AsyncEventSource.h"
#endif /* _AsyncWebServer_H_ */

View File

@@ -0,0 +1,544 @@
#include "SPIFFSEditor.h"
#include <FS.h>
//File: edit.htm.gz, Size: 4151
#define edit_htm_gz_len 4151
const uint8_t edit_htm_gz[] PROGMEM = {
0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68,
0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED,
0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6,
0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB,
0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A,
0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61,
0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7,
0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02,
0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C,
0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A,
0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89,
0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76,
0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D,
0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9,
0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B,
0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91,
0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78,
0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78,
0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98,
0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E,
0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41,
0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21,
0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F,
0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74,
0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C,
0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0,
0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C,
0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30,
0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9,
0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61,
0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B,
0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9,
0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B,
0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD,
0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3,
0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77,
0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83,
0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF,
0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3,
0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55,
0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3,
0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF,
0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF,
0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60,
0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1,
0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE,
0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F,
0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0,
0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9,
0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5,
0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15,
0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74,
0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D,
0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD,
0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A,
0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6,
0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2,
0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF,
0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53,
0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2,
0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A,
0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83,
0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26,
0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0,
0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0,
0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84,
0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99,
0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5,
0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9,
0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87,
0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F,
0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6,
0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B,
0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D,
0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25,
0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3,
0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F,
0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35,
0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A,
0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6,
0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7,
0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A,
0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9,
0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97,
0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36,
0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C,
0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A,
0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C,
0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F,
0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11,
0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16,
0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA,
0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB,
0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A,
0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6,
0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28,
0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1,
0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E,
0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E,
0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92,
0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05,
0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8,
0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0,
0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85,
0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40,
0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56,
0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47,
0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA,
0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7,
0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD,
0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61,
0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58,
0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D,
0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8,
0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C,
0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA,
0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49,
0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51,
0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00,
0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A,
0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A,
0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35,
0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F,
0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E,
0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C,
0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64,
0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C,
0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1,
0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B,
0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC,
0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42,
0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B,
0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71,
0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F,
0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28,
0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9,
0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD,
0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6,
0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F,
0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5,
0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8,
0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF,
0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62,
0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C,
0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7,
0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89,
0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29,
0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95,
0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7,
0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB,
0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09,
0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F,
0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60,
0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35,
0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6,
0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B,
0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66,
0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25,
0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E,
0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97,
0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC,
0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE,
0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7,
0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13,
0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0,
0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A,
0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93,
0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E,
0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9,
0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78,
0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5,
0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12,
0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E,
0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35,
0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98,
0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58,
0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3,
0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64,
0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39,
0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D,
0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62,
0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48,
0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D,
0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8,
0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9,
0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30,
0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6,
0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1,
0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56,
0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84,
0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0,
0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC,
0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E,
0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39,
0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B,
0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA,
0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1,
0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1,
0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88,
0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4,
0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC,
0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98,
0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97,
0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8,
0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30,
0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA,
0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B,
0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC,
0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45,
0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD,
0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76,
0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD,
0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76,
0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4,
0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF,
0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4,
0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42,
0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43,
0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B,
0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97,
0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73,
0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B,
0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50,
0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51,
0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25,
0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26,
0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80,
0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9,
0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0,
0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74,
0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA,
0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D,
0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F,
0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2,
0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1,
0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99,
0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D,
0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B,
0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD,
0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F,
0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB,
0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47,
0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59,
0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D,
0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD,
0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94,
0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35,
0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81,
0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D,
0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20,
0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB,
0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B,
0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6,
0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17,
0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8,
0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26,
0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57,
0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36,
0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53,
0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00
};
#define SPIFFS_MAXLENGTH_FILEPATH 32
const char *excludeListFile = "/.exclude.files";
typedef struct ExcludeListS {
char *item;
ExcludeListS *next;
} ExcludeList;
static ExcludeList *excludes = NULL;
static bool matchWild(const char *pattern, const char *testee) {
const char *nxPat = NULL, *nxTst = NULL;
while (*testee) {
if (( *pattern == '?' ) || (*pattern == *testee)){
pattern++;testee++;
continue;
}
if (*pattern=='*'){
nxPat=pattern++; nxTst=testee;
continue;
}
if (nxPat){
pattern = nxPat+1; testee=++nxTst;
continue;
}
return false;
}
while (*pattern=='*'){pattern++;}
return (*pattern == 0);
}
static bool addExclude(const char *item){
size_t len = strlen(item);
if(!len){
return false;
}
ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList));
if(!e){
return false;
}
e->item = (char *)malloc(len+1);
if(!e->item){
free(e);
return false;
}
memcpy(e->item, item, len+1);
e->next = excludes;
excludes = e;
return true;
}
static void loadExcludeList(fs::FS &_fs, const char *filename){
static char linebuf[SPIFFS_MAXLENGTH_FILEPATH];
fs::File excludeFile=_fs.open(filename, "r");
if(!excludeFile){
//addExclude("/*.js.gz");
return;
}
#ifdef ESP32
if(excludeFile.isDirectory()){
excludeFile.close();
return;
}
#endif
if (excludeFile.size() > 0){
uint8_t idx;
bool isOverflowed = false;
while (excludeFile.available()){
linebuf[0] = '\0';
idx = 0;
int lastChar;
do {
lastChar = excludeFile.read();
if(lastChar != '\r'){
linebuf[idx++] = (char) lastChar;
}
} while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH));
if(isOverflowed){
isOverflowed = (lastChar != '\n');
continue;
}
isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH);
linebuf[idx-1] = '\0';
if(!addExclude(linebuf)){
excludeFile.close();
return;
}
}
}
excludeFile.close();
}
static bool isExcluded(fs::FS &_fs, const char *filename) {
if(excludes == NULL){
loadExcludeList(_fs, excludeListFile);
}
ExcludeList *e = excludes;
while(e){
if (matchWild(e->item, filename)){
return true;
}
e = e->next;
}
return false;
}
// WEB HANDLER IMPLEMENTATION
#ifdef ESP32
SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password)
#else
SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs)
#endif
:_fs(fs)
,_username(username)
,_password(password)
,_authenticated(false)
,_startTime(0)
{}
bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){
if(request->url().equalsIgnoreCase("/edit")){
if(request->method() == HTTP_GET){
if(request->hasParam("list"))
return true;
if(request->hasParam("edit")){
request->_tempFile = _fs.open(request->arg("edit"), "r");
if(!request->_tempFile){
return false;
}
#ifdef ESP32
if(request->_tempFile.isDirectory()){
request->_tempFile.close();
return false;
}
#endif
}
if(request->hasParam("download")){
request->_tempFile = _fs.open(request->arg("download"), "r");
if(!request->_tempFile){
return false;
}
#ifdef ESP32
if(request->_tempFile.isDirectory()){
request->_tempFile.close();
return false;
}
#endif
}
request->addInterestingHeader("If-Modified-Since");
return true;
}
else if(request->method() == HTTP_POST)
return true;
else if(request->method() == HTTP_DELETE)
return true;
else if(request->method() == HTTP_PUT)
return true;
}
return false;
}
void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){
if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication();
if(request->method() == HTTP_GET){
if(request->hasParam("list")){
String path = request->getParam("list")->value();
#ifdef ESP32
File dir = _fs.open(path);
#else
Dir dir = _fs.openDir(path);
#endif
path = String();
String output = "[";
#ifdef ESP32
File entry = dir.openNextFile();
while(entry){
#else
while(dir.next()){
fs::File entry = dir.openFile("r");
#endif
if (isExcluded(_fs, entry.name())) {
#ifdef ESP32
entry = dir.openNextFile();
#endif
continue;
}
if (output != "[") output += ',';
output += "{\"type\":\"";
output += "file";
output += "\",\"name\":\"";
output += String(entry.name());
output += "\",\"size\":";
output += String(entry.size());
output += "}";
#ifdef ESP32
entry = dir.openNextFile();
#else
entry.close();
#endif
}
#ifdef ESP32
dir.close();
#endif
output += "]";
request->send(200, "application/json", output);
output = String();
}
else if(request->hasParam("edit") || request->hasParam("download")){
request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download"));
}
else {
const char * buildTime = __DATE__ " " __TIME__ " GMT";
if (request->header("If-Modified-Since").equals(buildTime)) {
request->send(304);
} else {
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len);
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Last-Modified", buildTime);
request->send(response);
}
}
} else if(request->method() == HTTP_DELETE){
if(request->hasParam("path", true)){
_fs.remove(request->getParam("path", true)->value());
request->send(200, "", "DELETE: "+request->getParam("path", true)->value());
} else
request->send(404);
} else if(request->method() == HTTP_POST){
if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value()))
request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value());
else
request->send(500);
} else if(request->method() == HTTP_PUT){
if(request->hasParam("path", true)){
String filename = request->getParam("path", true)->value();
if(_fs.exists(filename)){
request->send(200);
} else {
fs::File f = _fs.open(filename, "w");
if(f){
f.write((uint8_t)0x00);
f.close();
request->send(200, "", "CREATE: "+filename);
} else {
request->send(500);
}
}
} else
request->send(400);
}
}
void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){
_authenticated = true;
request->_tempFile = _fs.open(filename, "w");
_startTime = millis();
}
}
if(_authenticated && request->_tempFile){
if(len){
request->_tempFile.write(data,len);
}
if(final){
request->_tempFile.close();
}
}
}

View File

@@ -0,0 +1,24 @@
#ifndef SPIFFSEditor_H_
#define SPIFFSEditor_H_
#include <ESPAsyncWebServer.h>
class SPIFFSEditor: public AsyncWebHandler {
private:
fs::FS _fs;
String _username;
String _password;
bool _authenticated;
uint32_t _startTime;
public:
#ifdef ESP32
SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String());
#else
SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS);
#endif
virtual bool canHandle(AsyncWebServerRequest *request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final;
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final;
virtual bool isRequestHandlerTrivial() override final {return false;}
};
#endif

View File

@@ -0,0 +1,193 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef STRINGARRAY_H_
#define STRINGARRAY_H_
#include "stddef.h"
#include "WString.h"
template <typename T>
class LinkedListNode {
T _value;
public:
LinkedListNode<T>* next;
LinkedListNode(const T val): _value(val), next(nullptr) {}
~LinkedListNode(){}
const T& value() const { return _value; };
T& value(){ return _value; }
};
template <typename T, template<typename> class Item = LinkedListNode>
class LinkedList {
public:
typedef Item<T> ItemType;
typedef std::function<void(const T&)> OnRemove;
typedef std::function<bool(const T&)> Predicate;
private:
ItemType* _root;
OnRemove _onRemove;
class Iterator {
ItemType* _node;
public:
Iterator(ItemType* current = nullptr) : _node(current) {}
Iterator(const Iterator& i) : _node(i._node) {}
Iterator& operator ++() { _node = _node->next; return *this; }
bool operator != (const Iterator& i) const { return _node != i._node; }
const T& operator * () const { return _node->value(); }
const T* operator -> () const { return &_node->value(); }
};
public:
typedef const Iterator ConstIterator;
ConstIterator begin() const { return ConstIterator(_root); }
ConstIterator end() const { return ConstIterator(nullptr); }
LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
~LinkedList(){}
void add(const T& t){
auto it = new ItemType(t);
if(!_root){
_root = it;
} else {
auto i = _root;
while(i->next) i = i->next;
i->next = it;
}
}
T& front() const {
return _root->value();
}
bool isEmpty() const {
return _root == nullptr;
}
size_t length() const {
size_t i = 0;
auto it = _root;
while(it){
i++;
it = it->next;
}
return i;
}
size_t count_if(Predicate predicate) const {
size_t i = 0;
auto it = _root;
while(it){
if (!predicate){
i++;
}
else if (predicate(it->value())) {
i++;
}
it = it->next;
}
return i;
}
const T* nth(size_t N) const {
size_t i = 0;
auto it = _root;
while(it){
if(i++ == N)
return &(it->value());
it = it->next;
}
return nullptr;
}
bool remove(const T& t){
auto it = _root;
auto pit = _root;
while(it){
if(it->value() == t){
if(it == _root){
_root = _root->next;
} else {
pit->next = it->next;
}
if (_onRemove) {
_onRemove(it->value());
}
delete it;
return true;
}
pit = it;
it = it->next;
}
return false;
}
bool remove_first(Predicate predicate){
auto it = _root;
auto pit = _root;
while(it){
if(predicate(it->value())){
if(it == _root){
_root = _root->next;
} else {
pit->next = it->next;
}
if (_onRemove) {
_onRemove(it->value());
}
delete it;
return true;
}
pit = it;
it = it->next;
}
return false;
}
void free(){
while(_root != nullptr){
auto it = _root;
_root = _root->next;
if (_onRemove) {
_onRemove(it->value());
}
delete it;
}
_root = nullptr;
}
};
class StringArray : public LinkedList<String> {
public:
StringArray() : LinkedList(nullptr) {}
bool containsIgnoreCase(const String& str){
for (const auto& s : *this) {
if (str.equalsIgnoreCase(s)) {
return true;
}
}
return false;
}
};
#endif /* STRINGARRAY_H_ */

View File

@@ -0,0 +1,238 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "WebAuthentication.h"
#include <libb64/cencode.h>
#ifdef ESP32
#include "mbedtls/md5.h"
#else
#include "md5.h"
#endif
// Basic Auth hash = base64("username:password")
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){
if(username == NULL || password == NULL || hash == NULL)
return false;
size_t toencodeLen = strlen(username)+strlen(password)+1;
size_t encodedLen = base64_encode_expected_len(toencodeLen);
if(strlen(hash) != encodedLen)
return false;
char *toencode = new char[toencodeLen+1];
if(toencode == NULL){
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
if(encoded == NULL){
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
return false;
}
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
#ifdef ESP32
mbedtls_md5_context _ctx;
#else
md5_context_t _ctx;
#endif
uint8_t i;
uint8_t * _buf = (uint8_t*)malloc(16);
if(_buf == NULL)
return false;
memset(_buf, 0x00, 16);
#ifdef ESP32
mbedtls_md5_init(&_ctx);
mbedtls_md5_update_ret (&_ctx,data,len);
mbedtls_md5_finish_ret(&_ctx,data);
mbedtls_internal_md5_process( &_ctx ,data);
// mbedtls_md5_starts(&_ctx);
// mbedtls_md5_update(&_ctx, data, len);
// mbedtls_md5_finish(&_ctx, _buf);
#else
MD5Init(&_ctx);
MD5Update(&_ctx, data, len);
MD5Final(_buf, &_ctx);
#endif
for(i = 0; i < 16; i++) {
sprintf(output + (i * 2), "%02x", _buf[i]);
}
free(_buf);
return true;
}
static String genRandomMD5(){
#ifdef ESP8266
uint32_t r = RANDOM_REG32;
#else
uint32_t r = rand();
#endif
char * out = (char*)malloc(33);
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
return "";
String res = String(out);
free(out);
return res;
}
static String stringMD5(const String& in){
char * out = (char*)malloc(33);
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
return "";
String res = String(out);
free(out);
return res;
}
String generateDigestHash(const char * username, const char * password, const char * realm){
if(username == NULL || password == NULL || realm == NULL){
return "";
}
char * out = (char*)malloc(33);
String res = String(username);
res.concat(":");
res.concat(realm);
res.concat(":");
String in = res;
in.concat(password);
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
return "";
res.concat(out);
free(out);
return res;
}
String requestDigestAuthentication(const char * realm){
String header = "realm=\"";
if(realm == NULL)
header.concat("asyncesp");
else
header.concat(realm);
header.concat( "\", qop=\"auth\", nonce=\"");
header.concat(genRandomMD5());
header.concat("\", opaque=\"");
header.concat(genRandomMD5());
header.concat("\"");
return header;
}
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
if(username == NULL || password == NULL || header == NULL || method == NULL){
//os_printf("AUTH FAIL: missing requred fields\n");
return false;
}
String myHeader = String(header);
int nextBreak = myHeader.indexOf(",");
if(nextBreak < 0){
//os_printf("AUTH FAIL: no variables\n");
return false;
}
String myUsername = String();
String myRealm = String();
String myNonce = String();
String myUri = String();
String myResponse = String();
String myQop = String();
String myNc = String();
String myCnonce = String();
myHeader += ", ";
do {
String avLine = myHeader.substring(0, nextBreak);
avLine.trim();
myHeader = myHeader.substring(nextBreak+1);
nextBreak = myHeader.indexOf(",");
int eqSign = avLine.indexOf("=");
if(eqSign < 0){
//os_printf("AUTH FAIL: no = sign\n");
return false;
}
String varName = avLine.substring(0, eqSign);
avLine = avLine.substring(eqSign + 1);
if(avLine.startsWith("\"")){
avLine = avLine.substring(1, avLine.length() - 1);
}
if(varName.equals("username")){
if(!avLine.equals(username)){
//os_printf("AUTH FAIL: username\n");
return false;
}
myUsername = avLine;
} else if(varName.equals("realm")){
if(realm != NULL && !avLine.equals(realm)){
//os_printf("AUTH FAIL: realm\n");
return false;
}
myRealm = avLine;
} else if(varName.equals("nonce")){
if(nonce != NULL && !avLine.equals(nonce)){
//os_printf("AUTH FAIL: nonce\n");
return false;
}
myNonce = avLine;
} else if(varName.equals("opaque")){
if(opaque != NULL && !avLine.equals(opaque)){
//os_printf("AUTH FAIL: opaque\n");
return false;
}
} else if(varName.equals("uri")){
if(uri != NULL && !avLine.equals(uri)){
//os_printf("AUTH FAIL: uri\n");
return false;
}
myUri = avLine;
} else if(varName.equals("response")){
myResponse = avLine;
} else if(varName.equals("qop")){
myQop = avLine;
} else if(varName.equals("nc")){
myNc = avLine;
} else if(varName.equals("cnonce")){
myCnonce = avLine;
}
} while(nextBreak > 0);
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(password));
String ha2 = String(method) + ":" + myUri;
String response = ha1 + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2);
if(myResponse.equals(stringMD5(response))){
//os_printf("AUTH SUCCESS\n");
return true;
}
//os_printf("AUTH FAIL: password\n");
return false;
}

View File

@@ -0,0 +1,34 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_
#include "Arduino.h"
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
String requestDigestAuthentication(const char * realm);
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
//for storing hashed versions on the device that can be authenticated against
String generateDigestHash(const char * username, const char * password, const char * realm);
#endif

View File

@@ -0,0 +1,133 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
#define ASYNCWEBSERVERHANDLERIMPL_H_
#include <string>
#include <regex>
#include "stddef.h"
#include <time.h>
class AsyncStaticWebHandler: public AsyncWebHandler {
using File = fs::File;
using FS = fs::FS;
private:
bool _getFile(AsyncWebServerRequest *request);
bool _fileExists(AsyncWebServerRequest *request, const String& path);
uint8_t _countBits(const uint8_t value) const;
protected:
FS _fs;
String _uri;
String _path;
String _default_file;
String _cache_control;
String _last_modified;
AwsTemplateProcessor _callback;
bool _isDir;
bool _gzipFirst;
uint8_t _gzipStats;
public:
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
virtual bool canHandle(AsyncWebServerRequest *request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final;
AsyncStaticWebHandler& setIsDir(bool isDir);
AsyncStaticWebHandler& setDefaultFile(const char* filename);
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
AsyncStaticWebHandler& setLastModified(const char* last_modified);
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
#ifdef ESP8266
AsyncStaticWebHandler& setLastModified(time_t last_modified);
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
#endif
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
};
class AsyncCallbackWebHandler: public AsyncWebHandler {
private:
protected:
String _uri;
WebRequestMethodComposite _method;
ArRequestHandlerFunction _onRequest;
ArUploadHandlerFunction _onUpload;
ArBodyHandlerFunction _onBody;
bool _isRegex;
public:
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false){}
void setUri(const String& uri){
_uri = uri;
_isRegex = uri.startsWith("^") && uri.endsWith("$");
}
void setMethod(WebRequestMethodComposite method){ _method = method; }
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; }
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; }
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; }
virtual bool canHandle(AsyncWebServerRequest *request) override final{
if(!_onRequest)
return false;
if(!(_method & request->method()))
return false;
if (_isRegex) {
std::regex rgx(_uri.c_str());
std::smatch matches;
std::string s(request->url().c_str());
if(std::regex_search(s, matches, rgx)) {
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
request->_addPathParam(matches[i].str().c_str());
}
} else {
return false;
}
} else if (_uri.length() && _uri.endsWith("*")) {
String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
if (!request->url().startsWith(uriTemplate))
return false;
}
else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
return false;
request->addInterestingHeader("ANY");
return true;
}
virtual void handleRequest(AsyncWebServerRequest *request) override final {
if(_onRequest)
_onRequest(request);
else
request->send(500);
}
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
if(_onUpload)
_onUpload(request, filename, index, data, len, final);
}
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
if(_onBody)
_onBody(request, data, len, index, total);
}
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
};
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */

View File

@@ -0,0 +1,220 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
: _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr)
{
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
// If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
_isDir = _path[_path.length()-1] == '/';
// Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/"
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
// Reset stats
_gzipFirst = false;
_gzipStats = 0xF8;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
_isDir = isDir;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
_default_file = String(filename);
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
_cache_control = String(cache_control);
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
_last_modified = String(last_modified);
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
char result[30];
strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified);
return setLastModified((const char *)result);
}
#ifdef ESP8266
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
return setLastModified((struct tm *)gmtime(&last_modified));
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
time_t last_modified;
if(time(&last_modified) == 0) //time is not yet set
return *this;
return setLastModified(last_modified);
}
#endif
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
if(request->method() != HTTP_GET
|| !request->url().startsWith(_uri)
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
){
return false;
}
if (_getFile(request)) {
// We interested in "If-Modified-Since" header to check if file was modified
if (_last_modified.length())
request->addInterestingHeader("If-Modified-Since");
if(_cache_control.length())
request->addInterestingHeader("If-None-Match");
DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n");
return true;
}
return false;
}
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
{
// Remove the found uri
String path = request->url().substring(_uri.length());
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
path = _path + path;
// Do we have a file or .gz file
if (!canSkipFileCheck && _fileExists(request, path))
return true;
// Can't handle if not default file
if (_default_file.length() == 0)
return false;
// Try to add default file, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length()-1] != '/')
path += "/";
path += _default_file;
return _fileExists(request, path);
}
#ifdef ESP32
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
#else
#define FILE_IS_REAL(f) (f == true)
#endif
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path)
{
bool fileFound = false;
bool gzipFound = false;
String gzip = path + ".gz";
if (_gzipFirst) {
request->_tempFile = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(request->_tempFile);
if (!gzipFound){
request->_tempFile = _fs.open(path, "r");
fileFound = FILE_IS_REAL(request->_tempFile);
}
} else {
request->_tempFile = _fs.open(path, "r");
fileFound = FILE_IS_REAL(request->_tempFile);
if (!fileFound){
request->_tempFile = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(request->_tempFile);
}
}
bool found = fileFound || gzipFound;
if (found) {
// Extract the file name from the path and keep it in _tempObject
size_t pathLen = path.length();
char * _tempPath = (char*)malloc(pathLen+1);
snprintf(_tempPath, pathLen+1, "%s", path.c_str());
request->_tempObject = (void*)_tempPath;
// Calculate gzip statistic
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
}
return found;
}
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const
{
uint8_t w = value;
uint8_t n;
for (n=0; w!=0; n++) w&=w-1;
return n;
}
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
{
// Get the filename from request->_tempObject and free it
String filename = String((char*)request->_tempObject);
free(request->_tempObject);
request->_tempObject = NULL;
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication();
if (request->_tempFile == true) {
String etag = String(request->_tempFile.size());
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) {
request->_tempFile.close();
request->send(304); // Not modified
} else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) {
request->_tempFile.close();
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
response->addHeader("Cache-Control", _cache_control);
response->addHeader("ETag", etag);
request->send(response);
} else {
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
if (_last_modified.length())
response->addHeader("Last-Modified", _last_modified);
if (_cache_control.length()){
response->addHeader("Cache-Control", _cache_control);
response->addHeader("ETag", etag);
}
request->send(response);
}
} else {
request->send(404);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
#define ASYNCWEBSERVERRESPONSEIMPL_H_
#ifdef Arduino_h
// arduino is not compatible with std::vector
#undef min
#undef max
#endif
#include <vector>
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
class AsyncBasicResponse: public AsyncWebServerResponse {
private:
String _content;
public:
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String());
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
};
class AsyncAbstractResponse: public AsyncWebServerResponse {
private:
String _head;
// Data is inserted into cache at begin().
// This is inefficient with vector, but if we use some other container,
// we won't be able to access it as contiguous array of bytes when reading from it,
// so by gaining performance in one place, we'll lose it in another.
std::vector<uint8_t> _cache;
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
protected:
AwsTemplateProcessor _callback;
public:
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return false; }
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
};
#ifndef TEMPLATE_PLACEHOLDER
#define TEMPLATE_PLACEHOLDER '%'
#endif
#define TEMPLATE_PARAM_NAME_LENGTH 32
class AsyncFileResponse: public AsyncAbstractResponse {
using File = fs::File;
using FS = fs::FS;
private:
File _content;
String _path;
void _setContentType(const String& path);
public:
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
~AsyncFileResponse();
bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class AsyncStreamResponse: public AsyncAbstractResponse {
private:
Stream *_content;
public:
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class AsyncCallbackResponse: public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
size_t _filledLength;
public:
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class AsyncChunkedResponse: public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
size_t _filledLength;
public:
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class AsyncProgmemResponse: public AsyncAbstractResponse {
private:
const uint8_t * _content;
size_t _readLength;
public:
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
bool _sourceValid() const { return true; }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class cbuf;
class AsyncResponseStream: public AsyncAbstractResponse, public Print {
private:
cbuf *_content;
public:
AsyncResponseStream(const String& contentType, size_t bufferSize);
~AsyncResponseStream();
bool _sourceValid() const { return (_state < RESPONSE_END); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
using Print::write;
};
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */

View File

@@ -0,0 +1,697 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebResponseImpl.h"
#include "cbuf.h"
// Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count)
{
unsigned char* p = static_cast<unsigned char*>(ptr);
while(count--)
if(*p++ == static_cast<unsigned char>(ch))
return --p;
return nullptr;
}
/*
* Abstract Response
* */
const char* AsyncWebServerResponse::_responseCodeToString(int code) {
switch (code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Time-out";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Large";
case 415: return "Unsupported Media Type";
case 416: return "Requested range not satisfiable";
case 417: return "Expectation Failed";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Time-out";
case 505: return "HTTP Version not supported";
default: return "";
}
}
AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0)
, _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; }))
, _contentType()
, _contentLength(0)
, _sendContentLength(true)
, _chunked(false)
, _headLength(0)
, _sentLength(0)
, _ackedLength(0)
, _writtenLength(0)
, _state(RESPONSE_SETUP)
{
for(auto header: DefaultHeaders::Instance()) {
_headers.add(new AsyncWebHeader(header->name(), header->value()));
}
}
AsyncWebServerResponse::~AsyncWebServerResponse(){
_headers.free();
}
void AsyncWebServerResponse::setCode(int code){
if(_state == RESPONSE_SETUP)
_code = code;
}
void AsyncWebServerResponse::setContentLength(size_t len){
if(_state == RESPONSE_SETUP)
_contentLength = len;
}
void AsyncWebServerResponse::setContentType(const String& type){
if(_state == RESPONSE_SETUP)
_contentType = type;
}
void AsyncWebServerResponse::addHeader(const String& name, const String& value){
_headers.add(new AsyncWebHeader(name, value));
}
String AsyncWebServerResponse::_assembleHead(uint8_t version){
if(version){
addHeader("Accept-Ranges","none");
if(_chunked)
addHeader("Transfer-Encoding","chunked");
}
String out = String();
int bufSize = 300;
char buf[bufSize];
snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code));
out.concat(buf);
if(_sendContentLength) {
snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength);
out.concat(buf);
}
if(_contentType.length()) {
snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str());
out.concat(buf);
}
for(const auto& header: _headers){
snprintf(buf, bufSize, "%s: %s\r\n", header->name().c_str(), header->value().c_str());
out.concat(buf);
}
_headers.free();
out.concat("\r\n");
_headLength = out.length();
return out;
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; }
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); }
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ return 0; }
/*
* String/Code Response
* */
AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){
_code = code;
_content = content;
_contentType = contentType;
if(_content.length()){
_contentLength = _content.length();
if(!_contentType.length())
_contentType = "text/plain";
}
addHeader("Connection","close");
}
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
_state = RESPONSE_HEADERS;
String out = _assembleHead(request->version());
size_t outLen = out.length();
size_t space = request->client()->space();
if(!_contentLength && space >= outLen){
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK;
} else if(_contentLength && space >= outLen + _contentLength){
out += _content;
outLen += _contentLength;
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK;
} else if(space && space < outLen){
String partial = out.substring(0, space);
_content = out.substring(space) + _content;
_contentLength += outLen - space;
_writtenLength += request->client()->write(partial.c_str(), partial.length());
_state = RESPONSE_CONTENT;
} else if(space > outLen && space < (outLen + _contentLength)){
size_t shift = space - outLen;
outLen += shift;
_sentLength += shift;
out += _content.substring(0, shift);
_content = _content.substring(shift);
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_CONTENT;
} else {
_content = out + _content;
_contentLength += outLen;
_state = RESPONSE_CONTENT;
}
}
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
_ackedLength += len;
if(_state == RESPONSE_CONTENT){
size_t available = _contentLength - _sentLength;
size_t space = request->client()->space();
//we can fit in this packet
if(space > available){
_writtenLength += request->client()->write(_content.c_str(), available);
_content = String();
_state = RESPONSE_WAIT_ACK;
return available;
}
//send some data, the rest on ack
String out = _content.substring(0, space);
_content = _content.substring(space);
_sentLength += space;
_writtenLength += request->client()->write(out.c_str(), space);
return space;
} else if(_state == RESPONSE_WAIT_ACK){
if(_ackedLength >= _writtenLength){
_state = RESPONSE_END;
}
}
return 0;
}
/*
* Abstract Response
* */
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
{
// In case of template processing, we're unable to determine real response size
if(callback) {
_contentLength = 0;
_sendContentLength = false;
_chunked = true;
}
}
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
addHeader("Connection","close");
_head = _assembleHead(request->version());
_state = RESPONSE_HEADERS;
_ack(request, 0, 0);
}
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
if(!_sourceValid()){
_state = RESPONSE_FAILED;
request->client()->close();
return 0;
}
_ackedLength += len;
size_t space = request->client()->space();
size_t headLen = _head.length();
if(_state == RESPONSE_HEADERS){
if(space >= headLen){
_state = RESPONSE_CONTENT;
space -= headLen;
} else {
String out = _head.substring(0, space);
_head = _head.substring(space);
_writtenLength += request->client()->write(out.c_str(), out.length());
return out.length();
}
}
if(_state == RESPONSE_CONTENT){
size_t outLen;
if(_chunked){
if(space <= 8){
return 0;
}
outLen = space;
} else if(!_sendContentLength){
outLen = space;
} else {
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength);
}
uint8_t *buf = (uint8_t *)malloc(outLen+headLen);
if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen);
return 0;
}
if(headLen){
memcpy(buf, _head.c_str(), _head.length());
}
size_t readLen = 0;
if(_chunked){
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
// See RFC2616 sections 2, 3.6.1.
readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8);
if(readLen == RESPONSE_TRY_AGAIN){
free(buf);
return 0;
}
outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen;
while(outLen < headLen + 4) buf[outLen++] = ' ';
buf[outLen++] = '\r';
buf[outLen++] = '\n';
outLen += readLen;
buf[outLen++] = '\r';
buf[outLen++] = '\n';
} else {
readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen);
if(readLen == RESPONSE_TRY_AGAIN){
free(buf);
return 0;
}
outLen = readLen + headLen;
}
if(headLen){
_head = String();
}
if(outLen){
_writtenLength += request->client()->write((const char*)buf, outLen);
}
if(_chunked){
_sentLength += readLen;
} else {
_sentLength += outLen - headLen;
}
free(buf);
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
_state = RESPONSE_WAIT_ACK;
}
return outLen;
} else if(_state == RESPONSE_WAIT_ACK){
if(!_sendContentLength || _ackedLength >= _writtenLength){
_state = RESPONSE_END;
if(!_chunked && !_sendContentLength)
request->client()->close(true);
}
}
return 0;
}
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
{
// If we have something in cache, copy it to buffer
const size_t readFromCache = std::min(len, _cache.size());
if(readFromCache) {
memcpy(data, _cache.data(), readFromCache);
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
}
// If we need to read more...
const size_t needFromFile = len - readFromCache;
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
return readFromCache + readFromContent;
}
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
{
if(!_callback)
return _fillBuffer(data, len);
const size_t originalLen = len;
len = _readDataFromCacheOrContent(data, len);
// Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders
uint8_t* pTemplateStart = data;
while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
// temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName;
// If closing placeholder is found:
if(pTemplateEnd) {
// prepare argument to callback
const size_t paramNameLength = std::min(sizeof(buf) - 1, (unsigned int)(pTemplateEnd - pTemplateStart - 1));
if(paramNameLength) {
memcpy(buf, pTemplateStart + 1, paramNameLength);
buf[paramNameLength] = 0;
paramName = String(reinterpret_cast<char*>(buf));
} else { // double percent sign encountered, this is single percent sign escaped.
// remove the 2nd percent sign
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++pTemplateStart;
}
} else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
if(readFromCacheOrContent) {
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if(pTemplateEnd) {
// prepare argument to callback
*pTemplateEnd = 0;
paramName = String(reinterpret_cast<char*>(buf));
// Copy remaining read-ahead data into cache
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
pTemplateEnd = &data[len - 1];
}
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
{
// but first, store read file data in cache
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart;
}
}
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
}
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
if(paramName.length()) {
// call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
// Data after pTemplateEnd may need to be moved.
// The first byte of data after placeholder is located at pTemplateEnd + 1.
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
const String paramValue(_callback(paramName));
const char* pvstr = paramValue.c_str();
const unsigned int pvlen = paramValue.length();
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
// make room for param value
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
} else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
// Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
// 3. replace placeholder with actual value
memcpy(pTemplateStart, pvstr, numBytesCopied);
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
if(numBytesCopied < pvlen) {
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
} else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
// there is some free room, fill it from cache
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
const size_t totalFreeRoom = originalLen - len + roomFreed;
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
} else { // result is copied fully; it is longer than placeholder text
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
len = std::min(len + roomTaken, originalLen);
}
}
} // while(pTemplateStart)
return len;
}
/*
* File Response
* */
AsyncFileResponse::~AsyncFileResponse(){
if(_content)
_content.close();
}
void AsyncFileResponse::_setContentType(const String& path){
if (path.endsWith(".html")) _contentType = "text/html";
else if (path.endsWith(".htm")) _contentType = "text/html";
else if (path.endsWith(".css")) _contentType = "text/css";
else if (path.endsWith(".json")) _contentType = "application/json";
else if (path.endsWith(".js")) _contentType = "application/javascript";
else if (path.endsWith(".png")) _contentType = "image/png";
else if (path.endsWith(".gif")) _contentType = "image/gif";
else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
else if (path.endsWith(".ico")) _contentType = "image/x-icon";
else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
else if (path.endsWith(".eot")) _contentType = "font/eot";
else if (path.endsWith(".woff")) _contentType = "font/woff";
else if (path.endsWith(".woff2")) _contentType = "font/woff2";
else if (path.endsWith(".ttf")) _contentType = "font/ttf";
else if (path.endsWith(".xml")) _contentType = "text/xml";
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
else if (path.endsWith(".zip")) _contentType = "application/zip";
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
else _contentType = "text/plain";
}
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
_code = 200;
_path = path;
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
_path = _path+".gz";
addHeader("Content-Encoding", "gzip");
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
}
_content = fs.open(_path, "r");
_contentLength = _content.size();
if(contentType == "")
_setContentType(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
// set filename and force download
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else {
// set filename and force rendering
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
_code = 200;
_path = path;
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
addHeader("Content-Encoding", "gzip");
_callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true;
_chunked = false;
}
_content = content;
_contentLength = _content.size();
if(contentType == "")
_setContentType(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else {
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
return _content.read(data, len);
}
/*
* Stream Response
* */
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
_code = 200;
_content = &stream;
_contentLength = len;
_contentType = contentType;
}
size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
size_t available = _content->available();
size_t outLen = (available > len)?len:available;
size_t i;
for(i=0;i<outLen;i++)
data[i] = _content->read();
return outLen;
}
/*
* Callback Response
* */
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
_code = 200;
_content = callback;
_contentLength = len;
if(!len)
_sendContentLength = false;
_contentType = contentType;
_filledLength = 0;
}
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
size_t ret = _content(data, len, _filledLength);
if(ret != RESPONSE_TRY_AGAIN){
_filledLength += ret;
}
return ret;
}
/*
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
_code = 200;
_content = callback;
_contentLength = 0;
_contentType = contentType;
_sendContentLength = false;
_chunked = true;
_filledLength = 0;
}
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
size_t ret = _content(data, len, _filledLength);
if(ret != RESPONSE_TRY_AGAIN){
_filledLength += ret;
}
return ret;
}
/*
* Progmem Response
* */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
_code = code;
_content = content;
_contentType = contentType;
_contentLength = len;
_readLength = 0;
}
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
size_t left = _contentLength - _readLength;
if (left > len) {
memcpy_P(data, _content + _readLength, len);
_readLength += len;
return len;
}
memcpy_P(data, _content + _readLength, left);
_readLength += left;
return left;
}
/*
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
* */
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){
_code = 200;
_contentLength = 0;
_contentType = contentType;
_content = new cbuf(bufferSize);
}
AsyncResponseStream::~AsyncResponseStream(){
delete _content;
}
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){
return _content->read((char*)buf, maxLen);
}
size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
if(_started())
return 0;
if(len > _content->room()){
size_t needed = len - _content->room();
_content->resizeAdd(needed);
}
size_t written = _content->write((const char*)data, len);
_contentLength += written;
return written;
}
size_t AsyncResponseStream::write(uint8_t data){
return write(&data, 1);
}

View File

@@ -0,0 +1,193 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
return WiFi.localIP() == request->client()->localIP();
}
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
return WiFi.localIP() != request->client()->localIP();
}
AsyncWebServer::AsyncWebServer(uint16_t port)
: _server(port)
, _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
, _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
{
_catchAllHandler = new AsyncCallbackWebHandler();
if(_catchAllHandler == NULL)
return;
_server.onClient([](void *s, AsyncClient* c){
if(c == NULL)
return;
c->setRxTimeout(3);
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
if(r == NULL){
c->close(true);
c->free();
delete c;
}
}, this);
}
AsyncWebServer::~AsyncWebServer(){
reset();
end();
if(_catchAllHandler) delete _catchAllHandler;
}
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
_rewrites.add(rewrite);
return *rewrite;
}
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
return _rewrites.remove(rewrite);
}
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
return addRewrite(new AsyncWebRewrite(from, to));
}
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
_handlers.add(handler);
return *handler;
}
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
return _handlers.remove(handler);
}
void AsyncWebServer::begin(){
_server.setNoDelay(true);
_server.begin();
}
void AsyncWebServer::end(){
_server.end();
}
#if ASYNC_TCP_SSL_ENABLED
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){
_server.onSslFileRequest(cb, arg);
}
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){
_server.beginSecure(cert, key, password);
}
#endif
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){
delete request;
}
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){
for(const auto& r: _rewrites){
if (r->match(request)){
request->_url = r->toUrl();
request->_addGetParams(r->params());
}
}
}
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
for(const auto& h: _handlers){
if (h->filter(request) && h->canHandle(request)){
request->setHandler(h);
return;
}
}
request->addInterestingHeader("ANY");
request->setHandler(_catchAllHandler);
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
handler->onRequest(onRequest);
handler->onUpload(onUpload);
handler->onBody(onBody);
addHandler(handler);
return *handler;
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
handler->onRequest(onRequest);
handler->onUpload(onUpload);
addHandler(handler);
return *handler;
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
handler->onRequest(onRequest);
addHandler(handler);
return *handler;
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->onRequest(onRequest);
addHandler(handler);
return *handler;
}
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
addHandler(handler);
return *handler;
}
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){
_catchAllHandler->onRequest(fn);
}
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){
_catchAllHandler->onUpload(fn);
}
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){
_catchAllHandler->onBody(fn);
}
void AsyncWebServer::reset(){
_rewrites.free();
_handlers.free();
if (_catchAllHandler != NULL){
_catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL);
}
}

View File

@@ -0,0 +1,627 @@
<!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>ESP Editor</title>
<style type="text/css" media="screen">
.cm {
z-index: 300;
position: absolute;
left: 5px;
border: 1px solid #444;
background-color: #F5F5F5;
display: none;
box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );
font-size: 12px;
font-family: sans-serif;
font-weight:bold;
}
.cm ul {
list-style: none;
top: 0;
left: 0;
margin: 0;
padding: 0;
}
.cm li {
position: relative;
min-width: 60px;
cursor: pointer;
}
.cm span {
color: #444;
display: inline-block;
padding: 6px;
}
.cm li:hover { background: #444; }
.cm li:hover span { color: #EEE; }
.tvu ul, .tvu li {
padding: 0;
margin: 0;
list-style: none;
}
.tvu input {
position: absolute;
opacity: 0;
}
.tvu {
font: normal 12px Verdana, Arial, Sans-serif;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
color: #444;
line-height: 16px;
}
.tvu span {
margin-bottom:5px;
padding: 0 0 0 18px;
cursor: pointer;
display: inline-block;
height: 16px;
vertical-align: middle;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC') no-repeat;
background-position: 0px 0px;
}
.tvu span:hover {
text-decoration: underline;
}
@media screen and (-webkit-min-device-pixel-ratio:0){
.tvu{
-webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
}
@-webkit-keyframes webkit-adjacent-element-selector-bugfix {
from {
padding: 0;
}
to {
padding: 0;
}
}
}
#uploader {
position: absolute;
top: 0;
right: 0;
left: 0;
height:28px;
line-height: 24px;
padding-left: 10px;
background-color: #444;
color:#EEE;
}
#tree {
position: absolute;
top: 28px;
bottom: 0;
left: 0;
width:160px;
padding: 8px;
}
#editor, #preview {
position: absolute;
top: 28px;
right: 0;
bottom: 0;
left: 160px;
border-left:1px solid #EEE;
}
#preview {
background-color: #EEE;
padding:5px;
}
#loader {
position: absolute;
top: 36%;
right: 40%;
}
.loader {
z-index: 10000;
border: 8px solid #b5b5b5; /* Grey */
border-top: 8px solid #3498db; /* Blue */
border-bottom: 8px solid #3498db; /* Blue */
border-radius: 50%;
width: 240px;
height: 240px;
animation: spin 2s linear infinite;
display:none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<script>
if (typeof XMLHttpRequest === "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}
function ge(a){
return document.getElementById(a);
}
function ce(a){
return document.createElement(a);
}
function sortByKey(array, key) {
return array.sort(function(a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
var QueuedRequester = function () {
this.queue = [];
this.running = false;
this.xmlhttp = null;
}
QueuedRequester.prototype = {
_request: function(req){
this.running = true;
if(!req instanceof Object) return;
var that = this;
function ajaxCb(x,d){ return function(){
if (x.readyState == 4){
ge("loader").style.display = "none";
d.callback(x.status, x.responseText);
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}
}}
ge("loader").style.display = "block";
var p = "";
if(req.params instanceof FormData){
p = req.params;
} else if(req.params instanceof Object){
for (var key in req.params) {
if(p === "")
p += (req.method === "GET")?"?":"";
else
p += "&";
p += encodeURIComponent(key)+"="+encodeURIComponent(req.params[key]);
};
}
this.xmlhttp = new XMLHttpRequest();
this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
if(req.method === "GET"){
this.xmlhttp.open(req.method, req.url+p, true);
this.xmlhttp.send();
} else {
this.xmlhttp.open(req.method, req.url, true);
if(p instanceof String)
this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.xmlhttp.send(p);
}
},
stop: function(){
if(this.running) this.running = false;
if(this.xmlhttp && this.xmlhttp.readyState < 4){
this.xmlhttp.abort();
}
},
add: function(method, url, params, callback){
this.queue.push({url:url,method:method,params:params,callback:callback});
if(!this.running){
this._request(this.queue.shift());
}
}
}
var requests = new QueuedRequester();
function createFileUploader(element, tree, editor){
var xmlHttp;
var refresh = ce("button");
refresh.innerHTML = 'Refresh List';
ge(element).appendChild(refresh);
var input = ce("input");
input.type = "file";
input.multiple = false;
input.name = "data";
input.id="upload-select";
ge(element).appendChild(input);
var path = ce("input");
path.id = "upload-path";
path.type = "text";
path.name = "path";
path.defaultValue = "/";
ge(element).appendChild(path);
var button = ce("button");
button.innerHTML = 'Upload';
ge(element).appendChild(button);
var mkfile = ce("button");
mkfile.innerHTML = 'Create';
ge(element).appendChild(mkfile);
var filename = ce("input");
filename.id = "editor-filename";
filename.type = "text";
filename.disabled= true;
filename.size = 20;
ge(element).appendChild(filename);
var savefile = ce("button");
savefile.innerHTML = ' Save ' ;
ge(element).appendChild(savefile);
function httpPostProcessRequest(status, responseText){
if(status != 200)
alert("ERROR["+status+"]: "+responseText);
else
tree.refreshPath(path.value);
}
function createPath(p){
var formData = new FormData();
formData.append("path", p);
requests.add("PUT", "/edit", formData, httpPostProcessRequest);
}
mkfile.onclick = function(e){
createPath(path.value);
editor.loadUrl(path.value);
path.value="/";
};
savefile.onclick = function(e){
editor.execCommand('saveCommand');
};
refresh.onclick = function(e){
tree.refreshPath(path.value);
};
button.onclick = function(e){
if(input.files.length === 0){
return;
}
var formData = new FormData();
formData.append("data", input.files[0], path.value);
requests.add("POST", "/edit", formData, httpPostProcessRequest);
var uploadPath= ge("upload-path");
uploadPath.value="/";
var uploadSelect= ge("upload-select");
uploadSelect.value="";
};
input.onchange = function(e){
if(input.files.length === 0) return;
var filename = input.files[0].name;
var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
var name = /(.*)\.[^.]+$/.exec(filename)[1];
if(typeof name !== undefined){
filename = name;
}
path.value = "/"+filename+"."+ext;
};
}
function createTree(element, editor){
var preview = ge("preview");
var treeRoot = ce("div");
treeRoot.className = "tvu";
ge(element).appendChild(treeRoot);
function loadDownload(path){
ge('download-frame').src = "/edit?download="+path;
}
function loadPreview(path){
var edfname = ge("editor-filename");
edfname.value=path;
ge("editor").style.display = "none";
preview.style.display = "block";
preview.innerHTML = '<img src="/edit?edit='+path+'&_cb='+Date.now()+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />';
}
function fillFileMenu(el, path){
var list = ce("ul");
el.appendChild(list);
var action = ce("li");
list.appendChild(action);
if(isImageFile(path)){
action.innerHTML = "<span>Preview</span>";
action.onclick = function(e){
loadPreview(path);
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
};
} else if(isTextFile(path)){
action.innerHTML = "<span>Edit</span>";
action.onclick = function(e){
editor.loadUrl(path);
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
};
}
var download = ce("li");
list.appendChild(download);
download.innerHTML = "<span>Download</span>";
download.onclick = function(e){
loadDownload(path);
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
};
var delFile = ce("li");
list.appendChild(delFile);
delFile.innerHTML = "<span>Delete</span>";
delFile.onclick = function(e){
httpDelete(path);
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
};
}
function showContextMenu(event, path, isfile){
var divContext = ce("div");
var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;
var left = event.clientX + scrollLeft;
var top = event.clientY + scrollTop;
divContext.className = 'cm';
divContext.style.display = 'block';
divContext.style.left = left + 'px';
divContext.style.top = top + 'px';
fillFileMenu(divContext, path);
document.body.appendChild(divContext);
var width = divContext.offsetWidth;
var height = divContext.offsetHeight;
divContext.onmouseout = function(e){
if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(divContext);
}
};
}
function createTreeLeaf(path, name, size){
var leaf = ce("li");
leaf.id = name;
var label = ce("span");
label.innerHTML = name;
leaf.appendChild(label);
leaf.onclick = function(e){
if(isTextFile(leaf.id.toLowerCase())){
editor.loadUrl(leaf.id);
} else if(isImageFile(leaf.id.toLowerCase())){
loadPreview(leaf.id);
}
};
leaf.oncontextmenu = function(e){
e.preventDefault();
e.stopPropagation();
showContextMenu(e, leaf.id, true);
};
return leaf;
}
function addList(parent, path, items){
sortByKey(items, 'name');
var list = ce("ul");
parent.appendChild(list);
var ll = items.length;
for(var i = 0; i < ll; i++){
if(items[i].type === "file")
list.appendChild(createTreeLeaf(path, items[i].name, items[i].size));
}
}
function isTextFile(path){
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
if(typeof ext !== undefined){
switch(ext){
case "txt":
case "htm":
case "html":
case "js":
case "css":
case "xml":
case "json":
case "conf":
case "ini":
case "h":
case "c":
case "cpp":
case "php":
case "hex":
case "ino":
case "pde":
return true;
}
}
return false;
}
function isImageFile(path){
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
if(typeof ext !== undefined){
switch(ext){
case "png":
case "jpg":
case "gif":
case "bmp":
return true;
}
}
return false;
}
this.refreshPath = function(path){
treeRoot.removeChild(treeRoot.childNodes[0]);
httpGet(treeRoot, "/");
};
function delCb(path){
return function(status, responseText){
if(status != 200){
alert("ERROR["+status+"]: "+responseText);
} else {
treeRoot.removeChild(treeRoot.childNodes[0]);
httpGet(treeRoot, "/");
}
}
}
function httpDelete(filename){
var formData = new FormData();
formData.append("path", filename);
requests.add("DELETE", "/edit", formData, delCb(filename));
}
function getCb(parent, path){
return function(status, responseText){
if(status == 200)
addList(parent, path, JSON.parse(responseText));
}
}
function httpGet(parent, path){
requests.add("GET", "/edit", { list: path }, getCb(parent, path));
}
httpGet(treeRoot, "/");
return this;
}
function createEditor(element, file, lang, theme, type){
function getLangFromFilename(filename){
var lang = "plain";
var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
if(typeof ext !== undefined){
switch(ext){
case "txt": lang = "plain"; break;
case "hex": lang = "plain"; break;
case "conf": lang = "plain"; break;
case "htm": lang = "html"; break;
case "js": lang = "javascript"; break;
case "h": lang = "c_cpp"; break;
case "c": lang = "c_cpp"; break;
case "cpp": lang = "c_cpp"; break;
case "css":
case "scss":
case "php":
case "html":
case "json":
case "xml":
case "ini": lang = ext;
}
}
return lang;
}
if(typeof file === "undefined") file = "/index.html";
if(typeof lang === "undefined"){
lang = getLangFromFilename(file);
}
if(typeof theme === "undefined") theme = "textmate";
if(typeof type === "undefined"){
type = "text/"+lang;
if(lang === "c_cpp") type = "text/plain";
}
var editor = ace.edit(element);
function httpPostProcessRequest(status, responseText){
if(status != 200) alert("ERROR["+status+"]: "+responseText);
}
function httpPost(filename, data, type){
var formData = new FormData();
formData.append("data", new Blob([data], { type: type }), filename);
requests.add("POST", "/edit", formData, httpPostProcessRequest);
}
function httpGetProcessRequest(status, responseText){
ge("preview").style.display = "none";
ge("editor").style.display = "block";
if(status == 200)
editor.setValue(responseText);
else
editor.setValue("");
editor.clearSelection();
}
function httpGet(theUrl){
requests.add("GET", "/edit", { edit: theUrl }, httpGetProcessRequest);
}
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
editor.setTheme("ace/theme/"+theme);
editor.$blockScrolling = Infinity;
editor.getSession().setUseSoftTabs(true);
editor.getSession().setTabSize(2);
editor.setHighlightActiveLine(true);
editor.setShowPrintMargin(false);
editor.commands.addCommand({
name: 'saveCommand',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
httpPost(file, editor.getValue()+"", type);
},
readOnly: false
});
editor.commands.addCommand({
name: 'undoCommand',
bindKey: {win: 'Ctrl-Z', mac: 'Command-Z'},
exec: function(editor) {
editor.getSession().getUndoManager().undo(false);
},
readOnly: false
});
editor.commands.addCommand({
name: 'redoCommand',
bindKey: {win: 'Ctrl-Shift-Z', mac: 'Command-Shift-Z'},
exec: function(editor) {
editor.getSession().getUndoManager().redo(false);
},
readOnly: false
});
editor.loadUrl = function(filename){
var edfname = ge("editor-filename");
edfname.value=filename;
file = filename;
lang = getLangFromFilename(file);
type = "text/"+lang;
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
httpGet(file);
};
return editor;
}
function onBodyLoad(){
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });
var editor = createEditor("editor", vars.file, vars.lang, vars.theme);
var tree = createTree("tree", editor);
createFileUploader("uploader", tree, editor);
if(typeof vars.file === "undefined") vars.file = "/index.htm";
editor.loadUrl(vars.file);
};
</script>
<script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
if (typeof ace.edit == "undefined") {
var script = document.createElement('script');
script.src = "/ace.js";
script.async = false;
document.head.appendChild(script);
}
</script>
</head>
<body onload="onBodyLoad();">
<div id="loader" class="loader"></div>
<div id="uploader"></div>
<div id="tree"></div>
<div id="editor"></div>
<div id="preview" style="display:none;"></div>
<iframe id=download-frame style='display:none;'></iframe>
</body>
</html>

View File

@@ -8,34 +8,49 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:d1_mini]
platform = espressif8266
board = d1_mini
board_build.filesystem = littlefs
board_build.f_flash = 80000000L
board_build.ldscript = eagle.flash.4m1m.ld
[platformio]
extra_configs =
wifi_credentials.ini
upload_protocol = espota
upload_port = ESP_OTA.local
upload_flags=
--auth=UploadTheFlag
[env:de-timer]
platform = espressif32
board = ttgo-lora32-v1
board_build.filesystem = littlefs
;board_build.f_flash = 80000000L
;board_build.ldscript = eagle.flash.4m1m.ld
monitor_filters = esp32_exception_decoder
monitor_speed = 115200
upload_protocol = esptool
upload_speed = 921600
;upload_port = 10.0.1.48
;upload_protocol = espota
;upload_flags =
; --auth=${wifi_cred.admin_password}
build_type = debug
build_flags=
-D SERIAL_DEBUG
-D DEVICE_NAME='"DE CTF Timer Prototype 1"'
-D WIFI_CLIENT
-D WIFI_SSID='"BND_Scanner_#42"'
-D WIFI_PASS='"5xMYkerbLMdrsSdF3hpy5DM9"'
;-D WIFI_AP
;-D WIFI_SSID='"Dark Emergency CTF Timer"'
;-D WIFI_PASS='"CaptureTheFlag"'
-D FACTION_1_NAME='"GOF"'
-D FACTION_2_NAME='"MILIZ"'
-D FACTION_3_NAME='"KGG"'
!python git_rev_macro.py
-DSERIAL_DEBUG
;-DWIFI_CLIENT
;-DCAPTIVE
-DWIFI_AP_IP_GW=10,0,1,1
-DADMIN_PASSWORD=${wifi_cred.admin_password}
-DWIFI_SSID=${wifi_cred.wifi_ssid}
-DWIFI_PASSWORD=${wifi_cred.wifi_password}
-DWIFI_AP_PASSWORD=${wifi_cred.wifi_ap_password}
-DDEVICE_NAME='"Dark Emergency Timer"'
-DFACTION_1_NAME='"GOF"'
-DFACTION_2_NAME='"MILIZ"'
-DFACTION_3_NAME='"KGG"'
framework = arduino
lib_deps =
smougenot/TM1637@0.0.0-alpha+sha.9486982048
me-no-dev/ESP Async WebServer @ ^1.2.3
;me-no-dev/ESP Async WebServer @ ^1.2.3 ; local version included due to bug
me-no-dev/AsyncTCP @ ^1.1.1
sstaub/Ticker @ ^4.2.0
adafruit/Adafruit INA219 @ ^1.1.1
robtillaart/I2C_EEPROM @ ^1.5.2

41
src/common.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#define Q(x) #x
#define QUOTE(x) Q(x)
// Module connection pins (ESP GPIO-Nums)
#define CLK 16
#define DIO_FAC_1_7SEG 14
#define DIO_FAC_2_7SEG 12
#define DIO_FAC_3_7SEG 13
#define DIO_FAC_1_TRG 0
#define FAC_1_TRG_PRESSED LOW
#define DIO_FAC_2_TRG 2
#define FAC_2_TRG_PRESSED LOW
#define DIO_FAC_3_TRG 15
#define FAC_3_TRG_PRESSED HIGH
#ifndef HOST_NAME
#define HOST_NAME "DE_Timer_%06X" // Use printf-Formatting - Chip-ID (uin32_t) will be added
#endif
#ifndef OTA_DELAY
#define OTA_DELAY 50 // ticks -> 10ms / tick
#endif
#ifndef ADMIN_PASSWORD
#error "You need to define ADMIN_PASSWORD for OTA-Update"
#endif
#ifndef WIFI_PASSWORD
#error "You must define an WIFI_PASSWORD for OTA-Update"
#endif
#ifndef WIFI_SSID
#error "You must define an WIFI_SSID for OTA-Update"
#endif
#ifndef WIFI_AP_PASSWORD
#error "You must define an WIFI_AP_PASSWORD for Standalone AP-Mode"
#endif
#endif

261
src/config.cpp Normal file
View File

@@ -0,0 +1,261 @@
#include "config.h"
I2C_eeprom ee(0x50, EEPROM_SIZE_BYTES);
configData_t ConfigData;
persistenceData_t PersistenceData;
const uint16_t eeVersion = 1; // inc
boolean eeAvailable = false;
const uint16_t persistencemarker_Adress = 0; // sizeof 4
const uint16_t startofConfig_Adress = 16;
const uint16_t startofPersistence_Adress = startofConfig_Adress + sizeof(ConfigData) + (sizeof(ConfigData) % 16);
boolean checkEEPROMavailable();
void InitEEPROM()
{
ee.begin();
if (!checkEEPROMavailable())
{
globals.systemStatus = sysStat_Error;
MaintainDTC(DTC_NO_EEPROM_FOUND, true);
return;
}
GetConfig_EEPROM();
if (ConfigData.EEPROM_Version != eeVersion)
{
FormatConfig_EEPROM();
globals.systemStatus = sysStat_Error;
MaintainDTC(DTC_EEPROM_VERSION_BAD, true);
return;
}
if (getPersistanceAddress() > ee.getDeviceSize())
{
FormatPersistence_EEPROM();
globals.systemStatus = sysStat_Error;
MaintainDTC(DTC_EEPROM_PDS_MARKER_INVALID, true);
return;
}
GetPersistence_EEPROM();
}
void EEPROM_Process()
{
switch (globals.requestEEAction)
{
case EE_CFG_SAVE:
StoreConfig_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_CFG_LOAD:
GetConfig_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_PDS_SAVE:
StorePersistence_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_PDS_LOAD:
GetPersistence_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_IDLE:
default:
globals.requestEEAction = EE_IDLE;
}
}
void StoreConfig_EEPROM()
{
ConfigData.checksum = 0;
ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData));
if (!checkEEPROMavailable())
return;
ee.updateBlock(startofConfig_Adress, (uint8_t *)&ConfigData, sizeof(ConfigData));
}
void GetConfig_EEPROM()
{
if (!checkEEPROMavailable())
return;
ee.readBlock(startofConfig_Adress, (uint8_t *)&ConfigData, sizeof(ConfigData));
uint32_t checksum = ConfigData.checksum;
ConfigData.checksum = 0;
if (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)
{
MaintainDTC(DTC_EEPROM_CFG_BAD, true);
FormatConfig_EEPROM();
}
ConfigData.checksum = checksum;
}
uint32_t getPersistanceAddress()
{
uint32_t eePersistenceMarker;
ee.readBlock(persistencemarker_Adress, (uint8_t *)&eePersistenceMarker, sizeof(eePersistenceMarker));
return eePersistenceMarker;
}
void updatePersistanceAddress(uint32_t adress)
{
ee.updateBlock(persistencemarker_Adress, (uint8_t *)&adress, sizeof(adress));
}
void StorePersistence_EEPROM()
{
if (PersistenceData.writeCycleCounter >= EEPROM_ENDURANCE)
MovePersistencePage_EEPROM(false);
else
PersistenceData.writeCycleCounter++;
PersistenceData.checksum = 0;
PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData));
if (!checkEEPROMavailable())
return;
ee.updateBlock(getPersistanceAddress(), (uint8_t *)&PersistenceData, sizeof(PersistenceData));
}
void GetPersistence_EEPROM()
{
if (!checkEEPROMavailable())
return;
ee.readBlock(getPersistanceAddress(), (uint8_t *)&PersistenceData, sizeof(PersistenceData));
uint32_t checksum = PersistenceData.checksum;
PersistenceData.checksum = 0;
if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)
{
MaintainDTC(DTC_EEPROM_PDS_BAD, true);
FormatPersistence_EEPROM();
}
PersistenceData.checksum = checksum;
}
void FormatConfig_EEPROM()
{
configData_t defaults;
ConfigData = defaults;
ConfigData.EEPROM_Version = eeVersion;
StoreConfig_EEPROM();
}
void FormatPersistence_EEPROM()
{
persistenceData_t defaults;
PersistenceData = defaults;
updatePersistanceAddress(startofPersistence_Adress);
StorePersistence_EEPROM();
}
void MovePersistencePage_EEPROM(boolean reset)
{
if (!checkEEPROMavailable())
return;
if (reset)
{
updatePersistanceAddress(startofPersistence_Adress);
}
else
{
uint32_t newPersistenceMarker = getPersistanceAddress() + sizeof(PersistenceData);
// check if we reached the End of the EEPROM and Startover at the beginning
if ((newPersistenceMarker + sizeof(PersistenceData)) > ee.getDeviceSize())
{
MaintainDTC(DTC_EEPROM_WORNOUT, true);
return;
}
else
{
updatePersistanceAddress(newPersistenceMarker);
PersistenceData.writeCycleCounter = 0;
}
}
}
uint32_t GetEESize()
{
return ee.getDeviceSize();
}
uint32_t Checksum_EEPROM(uint8_t const *data, size_t len)
{
if (data == NULL)
return 0;
uint32_t crc, mask;
crc = 0xFFFFFFFF;
while (len--)
{
crc ^= *data++;
for (uint8_t k = 0; k < 8; k++)
{
mask = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
}
return ~crc;
}
void dumpEEPROM(uint16_t memoryAddress, uint16_t length)
{
#define BLOCK_TO_LENGTH 16
if (length > ee.getDeviceSize())
length = ee.getDeviceSize();
if (memoryAddress + length > ee.getDeviceSize())
return;
if (!checkEEPROMavailable())
return;
char ascii_buf[BLOCK_TO_LENGTH + 1];
sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII");
Serial.print(PSTR("\nAddress "));
for (int x = 0; x < BLOCK_TO_LENGTH; x++)
Serial.printf("%3d", x);
memoryAddress = memoryAddress / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;
length = (length + BLOCK_TO_LENGTH - 1) / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;
for (unsigned int i = 0; i < length; i++)
{
int blockpoint = memoryAddress % BLOCK_TO_LENGTH;
if (blockpoint == 0)
{
ascii_buf[BLOCK_TO_LENGTH] = 0;
Serial.printf(" %s", ascii_buf);
Serial.printf("\n0x%05X:", memoryAddress);
}
ascii_buf[blockpoint] = ee.readByte(memoryAddress);
Serial.printf(" %02X", ascii_buf[blockpoint]);
if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E)
ascii_buf[blockpoint] = '.';
memoryAddress++;
}
Serial.println();
}
boolean checkEEPROMavailable()
{
if (!ee.isConnected())
{
MaintainDTC(DTC_NO_EEPROM_FOUND, true);
return false;
}
return true;
}

69
src/config.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
#include <Arduino.h>
#include <Wire.h>
#include <I2C_eeprom.h>
#include "globals.h"
#include "dtc.h"
#define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC01
#define EEPROM_ENDURANCE 1000000
typedef enum
{
NONE,
FACTION_1,
FACTION_2,
FACTION_3
} factions_t;
typedef struct
{
uint32_t writeCycleCounter = 0;
uint32_t faction_1_timer = 0;
uint32_t faction_2_timer = 0;
uint32_t faction_3_timer = 0;
factions_t activeFaction = NONE;
uint32_t checksum = 0;
} persistenceData_t;
typedef enum
{
BATTERY_UNDEFINED,
BATTERY_LIPO_2S,
BATTERY_LIPO_3S
} batteryType_t;
const char BatteryString[][10]{
"Undefined",
"LiPo 2S",
"LiPo 3S"
};
typedef struct
{
uint8_t EEPROM_Version = 1;
batteryType_t batteryType = BATTERY_UNDEFINED;
uint32_t checksum = 0;
} configData_t;
void InitEEPROM();
void EEPROM_Process();
void StoreConfig_EEPROM();
void GetConfig_EEPROM();
void StorePersistence_EEPROM();
void GetPersistence_EEPROM();
void FormatConfig_EEPROM();
void FormatPersistence_EEPROM();
uint32_t Checksum_EEPROM(uint8_t const *data, size_t len);
void dumpEEPROM(uint16_t memoryAddress, uint16_t length);
void MovePersistencePage_EEPROM(boolean reset);
uint32_t getPersistanceAddress();
void updatePersistanceAddress(uint32_t adress);
uint32_t GetEESize();
extern configData_t ConfigData;
extern persistenceData_t PersistenceData;
#endif // _CONFIG_H_

View File

@@ -2,10 +2,10 @@
#define _DEFAULTS_H_
#ifndef WIFI_CLIENT
#define WIFI_AP
#define WIFI_ACCESSPOINT
#endif
#if defined(WIFI_CLIENT) && defined(WIFI_AP)
#if defined(WIFI_CLIENT) && defined(WIFI_ACCESSPOINT)
#error "You can't define AP and CLIENT at the same Time!"
#endif

85
src/dtc.cpp Normal file
View File

@@ -0,0 +1,85 @@
#include "dtc.h"
DTCEntry_s DTCStorage[MAX_DTC_STORAGE];
void MaintainDTC(DTCNums_t DTC_no, boolean active)
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number == DTC_no)
{
if (active && DTCStorage[i].active != DTC_ACTIVE)
{
Serial.printf("DTC gone active: %d", DTC_no);
DTCStorage[i].timestamp = millis();
DTCStorage[i].active = DTC_ACTIVE;
}
if (!active && DTCStorage[i].active == DTC_ACTIVE)
{
Serial.printf("DTC gone previous: %d", DTC_no);
DTCStorage[i].active = DTC_PREVIOUS;
}
return;
}
}
// DTC was not found with upper iteration, but is active
// so we need to look for free space to store DTC
if (active == true)
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number == DTC_LAST_DTC)
{
Serial.printf("new DTC registered: %d", DTC_no);
DTCStorage[i].Number = DTC_no;
DTCStorage[i].timestamp = millis();
DTCStorage[i].active = DTC_ACTIVE;
return;
}
}
}
}
void ClearDTC(DTCNums_t DTC_no)
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number == DTC_no)
{
DTCStorage[i].Number = DTC_LAST_DTC;
DTCStorage[i].active = DTC_NONE;
DTCStorage[i].timestamp = 0;
}
}
}
void ClearAllDTC()
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
DTCStorage[i].Number = DTC_LAST_DTC;
DTCStorage[i].active = DTC_NONE;
DTCStorage[i].timestamp = 0;
}
}
DTCNums_t getlastDTC(boolean only_active)
{
int8_t pointer = -1;
uint32_t lasttimestamp = 0;
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number > 0 && DTCStorage[i].timestamp > lasttimestamp)
{
if (only_active == false || DTCStorage[i].active == DTC_ACTIVE)
{
pointer = i;
lasttimestamp = DTCStorage[i].timestamp;
}
}
}
return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC;
}

39
src/dtc.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef _DTC_H_
#define _DTC_H_
#include <Arduino.h>
#define MAX_DTC_STORAGE 6
typedef enum DTCNums_e
{
DTC_NO_EEPROM_FOUND,
DTC_EEPROM_CFG_BAD,
DTC_EEPROM_PDS_BAD,
DTC_EEPROM_VERSION_BAD,
DTC_EEPROM_WORNOUT, // this will happen if the EEPROM-cells are all overwritten 1 million times!
DTC_EEPROM_PDS_MARKER_INVALID, // This happens if the Marker of the PersistanceData was pointing to an EE-Adress bigger than the used EEPROM-IC
DTC_LAST_DTC
} DTCNums_t;
typedef enum DTCActive_e
{
DTC_ACTIVE,
DTC_PREVIOUS,
DTC_NONE
} DTCActive_t;
typedef struct DTCEntry_s
{
DTCNums_t Number;
uint32_t timestamp;
DTCActive_t active;
} DTCEntry_t;
void MaintainDTC(DTCNums_t DTC_no, boolean active);
void ClearDTC(DTCNums_t DTC_no);
void ClearAllDTC();
DTCNums_t getlastDTC(boolean only_active);
extern DTCEntry_s DTCStorage[MAX_DTC_STORAGE];
#endif

36
src/globals.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef _GLOBALS_H_
#define _GLOBALS_H_
#include <Arduino.h>
typedef enum eSystem_Status
{
sysStat_Startup,
sysStat_Normal,
sysStat_Error,
sysStat_Shutdown
} tSystem_Status;
typedef enum eEERequest
{
EE_IDLE,
EE_CFG_SAVE,
EE_CFG_LOAD,
EE_PDS_SAVE,
EE_PDS_LOAD
} tEERequest;
typedef struct Globals_s
{
char DeviceName[33];
char DeviceName_ID[43];
tSystem_Status systemStatus = sysStat_Startup;
tSystem_Status resumeStatus = sysStat_Startup;
eEERequest requestEEAction = EE_IDLE;
float loadvoltage = 0;
int battery_level = 0;
} Globals_t;
extern Globals_t globals;
#endif

View File

@@ -1,50 +1,51 @@
#include <Arduino.h>
#include <TM1637Display.h>
#include <Ticker.h>
#ifdef CAPTIVE
#include <DNSServer.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#include <WiFiMulti.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266mDNS.h>
#endif
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
// local includes
#include "defaults.h"
#include "webui.h"
#include "config.h"
#include "globals.h"
#include "dtc.h"
#include "common.h"
// Module connection pins (ESP GPIO-Nums)
#define CLK 16
#define DIO_FAC_1_7SEG 14
#define DIO_FAC_2_7SEG 12
#define DIO_FAC_3_7SEG 13
#ifdef WIFI_CLIENT
#include <WiFiMulti.h>
#define DIO_FAC_1_TRG 0
#define DIO_FAC_2_TRG 2
#define DIO_FAC_3_TRG 15
const char *ssid = QUOTE(WIFI_SSID);
const char *password = QUOTE(WIFI_PASSWORD);
const uint32_t connectTimeoutMs = 5000;
enum Factions
{
NONE,
FACTION_1,
FACTION_2,
FACTION_3
};
WiFiMulti wifiMulti;
#endif
void SevenSeg_Output();
void FactionTicker_callback();
void serialOutTicker_callback();
void inputGetterTicker_callback();
void powerMonitorTicker_callback();
void EEPROMCyclicPDS_callback();
void toggleWiFiAP(boolean shutdown = false);
void SystemShutdown();
void SetBatteryType(batteryType_t type);
void ProcessKeyCombos();
void OverrideDisplay(const uint8_t *message, uint32_t time);
#ifdef WIFI_CLIENT
void wifiMaintainConnectionTicker_callback();
Ticker WiFiMaintainConnectionTicker(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS);
#endif
uint32_t getESPChipID();
TM1637Display disp_FAC_1(CLK, DIO_FAC_1_7SEG);
TM1637Display disp_FAC_2(CLK, DIO_FAC_2_7SEG);
@@ -52,309 +53,49 @@ TM1637Display disp_FAC_3(CLK, DIO_FAC_3_7SEG);
Adafruit_INA219 ina219;
WiFiEventHandler stationConnectedHandler;
WiFiEventHandler stationDisconnectedHandler;
#ifdef WIFI_CLIENT
ESP8266WiFiMulti wifiMulti;
#endif
#ifdef CAPTIVE
DNSServer dnsServer;
#endif
AsyncWebServer server(80);
Ticker FactionTicker(FactionTicker_callback, 1000, 0, MILLIS);
Ticker SerialOutputTicker(serialOutTicker_callback, 5000, 0, MILLIS);
Ticker InputGetterTicker(inputGetterTicker_callback, 250, 0, MILLIS);
Ticker PowerMonitorTicker(powerMonitorTicker_callback, 5000, 0, MILLIS);
Ticker EEPROMCyclicPDSTicker(EEPROMCyclicPDS_callback, 60000, 0, MILLIS);
Factions activeFaction = NONE;
uint32_t Count_Faction_1 = 0;
uint32_t Count_Faction_2 = 0;
uint32_t Count_Faction_3 = 0;
uint8_t Faction_1_dot = 0;
uint8_t Faction_2_dot = 0;
uint8_t Faction_3_dot = 0;
float loadvoltage = 0;
int battery_level = 0;
uint32_t DisplayOverrideFlag = 0;
uint8_t *DisplayOverrideValue = 0;
Globals_t globals;
const uint8_t sevenSeg_bat[] = {0x00, 0b01111100, 0b01110111, 0b01111000};
const uint8_t sevenSeg_low[] = {0b00111000, 0b01011100, 0x00, 0x00};
String processor(const String &var)
{
char buffer[16] = {0};
if (var == "POINTS_FAC_1")
itoa(Count_Faction_1, buffer, 10);
if (var == "POINTS_FAC_2")
itoa(Count_Faction_2, buffer, 10);
if (var == "POINTS_FAC_3")
itoa(Count_Faction_3, buffer, 10);
if (var == "STATUS_FAC_1")
return activeFaction == FACTION_1 ? "ACTIVE" : "INACTIVE";
if (var == "STATUS_FAC_2")
return activeFaction == FACTION_2 ? "ACTIVE" : "INACTIVE";
if (var == "STATUS_FAC_3")
return activeFaction == FACTION_3 ? "ACTIVE" : "INACTIVE";
if (var == "NAME_FAC_1")
return FACTION_1_NAME;
if (var == "NAME_FAC_2")
return FACTION_2_NAME;
if (var == "NAME_FAC_3")
return FACTION_3_NAME;
if (var == "TITLE")
return DEVICE_NAME;
if (var == "BATTERY_LEVEL")
{
sprintf(buffer, "%d", battery_level);
return String(buffer);
}
if (var == "BATTERY_VOLTAGE")
{
sprintf(buffer, "%f", loadvoltage);
return String(buffer);
}
return String(buffer);
}
String macToString(const unsigned char *mac)
{
char buf[20];
snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buf);
}
void onStationConnected(const WiFiEventSoftAPModeStationConnected &evt)
{
Serial.print("Station connected: ");
Serial.println(macToString(evt.mac));
}
void onStationDisconnected(const WiFiEventSoftAPModeStationDisconnected &evt)
{
Serial.print("Station disconnected: ");
Serial.println(macToString(evt.mac));
}
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++)
{
if (data.charAt(i) == separator || i == maxIndex)
{
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
class CaptiveRequestHandler : public AsyncWebHandler
{
public:
CaptiveRequestHandler() {}
virtual ~CaptiveRequestHandler() {}
bool canHandle(AsyncWebServerRequest *request)
{
return true;
}
void handleRequest(AsyncWebServerRequest *request)
{
Serial.print("Requested URL: ");
Serial.println(request->url());
//List all parameters
int params = request->params();
for (int i = 0; i < params; i++)
{
AsyncWebParameter *p = request->getParam(i);
if (p->isFile())
{ //p->isPost() is also true
Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
}
else if (p->isPost())
{
Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
}
else
{
Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
}
}
if (LittleFS.exists(request->url()) == false)
{
request->send(404, "text/plain", "Not found");
return;
}
if (request->url() == "/favicon.ico")
{
request->send(LittleFS, request->url(), "image/x-icon");
return;
}
if (request->url() == "/" || request->url() == "/index.html")
{
request->send(LittleFS, "/index.html", String(), false, processor);
return;
}
if (getValue(request->url(), '/', 1) == "css")
{
request->send(LittleFS, request->url(), "text/css");
return;
}
}
};
void SevenSeg_Output()
{
if (battery_level < BAT_LOW_PERCENT)
{
disp_FAC_1.setBrightness(0);
disp_FAC_2.setBrightness(0);
disp_FAC_3.setBrightness(0);
disp_FAC_3.setSegments(sevenSeg_bat);
disp_FAC_2.setSegments(sevenSeg_low);
if (millis() % 5000 > 2500)
disp_FAC_1.showNumberDec(battery_level);
else
disp_FAC_1.showNumberDecEx(loadvoltage * 100, 0x40);
}
else
{
disp_FAC_1.setBrightness(activeFaction == FACTION_1 ? 7 : 0);
disp_FAC_2.setBrightness(activeFaction == FACTION_2 ? 7 : 0);
disp_FAC_3.setBrightness(activeFaction == FACTION_3 ? 7 : 0);
disp_FAC_1.showNumberDecEx(Count_Faction_1 / 60, Faction_1_dot, true, 4, 0);
disp_FAC_2.showNumberDecEx(Count_Faction_2 / 60, Faction_2_dot, true, 4, 0);
disp_FAC_3.showNumberDecEx(Count_Faction_3 / 60, Faction_3_dot, true, 4, 0);
}
}
void FactionTicker_callback()
{
switch (activeFaction)
{
case FACTION_1:
Count_Faction_1++;
Faction_1_dot = Faction_1_dot == 0x80 || Faction_1_dot == 0x00 ? 0x10 : Faction_1_dot << 1;
Faction_2_dot = 0;
Faction_3_dot = 0;
break;
case FACTION_2:
Count_Faction_2++;
Faction_2_dot = Faction_2_dot == 0x80 || Faction_2_dot == 0x00 ? 0x10 : Faction_2_dot << 1;
Faction_1_dot = 0;
Faction_3_dot = 0;
break;
case FACTION_3:
Count_Faction_3++;
Faction_3_dot = Faction_3_dot == 0x80 || Faction_3_dot == 0x00 ? 0x10 : Faction_3_dot << 1;
Faction_1_dot = 0;
Faction_2_dot = 0;
break;
default:
break;
}
}
void serialOutTicker_callback()
{
static uint32_t SerialPrintCount = 0;
if (SerialPrintCount % 10 == 0)
{
Serial.printf("| %8s | %8s | %8s |\n", FACTION_1_NAME, FACTION_2_NAME, FACTION_3_NAME);
}
Serial.printf(" %8d %8d %8d\n", Count_Faction_1, Count_Faction_2, Count_Faction_3);
SerialPrintCount++;
}
void inputGetterTicker_callback()
{
activeFaction = NONE;
if (digitalRead(DIO_FAC_1_TRG) + digitalRead(DIO_FAC_2_TRG) + !digitalRead(DIO_FAC_3_TRG) < 2)
{
Serial.println("ERROR: More than one Flag active");
return;
}
if (digitalRead(DIO_FAC_1_TRG) == LOW)
activeFaction = FACTION_1;
if (digitalRead(DIO_FAC_2_TRG) == LOW)
activeFaction = FACTION_2;
if (digitalRead(DIO_FAC_3_TRG) == HIGH)
activeFaction = FACTION_3;
}
void powerMonitorTicker_callback()
{
// loadvoltage and percentage is global, because of battery Monitoring
float shuntvoltage = 0;
float current_mA = 0;
float busvoltage = 0;
float power_mW = 0;
shuntvoltage = ina219.getShuntVoltage_mV();
busvoltage = ina219.getBusVoltage_V();
current_mA = ina219.getCurrent_mA();
power_mW = ina219.getPower_mW();
loadvoltage = busvoltage + (shuntvoltage / 1000);
battery_level = map(loadvoltage * 100, 655, 840, 0, 100);
Serial.printf("Battery Level: %d %%\n", battery_level);
Serial.printf("Bus Voltage: %f V\n", busvoltage);
Serial.printf("Shunt Voltage: %f mV\n", shuntvoltage);
Serial.printf("Load Voltage: %f V\n", loadvoltage);
Serial.printf("Current: %f mA\n", current_mA);
Serial.printf("Power: %f mW\n", power_mW);
}
const uint8_t sevenSeg_net[] = {0b01010100, 0b01111001, 0b01111000, 0x00};
const uint8_t sevenSeg_ota[] = {0x3F, 0x78, 0x77, 0x00};
const uint8_t sevenSeg_flsh[] = {0x71, 0x38, 0x6D, 0x76};
const uint8_t sevenSeg_file[] = {0x71, 0x30, 0x38, 0x79};
void setup()
{
setCpuFrequencyMhz(80);
WiFi.setAutoReconnect (false);
WiFi.persistent(false);
WiFi.disconnect();
Serial.begin(115200);
Serial.print("\n\n\n");
strcpy(globals.DeviceName, DEVICE_NAME);
snprintf(globals.DeviceName_ID, 42, "%s_%08X", globals.DeviceName, getESPChipID());
pinMode(DIO_FAC_1_TRG, INPUT_PULLUP);
pinMode(DIO_FAC_2_TRG, INPUT_PULLUP);
pinMode(DIO_FAC_3_TRG, INPUT);
Serial.begin(9600);
Serial.print("\n\n\n");
#ifdef SERIAL_DEBUG
Serial.setDebugOutput(true);
#endif
@@ -366,53 +107,51 @@ void setup()
LittleFS.begin();
WiFi.persistent(false);
#ifdef WIFI_AP
WiFi.mode(WIFI_AP);
WiFi.softAP(WIFI_SSID, WIFI_PASS);
stationConnectedHandler = WiFi.onSoftAPModeStationConnected(&onStationConnected);
stationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(&onStationDisconnected);
#else
#ifdef WIFI_CLIENT
WiFi.mode(WIFI_STA);
wifiMulti.addAP(WIFI_SSID, WIFI_PASS);
Serial.println("Connecting Wifi...");
if (wifiMulti.run() == WL_CONNECTED)
{
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.print(WiFi.localIP());
}
WiFi.setHostname(globals.DeviceName_ID);
wifiMulti.addAP(QUOTE(WIFI_SSID), QUOTE(WIFI_PASSWORD));
WiFiMaintainConnectionTicker.start();
#else
WiFi.mode(WIFI_AP);
WiFi.begin(QUOTE(DEVICE_NAME), QUOTE(WIFI_AP_PASSWORD));
WiFi.setSleep(true);
WiFi.mode(WIFI_OFF);
#endif
InitEEPROM();
ArduinoOTA.setPort(8266);
ArduinoOTA.setHostname(OTA_HOST);
ArduinoOTA.setPassword(OTA_PASS);
ArduinoOTA.setHostname(globals.DeviceName_ID);
ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD));
ArduinoOTA.onStart([]()
{
String type;
disp_FAC_1.setBrightness(7);
disp_FAC_2.setBrightness(7);
disp_FAC_3.setBrightness(7);
disp_FAC_1.setSegments(sevenSeg_ota);
disp_FAC_3.clear();
if (ArduinoOTA.getCommand() == U_FLASH)
{
type = "sketch";
disp_FAC_2.setSegments(sevenSeg_flsh);
}
else
{
type = "filesystem";
disp_FAC_2.setSegments(sevenSeg_file);
LittleFS.end();
}
Serial.println("Start updating " + type);
});
} });
ArduinoOTA.onEnd([]()
{ Serial.println("\nEnd"); });
{
const uint8_t seg_done[] = {0x5E, 0x3F, 0x54, 0x79};
disp_FAC_1.setSegments(seg_done);
disp_FAC_2.clear();
disp_FAC_3.clear(); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{ Serial.printf("Progress: %u%%\r", (progress / (total / 100))); });
{ disp_FAC_3.showNumberDecEx((progress / (total / 100))); });
ArduinoOTA.onError([](ota_error_t error)
{
@@ -426,70 +165,320 @@ void setup()
else if (error == OTA_RECEIVE_ERROR)
Serial.println("Receive Failed");
else if (error == OTA_END_ERROR)
Serial.println("End Failed");
});
Serial.println("End Failed"); });
ArduinoOTA.begin();
#ifdef CAPTIVE
dnsServer.start(53, "*", WiFi.softAPIP());
#endif
server.addHandler(new CaptiveRequestHandler());
server.begin();
initWebUI();
EEPROMCyclicPDSTicker.start();
FactionTicker.start();
SerialOutputTicker.start();
InputGetterTicker.start();
Serial.println("Setup Done");
}
void loop()
{
EEPROMCyclicPDSTicker.update();
FactionTicker.update();
SerialOutputTicker.update();
InputGetterTicker.update();
PowerMonitorTicker.update();
ArduinoOTA.handle();
SevenSeg_Output();
EEPROM_Process();
#ifdef CAPTIVE
dnsServer.processNextRequest();
#endif
#ifdef WIFI_CLIENT
WiFiMaintainConnectionTicker.update();
#endif
if (globals.systemStatus == sysStat_Shutdown)
SystemShutdown();
yield();
}
SevenSeg_Output();
String macToString(const unsigned char *mac)
{
char buf[20];
snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buf);
}
while (Serial.available() > 0)
void SevenSeg_Output()
{
if (DisplayOverrideFlag > millis())
{
char input = Serial.read();
switch (input)
disp_FAC_1.setBrightness(7);
disp_FAC_2.setBrightness(7);
disp_FAC_3.setBrightness(7);
disp_FAC_1.setSegments(DisplayOverrideValue);
disp_FAC_2.clear();
disp_FAC_3.clear();
}
else
{
if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000)
{
case 'n':
activeFaction = NONE;
break;
disp_FAC_1.setBrightness(0);
disp_FAC_2.setBrightness(0);
disp_FAC_3.setBrightness(0);
case 'g':
activeFaction = FACTION_1;
break;
disp_FAC_1.setSegments(sevenSeg_bat);
disp_FAC_2.setSegments(sevenSeg_low);
if (millis() % 3000 < 1500)
disp_FAC_3.showNumberDec(globals.battery_level);
else
disp_FAC_3.showNumberDecEx(globals.loadvoltage * 100, 0x40);
}
else
{
disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 7 : 0);
disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 7 : 0);
disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 7 : 0);
case 'k':
activeFaction = FACTION_3;
break;
case 'm':
activeFaction = FACTION_2;
break;
case 'x':
SerialOutputTicker.stop();
Serial.println("SerialOutputTicker.stop()");
break;
case 'y':
SerialOutputTicker.resume();
Serial.println("SerialOutputTicker.resume()");
break;
default:
break;
disp_FAC_1.showNumberDecEx(PersistenceData.faction_1_timer / 60, Faction_1_dot, true, 4, 0);
disp_FAC_2.showNumberDecEx(PersistenceData.faction_2_timer / 60, Faction_2_dot, true, 4, 0);
disp_FAC_3.showNumberDecEx(PersistenceData.faction_3_timer / 60, Faction_3_dot, true, 4, 0);
}
}
}
void FactionTicker_callback()
{
switch (PersistenceData.activeFaction)
{
case FACTION_1:
PersistenceData.faction_1_timer++;
Faction_1_dot = Faction_1_dot == 0x80 || Faction_1_dot == 0x00 ? 0x10 : Faction_1_dot << 1;
Faction_2_dot = 0;
Faction_3_dot = 0;
break;
case FACTION_2:
PersistenceData.faction_2_timer++;
Faction_2_dot = Faction_2_dot == 0x80 || Faction_2_dot == 0x00 ? 0x10 : Faction_2_dot << 1;
Faction_1_dot = 0;
Faction_3_dot = 0;
break;
case FACTION_3:
PersistenceData.faction_3_timer++;
Faction_3_dot = Faction_3_dot == 0x80 || Faction_3_dot == 0x00 ? 0x10 : Faction_3_dot << 1;
Faction_1_dot = 0;
Faction_2_dot = 0;
break;
default:
break;
}
}
void inputGetterTicker_callback()
{
ProcessKeyCombos();
uint8_t keysPressed = 0;
keysPressed = +digitalRead(DIO_FAC_1_TRG) == FAC_1_TRG_PRESSED ? 1 : 0;
keysPressed = +digitalRead(DIO_FAC_2_TRG) == FAC_2_TRG_PRESSED ? 1 : 0;
keysPressed = +digitalRead(DIO_FAC_3_TRG) == FAC_3_TRG_PRESSED ? 1 : 0;
if (keysPressed > 1)
{
Serial.println("ERROR: More than one Flag active - setting no Faction active");
PersistenceData.activeFaction = NONE;
return;
}
if (digitalRead(DIO_FAC_1_TRG) == FAC_1_TRG_PRESSED)
PersistenceData.activeFaction = FACTION_1;
if (digitalRead(DIO_FAC_2_TRG) == FAC_2_TRG_PRESSED)
PersistenceData.activeFaction = FACTION_2;
if (digitalRead(DIO_FAC_3_TRG) == FAC_3_TRG_PRESSED)
PersistenceData.activeFaction = FACTION_3;
}
void powerMonitorTicker_callback()
{
// loadvoltage and percentage is global, because of battery Monitoring
const int bat_min_2s = 680;
const int bat_max_2s = 840;
const int bat_min_3s = 1020;
const int bat_max_3s = 1260;
float shuntvoltage = 0;
float current_mA = 0;
float busvoltage = 0;
float power_mW = 0;
int battery_level = 0;
shuntvoltage = ina219.getShuntVoltage_mV();
busvoltage = ina219.getBusVoltage_V();
current_mA = ina219.getCurrent_mA();
power_mW = ina219.getPower_mW();
globals.loadvoltage = busvoltage + (shuntvoltage / 1000);
switch (ConfigData.batteryType)
{
case BATTERY_LIPO_2S:
battery_level = map(globals.loadvoltage * 100, bat_min_2s, bat_max_2s, 0, 100);
globals.battery_level = battery_level < 0 ? 0 : battery_level;
break;
case BATTERY_LIPO_3S:
battery_level = map(globals.loadvoltage * 100, bat_min_3s, bat_max_3s, 0, 100);
globals.battery_level = battery_level < 0 ? 0 : battery_level;
break;
default:
globals.battery_level = -1;
break;
}
Serial.printf("Battery Level: %d %%\n", globals.battery_level);
Serial.printf("Bus Voltage: %f V\n", busvoltage);
Serial.printf("Shunt Voltage: %f mV\n", shuntvoltage);
Serial.printf("Load Voltage: %f V\n", globals.loadvoltage);
Serial.printf("Current: %f mA\n", current_mA);
Serial.printf("Power: %f mW\n", power_mW);
}
void EEPROMCyclicPDS_callback()
{
StorePersistence_EEPROM();
}
#ifdef WIFI_CLIENT
void wifiMaintainConnectionTicker_callback()
{
static uint32_t WiFiFailCount = 0;
const uint32_t WiFiFailMax = 20;
if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED)
{
return;
}
else
{
if (WiFiFailCount < WiFiFailMax)
WiFiFailCount++;
else
toggleWiFiAP(false);
}
}
#endif
void toggleWiFiAP(boolean shutdown)
{
if (WiFi.getMode() != WIFI_OFF && shutdown == true)
{
WiFi.mode(WIFI_OFF);
#ifdef WIFI_CLIENT
WiFiMaintainConnectionTicker.stop();
#endif
}
else if (shutdown == false)
{
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0));
WiFi.softAP(globals.DeviceName_ID, QUOTE(WIFI_AP_PASSWORD));
#ifdef WIFI_CLIENT
WiFiMaintainConnectionTicker.stop();
#endif
}
}
void SystemShutdown()
{
StoreConfig_EEPROM();
ESP.restart();
}
void SetBatteryType(batteryType_t type)
{
if (ConfigData.batteryType != type)
{
ConfigData.batteryType = type;
globals.requestEEAction = EE_CFG_SAVE;
Serial.printf("Set Batterytype to %s\n", type == BATTERY_LIPO_2S ? "2s Lipo" : "3s LiPo");
}
}
void ProcessKeyCombos()
{
typedef enum
{
KEY_PRESSED,
KEY_RELEASED
} keyStatus_t;
static keyStatus_t keyStatus_Fac1 = KEY_RELEASED;
static uint8_t keyCount_Fac1 = 0;
static keyStatus_t keyStatus_Fac2 = KEY_RELEASED;
static uint8_t keyCount_Fac2 = 0;
static keyStatus_t keyStatus_Fac3 = KEY_RELEASED;
if (digitalRead(DIO_FAC_3_TRG) == FAC_3_TRG_PRESSED)
{
keyStatus_Fac3 = KEY_PRESSED;
// Process FactionKey 1 ComboCounter
if (digitalRead(DIO_FAC_1_TRG) == FAC_1_TRG_PRESSED && keyStatus_Fac1 == KEY_RELEASED)
{
keyStatus_Fac1 = KEY_PRESSED;
keyCount_Fac1++;
}
if (digitalRead(DIO_FAC_1_TRG) != FAC_1_TRG_PRESSED)
keyStatus_Fac1 = KEY_RELEASED;
// Process FactionKey 2 ComboCounter
if (digitalRead(DIO_FAC_2_TRG) == FAC_2_TRG_PRESSED && keyStatus_Fac2 == KEY_RELEASED)
{
keyStatus_Fac2 = KEY_PRESSED;
keyCount_Fac2++;
}
if (digitalRead(DIO_FAC_2_TRG) != FAC_2_TRG_PRESSED)
keyStatus_Fac2 = KEY_RELEASED;
}
if (digitalRead(DIO_FAC_3_TRG) != FAC_3_TRG_PRESSED && keyStatus_Fac3 == KEY_PRESSED)
{
Serial.printf("KeyCombo 1: %d | 2: %d\n", keyCount_Fac1, keyCount_Fac2);
if (keyCount_Fac1 == 2 && keyCount_Fac2 == 2)
{
Serial.println("KeyCombo: WiFi AP ON");
OverrideDisplay(sevenSeg_net, 5000);
toggleWiFiAP(false);
}
keyCount_Fac1 = 0;
keyCount_Fac2 = 0;
keyStatus_Fac1 = KEY_RELEASED;
keyStatus_Fac2 = KEY_RELEASED;
keyStatus_Fac3 = KEY_RELEASED;
}
}
void OverrideDisplay(const uint8_t *message, uint32_t time)
{
DisplayOverrideFlag = millis() + time;
DisplayOverrideValue = (uint8_t *)message;
}
uint32_t getESPChipID()
{
uint32_t chipId;
for (int i = 0; i < 17; i = i + 8)
{
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
return chipId;
}

209
src/webui.cpp Normal file
View File

@@ -0,0 +1,209 @@
#include "webui.h"
AsyncWebServer webServer(80);
typedef enum
{
RESPMSG_HIDE,
RESPMSG_SUCCESS,
RESPMSG_INFO,
RESPMSG_WARNING,
RESPMSG_DANGER
} statusResponseMessage_Type_t;
char StatusResponseMessage[64];
statusResponseMessage_Type_t StatusResponseMessage_Type = RESPMSG_INFO;
String processor(const String &var);
void WebserverPOST_Callback(AsyncWebServerRequest *request);
void WebserverNotFound_Callback(AsyncWebServerRequest *request);
void Webserver_Callback(AsyncWebServerRequest *request);
void WebserverCommands_Callback(String input);
void initWebUI()
{
if (!LittleFS.begin())
{
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000");
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->redirect("/index.htm"); });
webServer.onNotFound(WebserverNotFound_Callback);
webServer.on("/index.htm", HTTP_GET, Webserver_Callback);
webServer.on("/index.htm", HTTP_POST, WebserverPOST_Callback);
webServer.begin();
}
String processor(const String &var)
{
if (var == "SHOW_DTC_TABLE")
return globals.systemStatus == sysStat_Error ? "" : "hidden";
if (var == "SHOW_RESP_MESSAGE")
return StatusResponseMessage_Type != RESPMSG_HIDE ? "" : "hidden";
if (var == "RESP_MESSAGE_TYPE")
{
switch (StatusResponseMessage_Type)
{
case RESPMSG_SUCCESS:
return "success";
case RESPMSG_INFO:
return "info";
case RESPMSG_WARNING:
return "warning";
case RESPMSG_DANGER:
return "danger";
default:
return "info";
}
}
if (var == "RESP_MESSAGE")
return String(StatusResponseMessage);
if (var == "BAT_REMAIN_CAPACITY")
return String(globals.battery_level);
if (var == "DEVICE_NAME")
return String(globals.DeviceName);
if (var == "BAT_VOLTAGE")
return String(globals.loadvoltage);
if (var == "DTC_TABLE")
{
String temp;
char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx
for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number > 0)
{
sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d",
DTCStorage[i].timestamp / 86400000, // Days
DTCStorage[i].timestamp / 360000 % 24, // Hours
DTCStorage[i].timestamp / 60000 % 60, // Minutes
DTCStorage[i].timestamp / 1000 % 60, // Seconds
DTCStorage[i].timestamp % 1000); // milliseconds
temp = "<tr><td>" + String(buff_timestamp);
temp = temp + "</td><td>" + String(DTCStorage[i].Number) + "</td><td>";
if (DTCStorage[i].active == DTC_ACTIVE)
temp = temp + "active";
else if (DTCStorage[i].active == DTC_PREVIOUS)
temp = temp + "previous";
else
temp = temp + "none";
temp = temp + "</td></tr>";
}
}
return temp;
}
if (var == "PLACEHOLDER")
return "placeholder";
if (var == "POINTS_FAC_1")
{
char buff[12];
snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60);
return String(buff);
}
if (var == "POINTS_FAC_2")
{
char buff[12];
snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60);
return String(buff);
}
if (var == "POINTS_FAC_3")
{
char buff[12];
snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60);
return String(buff);
}
if (var == "STATUS_FAC_1")
return PersistenceData.activeFaction == FACTION_1 ? "ACTIVE" : "INACTIVE";
if (var == "STATUS_FAC_2")
return PersistenceData.activeFaction == FACTION_2 ? "ACTIVE" : "INACTIVE";
if (var == "STATUS_FAC_3")
return PersistenceData.activeFaction == FACTION_3 ? "ACTIVE" : "INACTIVE";
if (var == "NAME_FAC_1")
return FACTION_1_NAME;
if (var == "NAME_FAC_2")
return FACTION_2_NAME;
if (var == "NAME_FAC_3")
return FACTION_3_NAME;
if (var == "TITLE")
return DEVICE_NAME;
if (var == "BATTERY_LEVEL")
{
return String(globals.battery_level);
}
if (var == "BATTERY_TYPE")
{
return String(BatteryString[ConfigData.batteryType]);
}
if (var == "BATTERY_VOLTAGE")
{
return String(globals.loadvoltage);
}
return String();
}
void Webserver_Callback(AsyncWebServerRequest *request)
{
request->send(LittleFS, "/index.htm", "text/html", false, processor);
StatusResponseMessage_Type = RESPMSG_HIDE;
}
void WebserverPOST_Callback(AsyncWebServerRequest *request)
{
int paramsNr = request->params();
for (int i = 0; i < paramsNr; i++)
{
AsyncWebParameter *p = request->getParam(i);
if (p->name() == "commandInput")
WebserverCommands_Callback(p->value());
}
request->send(LittleFS, "/index.htm", "text/html", false, processor);
}
void WebserverNotFound_Callback(AsyncWebServerRequest *request)
{
request->send(404, "text/html", "Not found");
}
void WebserverCommands_Callback(String input)
{
String command = input.substring(0, input.indexOf(' '));
command.toUpperCase();
StatusResponseMessage_Type = RESPMSG_HIDE;
if (command == "RESET")
{
strcpy(StatusResponseMessage, "Counter Reset done");
StatusResponseMessage_Type = RESPMSG_SUCCESS;
PersistenceData.faction_1_timer = 0;
PersistenceData.faction_2_timer = 0;
PersistenceData.faction_3_timer = 0;
PersistenceData.activeFaction = NONE;
}
}

15
src/webui.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef _WEBUI_H_
#define _WEBUI_H_
#include <Arduino.h>
#include <FS.h>
#include <LittleFS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "config.h"
#include "globals.h"
#include "dtc.h"
void initWebUI();
#endif

View File

@@ -0,0 +1,5 @@
[wifi_cred]
wifi_ap_password = wifiappass
wifi_ssid = wifi-ssid
wifi_password = wifi-pass
admin_password = ota-password