1
0
mirror of https://github.com/s00500/ESPUI.git synced 2026-03-13 06:02:40 +00:00

49 Commits

Author SHA1 Message Date
c7b7edf7c9 regenerate all assets
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2026-01-28 16:16:08 +01:00
79581b6f5c Merge branch 'time-retrigger' 2026-01-28 16:10:58 +01:00
fdd455b999 chreate ports of all examples for pio
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2026-01-28 15:36:08 +01:00
458e21861d Fix #337 : tabbed content issue on mobile
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2026-01-28 09:25:24 +01:00
b7f18f9e2a Add go version of helper tool
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2026-01-28 09:22:04 +01:00
e0f06b4aeb Merge pull request #336 from blackpencil2019/custom
Make text labels of data points more readable
2026-01-20 16:04:01 +01:00
blackpencil
92eba52600 Make text labels of data points more readable 2026-01-20 21:06:24 +08:00
32d51125ab Merge pull request #335 from emmby/setPanelClass
User-defined CSS
2026-01-12 19:59:17 +01:00
0b47135a31 readme fixes
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2026-01-12 19:57:33 +01:00
Mike Burton
f922db3502 update generated files 2025-11-29 17:33:52 -08:00
Mike Burton
c8c41aa077 add example of using setCustomCSS + setPanelClass 2025-11-29 09:53:38 -08:00
Mike Burton
f990016623 update for clarity 2025-11-29 09:24:50 -08:00
Mike Burton
cea38b4b50 fix duplicate declaration 2025-11-29 09:04:08 -08:00
Mike Burton
8a7195827b more whitespace 2025-11-29 08:53:38 -08:00
Mike Burton
b68b7ce390 undo more whitespace 2025-11-29 08:53:10 -08:00
Mike Burton
9a589296ad update for clarity 2025-11-29 08:50:45 -08:00
Mike Burton
a05bbb6d9f undo whitespace changes 2025-11-29 08:49:45 -08:00
Mike Burton
dbb8774e93 Update README 2025-11-29 08:22:44 -08:00
Mike Burton
289fdb2ad7 feat: Add support for user-defined panel CSS classes. 2025-11-29 08:10:17 -08:00
Mike Burton
d9412d9d3c generate minified files 2025-11-27 02:15:07 +00:00
Mike Burton
9a088c090f Merge remote-tracking branch 'origin/master' into feature-setCustomCSS 2025-11-27 02:05:49 +00:00
aed06457b3 #310 Fix time retrigger loop
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2025-11-26 21:31:39 +01:00
ed9d7d58a4 Closes #300 remove unused function
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2025-11-26 21:29:52 +01:00
bdfd7cd794 #320 Fix disabled buttons not being disabled
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2025-11-26 21:25:34 +01:00
c5e5c8323e #329 Add clientCount getter
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2025-11-26 21:23:15 +01:00
4971f1c7f9 regenerate all assets
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2025-11-26 21:17:04 +01:00
415bc52416 Merge pull request #333 from emmby/master
User-defined JavaScript
2025-11-26 21:15:57 +01:00
4effb15b49 Merge branch 'master' into master 2025-11-26 21:15:47 +01:00
2a6fa24892 Fix classes for mobile
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2025-11-24 12:21:31 +01:00
google-labs-jules[bot]
368eeff3f8 Implement setCustomCSS for custom CSS injection
This commit introduces `setCustomCSS` to the ESPUI class, allowing users to inject custom CSS into the web interface, analogous to `setCustomJS`.

Changes include:
- Added `setCustomCSS` method to `ESPUI.h` and `ESPUI.cpp`.
- Added `/css/custom.css` route handler in `ESPUI.begin`.
- Updated `data/index.htm` to include the link to `/css/custom.css`.
- Regenerated static UI sources (minified files and C headers) using `tools/prepare_static_ui_sources.py` for index.htm.
- Updated README.md with usage instructions.
2025-11-22 22:33:41 +00:00
google-labs-jules[bot]
6b28240d24 Implement setCustomCSS for custom CSS injection
This commit introduces `setCustomCSS` to the ESPUI class, allowing users to inject custom CSS into the web interface, analogous to `setCustomJS`.

Changes include:
- Added `setCustomCSS` method to `ESPUI.h` and `ESPUI.cpp`.
- Added `/css/custom.css` route handler in `ESPUI.begin`.
- Updated `data/index.htm` to include the link to `/css/custom.css`.
- Updated `src/dataIndexHTML.h` and `data/index.min.htm` with the regenerated content including the new link.
- Reverted unintended changes to other generated files.
2025-11-22 22:29:58 +00:00
google-labs-jules[bot]
f6da0ed7e8 Implement setCustomCSS for custom CSS injection
This commit introduces `setCustomCSS` to the ESPUI class, allowing users to inject custom CSS into the web interface, analogous to `setCustomJS`.

Changes include:
- Added `setCustomCSS` method to `ESPUI.h` and `ESPUI.cpp`.
- Added `/css/custom.css` route handler in `ESPUI.begin`.
- Updated `data/index.htm` to include the link to `/css/custom.css`.
- Regenerated static UI sources (minified files and C headers) using `tools/prepare_static_ui_sources.py`.
2025-11-22 22:22:28 +00:00
google-labs-jules[bot]
41c75cc41e Implement setCustomCSS for custom CSS injection
This commit introduces `setCustomCSS` to the ESPUI class, allowing users to inject custom CSS into the web interface, analogous to `setCustomJS`.

Changes include:
- Added `setCustomCSS` method to `ESPUI.h` and `ESPUI.cpp`.
- Added `/css/custom.css` route handler in `ESPUI.begin`.
- Updated `src/dataIndexHTML.h` to include the link to `/css/custom.css` in both `HTML_INDEX` and `HTML_INDEX_GZIP`.
2025-11-22 22:08:37 +00:00
Mike Burton
a20fe6a4d6 remove unnecessary example 2025-11-20 08:11:02 -08:00
Mike Burton
0b16da9856 Add instructions to README 2025-11-20 08:07:46 -08:00
Mike Burton
ebe72e769e comments 2025-11-20 07:23:19 -08:00
Mike Burton
73c3a97cf8 comments 2025-11-20 07:21:44 -08:00
Mike Burton
af53137b55 comments 2025-11-20 07:19:32 -08:00
Mike Burton
e54a2ff8e6 Add /js/custom.js to index.htm 2025-11-20 07:16:53 -08:00
Mike Burton
47c4430a86 Add ability to set custom js at /js/custom.js 2025-11-18 08:01:10 -08:00
4e32746159 Merge pull request #324 from nanostra/master
ESPAsyncWebserver dependencies update
2025-04-12 12:39:08 +02:00
Frederic.D
027bc9c74a ESPAsyncWebserver dependencies update
Change from authors "Hristo Gochkov" to "ESP32Async"

Project moved to ESP32Async organization at https://github.com/ESP32Async/AsyncTCP
2025-01-25 22:13:49 +01:00
ceffe2b7b3 Merge pull request #316 from joyfullservice/master
Update key verification syntax
2024-11-06 09:32:25 +01:00
joyfullservice
c42c40d881 Update key verification syntax
Resolve deprecation warnings for changes in ArduinoJson. See: https://arduinojson.org/v7/api/jsondocument/containskey/
2024-10-04 16:14:41 -05:00
0ceb052a2e Merge pull request #315 from joyfullservice/master
Resolve null pointer exception
2024-09-28 16:45:28 +02:00
joyfullservice
8c729ee0da Remove unneeded arguments from separator function
Implement an alternate strategy for avoiding the null pointer exception. This takes the same approach as the function to add a label.
2024-09-27 09:29:03 -05:00
joyfullservice
8ca8241547 Resolve null pointer exception
Adding a separator using `ESPUI.separator("Separator name");` triggers an exception when the code tries to access the property of an invalid object.
2024-09-25 15:07:13 -05:00
66340823db Merge pull request #314 from telesyst/Original
Update ESPUI.cpp
2024-09-02 21:13:09 +02:00
Nikola Kirov
25250dd026 Update ESPUI.cpp
Captive Portal memory leak (~2KB) fix
2024-08-31 20:05:40 +03:00
41 changed files with 2626 additions and 453 deletions

View File

@@ -524,7 +524,8 @@ or other CSS effects. Add styles with the following functions:
``` ```
setPanelStyle(uint16_t id, String style); setPanelStyle(uint16_t id, String style);
setElementStyle(uint16_t id, String style) setElementStyle(uint16_t id, String style);
setPanelClass(uint16_t id, String pClass);
``` ```
A panel style is applied to the panel on which the UI element is placed, an element style is applied to the element itself. A panel style is applied to the panel on which the UI element is placed, an element style is applied to the element itself.
@@ -551,6 +552,8 @@ The [completeExample](examples/completeExample/completeExample.cpp) example incl
![More Inline Styles](docs/ui_inlinestyles2.png) ![More Inline Styles](docs/ui_inlinestyles2.png)
You can also add custom CSS classes to the panel of a control using `setPanelClass`. This allows you to apply arbitrary CSS classes to your controls.
### Disabling Controls ### Disabling Controls
@@ -667,6 +670,56 @@ All the example sketches include the DNS related code and will work as captive p
ESPUI.captivePortal = false; ESPUI.captivePortal = false;
``` ```
### User-defined JavaScript
You can add your own custom JavaScript to the UI. This allows you to create custom controls, handle events, or otherwise extend the functionality of the UI.
To add custom JavaScript, call `ESPUI.setCustomJS()` before `ESPUI.begin()`. The argument to `setCustomJS()` is a C-string containing the JavaScript code. This string must remain valid for the lifetime of the ESPUIClass instance.
```cpp
const char* myCustomJS = "alert('Hello from custom JS!');";
void setup() {
// ...
ESPUI.setCustomJS(myCustomJS);
ESPUI.begin("ESPUI Control");
// ...
}
```
The custom JavaScript is served at `/js/custom.js` and is automatically included in the `index.htm` file.
### User-defined CSS
You can add your own custom CSS to the UI. This allows you to globally style the UI.
To add custom CSS, call `ESPUI.setCustomCSS()` before `ESPUI.begin()`. The argument to `setCustomCSS()` is a C-string containing the CSS code. This string must remain valid for the lifetime of the ESPUIClass instance.
```cpp
const char* myCustomCSS = ".test { color: red; }";
void setup() {
// ...
ESPUI.setCustomCSS(myCustomCSS);
ESPUI.begin("ESPUI Control");
// ...
}
```
The custom CSS is served at `/css/custom.css` and is automatically included in the `index.htm` file.
This can be used in conjunction with `setPanelClass` to apply custom CSS styles to controls. For example:
```cpp
// Make the value span of the panel red on error
ESPUI.setCustomCSS(".err span { color: red; }");
// Set the panel class to 'err' to make its value red
ESPUI.setPanelClass(<id>, "err");
```
For simpler styles without using classes, you can use `setElementStyle` or `setPanelStyle` instead.
# Notes for Development # Notes for Development

View File

@@ -1 +1 @@
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:visible}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:initial}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:visible}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}

View File

@@ -427,6 +427,12 @@ button:enabled:active {
transform: translateX(4px) translateY(4px); transform: translateX(4px) translateY(4px);
} }
/* Pressed state for touch events */
button.pressed {
background-color: #666666 !important;
transform: translateX(4px) translateY(4px);
}
/* Main Head Part /* Main Head Part
*/ */
@@ -594,6 +600,14 @@ hr {
background-color:#777 background-color:#777
} }
/* Pressed state for touch events on pads */
.control:not(.disabled) a.pressed {
background-color: #777 !important;
}
.control:not(.disabled) li.pressed a {
background-color: #777 !important;
}
/* Switch /* Switch
*/ */

File diff suppressed because one or more lines are too long

View File

@@ -10,12 +10,14 @@
/> />
<link rel="stylesheet" href="/css/normalize.css" /> <link rel="stylesheet" href="/css/normalize.css" />
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/custom.css" />
<script src="/js/zepto.min.js"></script> <script src="/js/zepto.min.js"></script>
<script src="/js/slider.js"></script> <script src="/js/slider.js"></script>
<script src="/js/graph.js"></script> <script src="/js/graph.js"></script>
<script src="/js/controls.js"></script> <script src="/js/controls.js"></script>
<script src="/js/tabbedcontent.js"></script> <script src="/js/tabbedcontent.js"></script>
<script src="/js/custom.js"></script>
</head> </head>
<body onload="javascript:start();"> <body onload="javascript:start();">

View File

@@ -1 +1 @@
<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html> <!doctype html><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width,initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><link rel=stylesheet href=/css/custom.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script><script src=/js/custom.js></script><body onload=start()><div><h4><div id=mainHeader>Control</div><span id=conStatus class=label>Offline</span></h4></div><hr><div class=container><div id=row class="row u-full-width"></div><ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul><div id=tabscontent class="tabscontent u-full-width"></div></div>

45
data/js/controls.js vendored
View File

@@ -379,10 +379,12 @@ function start() {
$("#btn" + data.id).on({ $("#btn" + data.id).on({
touchstart: function (e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
$(this).addClass("pressed");
buttonclick(data.id, true); buttonclick(data.id, true);
}, },
touchend: function (e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
$(this).removeClass("pressed");
buttonclick(data.id, false); buttonclick(data.id, false);
}, },
}); });
@@ -403,50 +405,60 @@ function start() {
$("#pf" + data.id).on({ $("#pf" + data.id).on({
touchstart: function (e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().addClass("pressed");
padclick(UP, data.id, true); padclick(UP, data.id, true);
}, },
touchend: function (e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().removeClass("pressed");
padclick(UP, data.id, false); padclick(UP, data.id, false);
}, },
}); });
$("#pl" + data.id).on({ $("#pl" + data.id).on({
touchstart: function (e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().addClass("pressed");
padclick(LEFT, data.id, true); padclick(LEFT, data.id, true);
}, },
touchend: function (e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().removeClass("pressed");
padclick(LEFT, data.id, false); padclick(LEFT, data.id, false);
}, },
}); });
$("#pr" + data.id).on({ $("#pr" + data.id).on({
touchstart: function (e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().addClass("pressed");
padclick(RIGHT, data.id, true); padclick(RIGHT, data.id, true);
}, },
touchend: function (e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().removeClass("pressed");
padclick(RIGHT, data.id, false); padclick(RIGHT, data.id, false);
}, },
}); });
$("#pb" + data.id).on({ $("#pb" + data.id).on({
touchstart: function (e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().addClass("pressed");
padclick(DOWN, data.id, true); padclick(DOWN, data.id, true);
}, },
touchend: function (e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
$(this).parent().removeClass("pressed");
padclick(DOWN, data.id, false); padclick(DOWN, data.id, false);
}, },
}); });
$("#pc" + data.id).on({ $("#pc" + data.id).on({
touchstart: function (e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
$(this).addClass("pressed");
padclick(CENTER, data.id, true); padclick(CENTER, data.id, true);
}, },
touchend: function (e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
$(this).removeClass("pressed");
padclick(CENTER, data.id, false); padclick(CENTER, data.id, false);
}, },
}); });
@@ -464,10 +476,22 @@ function start() {
case UI_TAB: case UI_TAB:
if (data.visible) { if (data.visible) {
$("#tabsnav").append( $("#tabsnav").append(
"<li><a onmouseup='tabclick(" + data.id + ")' href='#tab" + data.id + "'>" + data.value + "</a></li>" "<li><a id='tablink" + data.id + "' href='#tab" + data.id + "'>" + data.value + "</a></li>"
); );
$("#tabscontent").append("<div id='tab" + data.id + "'></div>"); $("#tabscontent").append("<div id='tab" + data.id + "'></div>");
// Add touch and click handlers for tab
// Note: Do NOT use e.preventDefault() in touchend - it blocks the click
// event which tabbedcontent.js needs to actually switch tabs
$("#tablink" + data.id).on({
touchend: function(e) {
tabclick(data.id);
},
mouseup: function(e) {
tabclick(data.id);
}
});
tabs = $(".tabscontent").tabbedContent({ loop: true }).data("api"); tabs = $(".tabscontent").tabbedContent({ loop: true }).data("api");
// switch to tab... // switch to tab...
$("a") $("a")
@@ -768,6 +792,14 @@ function start() {
$("#id" + data.id).hide(); $("#id" + data.id).hide();
} }
if (data.hasOwnProperty('panelClass')) {
var element = $("#id" + data.id);
var baseClass = element.attr("data-base-class");
if (baseClass) {
element.attr("class", baseClass + " " + data.panelClass);
}
}
if (data.type == UPDATE_SLIDER) { if (data.type == UPDATE_SLIDER) {
element.removeClass( element.removeClass(
"slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin" "slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin"
@@ -886,6 +918,9 @@ function selectchange(number) {
} }
function buttonclick(number, isdown) { function buttonclick(number, isdown) {
if ($("#btn" + number).prop("disabled")) {
return;
}
if (isdown) websock.send("bdown:" + number); if (isdown) websock.send("bdown:" + number);
else websock.send("bup:" + number); else websock.send("bup:" + number);
} }
@@ -964,6 +999,7 @@ var rangeSlider = function (isDiscrete) {
var addToHTML = function (data) { var addToHTML = function (data) {
panelStyle = data.hasOwnProperty('panelStyle') ? " style='" + data.panelStyle + "' " : ""; panelStyle = data.hasOwnProperty('panelStyle') ? " style='" + data.panelStyle + "' " : "";
panelClass = data.hasOwnProperty('panelClass') ? " " + data.panelClass + " " : "";
panelwide = data.hasOwnProperty('wide') ? "wide" : ""; panelwide = data.hasOwnProperty('wide') ? "wide" : "";
if (!data.hasOwnProperty('parentControl') || $("#tab" + data.parentControl).length > 0) { if (!data.hasOwnProperty('parentControl') || $("#tab" + data.parentControl).length > 0) {
@@ -987,14 +1023,15 @@ var addToHTML = function (data) {
case UI_GAUGE: case UI_GAUGE:
case UI_ACCEL: case UI_ACCEL:
case UI_FILEDISPLAY: case UI_FILEDISPLAY:
html = "<div id='id" + data.id + "' " + panelStyle + " class='two columns " + panelwide + " card tcenter " + var baseClass = "two columns " + panelwide + " card tcenter " + colorClass(data.color);
colorClass(data.color) + "'><h5>" + data.label + "</h5><hr/>" + html = "<div id='id" + data.id + "' " + panelStyle + " class='" + baseClass + panelClass + "' data-base-class='" + baseClass + "'><h5>" + data.label + "</h5><hr/>" +
elementHTML(data) + elementHTML(data) +
"</div>"; "</div>";
break; break;
case UI_SEPARATOR: case UI_SEPARATOR:
html = "<div id='id" + data.id + "' " + panelStyle + " class='sectionbreak columns'>" + var baseClass = "sectionbreak columns";
html = "<div id='id" + data.id + "' " + panelStyle + " class='" + baseClass + panelClass + "' data-base-class='" + baseClass + "'>" +
"<h5>" + data.label + "</h5><hr/></div>"; "<h5>" + data.label + "</h5><hr/></div>";
break; break;
case UI_TIME: case UI_TIME:

File diff suppressed because one or more lines are too long

View File

@@ -172,7 +172,7 @@ function lineGraph(parent, xAccessor, yAccessor) {
var text = document.createElementNS("http://www.w3.org/2000/svg", "text"); var text = document.createElementNS("http://www.w3.org/2000/svg", "text");
// primitive formatting // primitive formatting
text.innerHTML = Math.floor(xDataValue) + " / " + Math.floor(yDataValue); text.innerHTML = new Date(Math.floor(xDataValue)).toLocaleTimeString() + " / " + Math.floor(yDataValue);
text.setAttribute("x", x); text.setAttribute("x", x);
text.setAttribute("y", y); text.setAttribute("y", y);

16
data/js/graph.min.js vendored
View File

@@ -1,15 +1 @@
function lineGraph(parent,xAccessor,yAccessor){const width=620;const height=420;const gutter=40;const pixelsPerTick=30;function numericTransformer(dataMin,dataMax,pxMin,pxMax){var dataDiff=dataMax-dataMin,pxDiff=pxMax-pxMin,dataRatio=pxDiff/dataDiff,coordRatio=dataDiff/pxDiff;return{toCoord:function(data){return(data-dataMin)*dataRatio+pxMin;},toData:function(coord){return(coord-pxMin)*coordRatio+dataMin;}};} function lineGraph(e,t,n){const o=620,i=420,s=40,a=30;function r(e,t,n,s){var o=t-e,i=s-n,a=i/o,r=o/i;return{toCoord:function(t){return(t-e)*a+n},toData:function(t){return(t-n)*r+e}}}function c(t,n){var r,c,l,d,h,m,p,g,u=document.createElementNS("http://www.w3.org/2000/svg","g"),f=document.createElementNS("http://www.w3.org/2000/svg","path");if(u.setAttribute("class",t+"-axis"),l=s,h=o-s,d=i-s,p=s,t==="x"){f.setAttribute("d","M "+l+" "+d+" L "+h+" "+d);for(r=l;r<=h;r++)(r-l)%(a*3)===0&&r!==l&&(c=document.createElementNS("http://www.w3.org/2000/svg","text"),c.innerHTML=new Date(Math.floor(n(r))).toLocaleTimeString(),c.setAttribute("x",r),c.setAttribute("y",d),c.setAttribute("dy","1em"),u.appendChild(c))}else{f.setAttribute("d","M "+l+" "+d+" L "+l+" "+p);for(r=p;r<=d;r++)(r-d)%a===0&&r!==d&&(m=document.createElementNS("http://www.w3.org/2000/svg","g"),g=document.createElementNS("http://www.w3.org/2000/svg","path"),c=document.createElementNS("http://www.w3.org/2000/svg","text"),c.innerHTML=Math.floor(n(r)),c.setAttribute("x",l),c.setAttribute("y",r),c.setAttribute("dx","-.5em"),c.setAttribute("dy",".3em"),g.setAttribute("d","M "+l+" "+r+" L "+h+" "+r),m.appendChild(g),m.appendChild(c),u.appendChild(m))}u.appendChild(f),e.appendChild(u)}function l(t,n,s,o){var a,i=document.createElementNS("http://www.w3.org/2000/svg","path");if(t.reset(),n.reset(),!t.hasNext()||!n.hasNext())return;for(a="M "+s(t.next())+" "+o(n.next());t.hasNext()&&n.hasNext();)a+=" L "+s(t.next())+" "+o(n.next());i.setAttribute("class","series"),i.setAttribute("d",a),e.appendChild(i)}function d(t,n,s,o){var i,a,c,l,d,u,r=document.createElementNS("http://www.w3.org/2000/svg","g");if(r.setAttribute("class","data-points"),t.reset(),n.reset(),!t.hasNext()||!n.hasNext())return;for(;t.hasNext()&&n.hasNext();)c=t.next(),l=s(c),d=n.next(),u=o(d),a=document.createElementNS("http://www.w3.org/2000/svg","circle"),a.setAttribute("cx",l),a.setAttribute("cy",u),a.setAttribute("r","4"),i=document.createElementNS("http://www.w3.org/2000/svg","text"),i.innerHTML=new Date(Math.floor(c)).toLocaleTimeString()+" / "+Math.floor(d),i.setAttribute("x",l),i.setAttribute("y",u),i.setAttribute("dx","1em"),i.setAttribute("dy","-.7em"),r.appendChild(a),r.appendChild(i);e.appendChild(r)}xTransform=r(t.min(),t.max(),0+s,o-s),yTransform=r(n.min(),n.max(),i-s,0+s),c("x",xTransform.toData),c("y",yTransform.toData),l(t,n,xTransform.toCoord,yTransform.toCoord),d(t,n,xTransform.toCoord,yTransform.toCoord)}function renderGraphSvg(e,t){for(var n,s=document.getElementById(t);s.hasChildNodes();)s.removeChild(s.lastChild);n=document.createElementNS("http://www.w3.org/2000/svg","svg"),n.setAttribute("viewBox","0 0 640 440"),n.setAttribute("preserveAspectRatio","xMidYMid meet"),lineGraph(n,function(e,t,n){var s=0;return{hasNext:function(){return s<e.length},next:function(){return e[s++].x},reset:function(){s=0},min:function(){return t},max:function(){return n}}}(e,Math.min.apply(Math,e.map(function(e){return e.x})),Math.max.apply(Math,e.map(function(e){return e.x}))),function(e,t,n){var s=0;return{hasNext:function(){return s<e.length},next:function(){return e[s++].y},reset:function(){s=0},min:function(){return t},max:function(){return n}}}(e,Math.min.apply(Math,e.map(function(e){return e.y})),Math.max.apply(Math,e.map(function(e){return e.y})))),s.appendChild(n)}
function axisRenderer(orientation,transform){var axisGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var axisPath=document.createElementNS("http://www.w3.org/2000/svg","path");axisGroup.setAttribute("class",orientation+"-axis");var xMin=gutter;var xMax=width-gutter;var yMin=height-gutter;var yMax=gutter;if(orientation==="x"){axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMax+" "+yMin);for(var i=xMin;i<=xMax;i++){if((i-xMin)%(pixelsPerTick*3)===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=new Date(Math.floor(transform(i))).toLocaleTimeString();text.setAttribute("x",i);text.setAttribute("y",yMin);text.setAttribute("dy","1em");axisGroup.appendChild(text);}}}else{axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMin+" "+yMax);for(var i=yMax;i<=yMin;i++){if((i-yMin)%pixelsPerTick===0&&i!==yMin){var tickGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var gridLine=document.createElementNS("http://www.w3.org/2000/svg","path");text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",xMin);text.setAttribute("y",i);text.setAttribute("dx","-.5em");text.setAttribute("dy",".3em");gridLine.setAttribute("d","M "+xMin+" "+i+" L "+xMax+" "+i);tickGroup.appendChild(gridLine);tickGroup.appendChild(text);axisGroup.appendChild(tickGroup);}}}
axisGroup.appendChild(axisPath);parent.appendChild(axisGroup);}
function lineRenderer(xAccessor,yAccessor,xTransform,yTransform){var line=document.createElementNS("http://www.w3.org/2000/svg","path");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+
xTransform(xAccessor.next())+
" "+
yTransform(yAccessor.next());}
line.setAttribute("class","series");line.setAttribute("d",pathString);parent.appendChild(line);}
function pointRenderer(xAccessor,yAccessor,xTransform,yTransform){var pointGroup=document.createElementNS("http://www.w3.org/2000/svg","g");pointGroup.setAttribute("class","data-points");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
while(xAccessor.hasNext()&&yAccessor.hasNext()){var xDataValue=xAccessor.next();var x=xTransform(xDataValue);var yDataValue=yAccessor.next();var y=yTransform(yDataValue);var circle=document.createElementNS("http://www.w3.org/2000/svg","circle");circle.setAttribute("cx",x);circle.setAttribute("cy",y);circle.setAttribute("r","4");var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(xDataValue)+" / "+Math.floor(yDataValue);text.setAttribute("x",x);text.setAttribute("y",y);text.setAttribute("dx","1em");text.setAttribute("dy","-.7em");pointGroup.appendChild(circle);pointGroup.appendChild(text);}
parent.appendChild(pointGroup);}
xTransform=numericTransformer(xAccessor.min(),xAccessor.max(),0+gutter,width-gutter);yTransform=numericTransformer(yAccessor.min(),yAccessor.max(),height-gutter,0+gutter);axisRenderer("x",xTransform.toData);axisRenderer("y",yTransform.toData);lineRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);pointRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);}
function renderGraphSvg(dataArray,renderId){var figure=document.getElementById(renderId);while(figure.hasChildNodes()){figure.removeChild(figure.lastChild);}
var svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 640 440");svg.setAttribute("preserveAspectRatio","xMidYMid meet");lineGraph(svg,(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].x;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.x;})),Math.max.apply(Math,dataArray.map(function(o){return o.x;}))),(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].y;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.y;})),Math.max.apply(Math,dataArray.map(function(o){return o.y;}))));figure.appendChild(svg);}

12
data/js/slider.min.js vendored
View File

@@ -1,11 +1 @@
function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find(".slider");slider_fill=slider.find(".slider-fill");slider_handle=slider.find(".slider-handle");slider_label=slider.find(".slider-label");var range_val=parseInt(range.val());slider_fill.css("width",range_val+"%");slider_handle.css("left",range_val+"%");slider_label.find("span").text(range_val);});self.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2){return false;} function rkmd_rangeSlider(e){var n,s,o,t=$(e),a=t.width(),r=t.offset().left,i=t;i.each(function(){n=$(this),n.append(sliderDiscrete_tmplt()),o=n.find('input[type="range"]'),s=n.find(".slider"),slider_fill=s.find(".slider-fill"),slider_handle=s.find(".slider-handle"),slider_label=s.find(".slider-label");var i=parseInt(o.val());slider_fill.css("width",i+"%"),slider_handle.css("left",i+"%"),slider_label.find("span").text(i)}),t.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2)return!1;var n,s,o,t=$(this).parents(".rkmd-slider"),i=t.width(),a=t.offset().left,r=t.find('input[type="range"]').is(":disabled");if(r===!0)return!1;$(this).addClass("is-active"),n=function(e){var s=e.pageX||e.changedTouches[0].pageX,n=s-a;n<=i&&!(n<"0")&&slider_move(t,n,i,!0)},s=function(){$(this).off(o),t.find(".is-active").removeClass("is-active")},o={mousemove:n,touchmove:n,mouseup:s,touchend:s},$(document).on(o)}),t.on("mousedown touchstart",".slider",function(e){if(e.button===2)return!1;var n,s,o,t=$(this).parents(".rkmd-slider"),i=t.width(),a=t.offset().left,r=t.find('input[type="range"]').is(":disabled");if(r===!0)return!1;n=e.pageX-a,n<=i&&!(n<"0")&&slider_move(t,n,i,!0),s=function(){$(this).off(o)},o={mouseup:s,touchend:s},$(document).on(o)})}function sliderDiscrete_tmplt(){var e='<div class="slider"><div class="slider-fill"></div><div class="slider-handle"><div class="slider-label"><span>0</span></div></div></div>';return e}function slider_move(e,t,n,s){var a,o=parseInt(Math.round(t/n*100)),r=e.find(".slider-fill"),c=e.find(".slider-handle"),i=e.find('input[type="range"]');i.next().html(t),r.css("width",o+"%"),c.css({left:o+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none"}),i.val(o),e.find(".slider-handle span").text()!=o&&(e.find(".slider-handle span").text(o),a=e.attr("id").substring(2),s&&websock.send("slvalue:"+o+":"+a))}
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
$(this).addClass("is-active");var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find(".is-active").removeClass("is-active");};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu,};$(document).on(handlers);});self.on("mousedown touchstart",".slider",function(e){if(e.button===2){return false;}
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}
var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu,};$(document).on(handlers);});}
function sliderDiscrete_tmplt(){var tmplt='<div class="slider">'+
'<div class="slider-fill"></div>'+
'<div class="slider-handle"><div class="slider-label"><span>0</span></div></div>'+
"</div>";return tmplt;}
function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round((newW/sliderW)*100));var slider_fill=parents.find(".slider-fill");var slider_handle=parents.find(".slider-handle");var range=parents.find('input[type="range"]');range.next().html(newW);slider_fill.css("width",slider_new_val+"%");slider_handle.css({left:slider_new_val+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none",});range.val(slider_new_val);if(parents.find(".slider-handle span").text()!=slider_new_val){parents.find(".slider-handle span").text(slider_new_val);var number=parents.attr("id").substring(2);if(send)websock.send("slvalue:"+slider_new_val+":"+number);}}

View File

@@ -1,35 +1 @@
;(function($,document,window,undefined){"use strict";var Tabbedcontent=function(tabcontent,options){var defaults={links:tabcontent.prev().find('a').length?tabcontent.prev().find('a'):'.tabs a',errorSelector:'.error-message',speed:false,onSwitch:false,onInit:false,currentClass:'active',tabErrorClass:'has-errors',history:true,historyOnInit:true,loop:false},firstTime=false,children=tabcontent.children(),history=window.history,loc=document.location,current=null;options=$.extend(defaults,options);if(!(options.links instanceof $)){options.links=$(options.links);} (function(e,t,n,s){"use strict";var o=function(o,i){var y={links:o.prev().find("a").length?o.prev().find("a"):".tabs a",errorSelector:".error-message",speed:!1,onSwitch:!1,onInit:!1,currentClass:"active",tabErrorClass:"has-errors",history:!0,historyOnInit:!0,loop:!1},p=!1,a=o.children(),l=n.history,d=t.location,c=null;i=e.extend(y,i),i.links instanceof e||(i.links=e(i.links));function j(e){return Boolean(a.filter(e).length)}function O(){return c===0}function E(e){return e%1===0}function x(){return c===a.length-1}function h(t){return e(this).attr("href").match(new RegExp(t+"$"))}function m(t){return t instanceof e?{tab:t,link:i.links.eq(t.index())}:E(t)?{tab:a.eq(t),link:i.links.eq(t)}:a.filter(t).length?{tab:a.filter(t),link:i.links.filter(function(){return h.apply(this,[t])})}:{tab:a.filter("#"+t),link:i.links.filter(function(){return h.apply(this,["#"+t])})}}function b(){return i.links.parent().filter("."+i.currentClass).index()}function w(e){return++c,e===s&&(e=i.loop),c<a.length?r(c,!0):!!(e&&c>=a.length)&&r(0,!0)}function _(e){return--c,e===s&&(e=i.loop),c>=0?r(c,!0):!!(e&&c<0)&&r(a.length-1,!0)}function f(e){i.history&&i.historyOnInit&&p&&l!==s&&"pushState"in l&&(p=!1,n.setTimeout(function(){l.replaceState(null,"",e)},100)),c=b(),i.onSwitch&&typeof i.onSwitch=="function"&&i.onSwitch(e,u()),o.trigger("tabcontent.switch",[e,u()])}function r(e,t){return e.toString().match(/^#/)||(e="#"+m(e).tab.attr("id")),!!j(e)&&(i.links.attr("aria-selected","false").parent().removeClass(i.currentClass),i.links.filter(function(){return h.apply(this,[e])}).attr("aria-selected","true").parent().addClass(i.currentClass),a.hide(),i.history&&t&&(l!==s&&"pushState"in l?l.pushState(null,"",e):n.location.hash=e),a.attr("aria-hidden","true").filter(e).show(i.speed,function(){i.speed&&f(e)}).attr("aria-hidden","false"),i.speed||f(e),!0)}function v(e){return r(e,!0)}function g(){r(d.hash)}function C(){if(j(d.hash)?r(d.hash):i.links.parent().filter("."+i.currentClass).length?r(i.links.parent().filter("."+i.currentClass).index()):i.errorSelector&&a.find(i.errorSelector).length?a.each(function(){if(e(this).find(i.errorSelector).length)return r("#"+e(this).attr("id")),!1}):r("#"+a.filter(":first-child").attr("id")),i.errorSelector&&a.find(i.errorSelector).each(function(){var t=m(e(this).parent());t.link.parent().addClass(i.tabErrorClass)}),"onhashchange"in n)e(n).bind("hashchange",g);else{var t=d.href;n.setInterval(function(){t!==d.href&&(g.call(n.event),t=d.href)},100)}e(i.links).on("click",function(t){r(e(this).attr("href").replace(/^[^#]+/,""),i.history),t.preventDefault()}),i.onInit&&typeof i.onInit=="function"&&i.onInit(u()),o.trigger("tabcontent.init",[u()])}function u(){return{switch:v,switchTab:v,getCurrent:b,getTab:m,next:w,prev:_,isFirst:O,isLast:x}}return C(),u()};e.fn.tabbedContent=function(t){return this.each(function(){var n=new o(e(this),t);e(this).data("api",n)})}})(window.jQuery||window.Zepto||window.$,document,window)
function tabExists(tab){return Boolean(children.filter(tab).length);}
function isFirst(){return current===0;}
function isInt(num){return num%1===0;}
function isLast(){return current===children.length-1;}
function filterTab(tab){return $(this).attr('href').match(new RegExp(tab+'$'));}
function getTab(tab){if(tab instanceof $){return{tab:tab,link:options.links.eq(tab.index())};}
if(isInt(tab)){return{tab:children.eq(tab),link:options.links.eq(tab)};}
if(children.filter(tab).length){return{tab:children.filter(tab),link:options.links.filter(function(){return filterTab.apply(this,[tab]);})};}
return{tab:children.filter('#'+tab),link:options.links.filter(function(){return filterTab.apply(this,['#'+tab]);})};}
function getCurrent(){return options.links.parent().filter('.'+options.currentClass).index();}
function next(loop){++current;if(loop===undefined)loop=options.loop;if(current<children.length){return switchTab(current,true);}else if(loop&&current>=children.length){return switchTab(0,true);}
return false;}
function prev(loop){--current;if(loop===undefined)loop=options.loop;if(current>=0){return switchTab(current,true);}else if(loop&&current<0){return switchTab(children.length-1,true);}
return false;}
function onSwitch(tab){if(options.history&&options.historyOnInit&&firstTime&&history!==undefined&&('pushState'in history)){firstTime=false;window.setTimeout(function(){history.replaceState(null,'',tab);},100);}
current=getCurrent();if(options.onSwitch&&typeof options.onSwitch==='function'){options.onSwitch(tab,api());}
tabcontent.trigger('tabcontent.switch',[tab,api()]);}
function switchTab(tab,api){if(!tab.toString().match(/^#/)){tab='#'+getTab(tab).tab.attr('id');}
if(!tabExists(tab)){return false;}
options.links.attr('aria-selected','false').parent().removeClass(options.currentClass);options.links.filter(function(){return filterTab.apply(this,[tab]);}).attr('aria-selected','true').parent().addClass(options.currentClass);children.hide();if(options.history&&api){if(history!==undefined&&('pushState'in history)){history.pushState(null,'',tab);}else{window.location.hash=tab;}}
children.attr('aria-hidden','true').filter(tab).show(options.speed,function(){if(options.speed){onSwitch(tab);}}).attr('aria-hidden','false');if(!options.speed){onSwitch(tab);}
return true;}
function apiSwitch(tab){return switchTab(tab,true);}
function hashSwitch(e){switchTab(loc.hash);}
function init(){if(tabExists(loc.hash)){switchTab(loc.hash);}
else if(options.links.parent().filter('.'+options.currentClass).length){switchTab(options.links.parent().filter('.'+options.currentClass).index());}
else if(options.errorSelector&&children.find(options.errorSelector).length){children.each(function(){if($(this).find(options.errorSelector).length){switchTab("#"+$(this).attr("id"));return false;}});}
else{switchTab("#"+children.filter(":first-child").attr("id"));}
if(options.errorSelector){children.find(options.errorSelector).each(function(){var tab=getTab($(this).parent());tab.link.parent().addClass(options.tabErrorClass);});}
if('onhashchange'in window){$(window).bind('hashchange',hashSwitch);}else{var current_href=loc.href;window.setInterval(function(){if(current_href!==loc.href){hashSwitch.call(window.event);current_href=loc.href;}},100);}
$(options.links).on('click',function(e){switchTab($(this).attr('href').replace(/^[^#]+/,''),options.history);e.preventDefault();});if(options.onInit&&typeof options.onInit==='function'){options.onInit(api());}
tabcontent.trigger('tabcontent.init',[api()]);}
function api(){return{'switch':apiSwitch,'switchTab':apiSwitch,'getCurrent':getCurrent,'getTab':getTab,'next':next,'prev':prev,'isFirst':isFirst,'isLast':isLast};}
init();return api();};$.fn.tabbedContent=function(options){return this.each(function(){var tabs=new Tabbedcontent($(this),options);$(this).data('api',tabs);});};})(window.jQuery||window.Zepto||window.$,document,window);

View File

@@ -16,8 +16,8 @@
], ],
"dependencies": [ "dependencies": [
{ {
"name": "ESP Async WebServer", "name": "ESPAsyncWebServer",
"authors": "Hristo Gochkov", "authors": "ESP32Async",
"frameworks": "arduino" "frameworks": "arduino"
}, },
{ {

View File

@@ -0,0 +1,43 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = ./src
data_dir = ../../data
[env]
framework = arduino
board_build.filesystem = littlefs
lib_extra_dirs = ../../
lib_deps =
; bblanchon/ArduinoJson @ ^6.18.5
bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bmedici/ESPAsyncWebServer ; Use a fork of the library that has a bugfix for the compile.... https://github.com/esphome/ESPAsyncWebServer/pull/17
lib_ignore =
ESP Async WebServer ; force the use of the esphome version
AsyncTCP ; force the use of the esphome version
LittleFS_esp32 ; force the use of the ESP32 built into the core version
[env:esp8266]
platform = espressif8266
board = nodemcuv2
monitor_speed = 115200
[env:esp32]
platform = espressif32
board = esp32dev
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout
lib_deps =
${env.lib_deps}
me-no-dev/AsyncTCP
monitor_speed = 115200

View File

@@ -0,0 +1,544 @@
/**
* @file completeExample.cpp
* @author Ian Gray @iangray1000
*
* This is an example GUI to show off all of the features of ESPUI.
* This can be built using the Arduino IDE, or PlatformIO.
*
* ---------------------------------------------------------------------------------------
* If you just want to see examples of the ESPUI code, jump down to the setUpUI() function
* ---------------------------------------------------------------------------------------
*
* When this program boots, it will load an SSID and password from the EEPROM.
* The SSID is a null-terminated C string stored at EEPROM addresses 0-31
* The password is a null-terminated C string stored at EEPROM addresses 32-95.
* If these credentials do not work for some reason, the ESP will create an Access
* Point wifi with the SSID HOSTNAME (defined below). You can then connect and use
* the controls on the "Wifi Credentials" tab to store credentials into the EEPROM.
*
*/
#include <Arduino.h>
#include <EEPROM.h>
#include <ESPUI.h>
#if defined(ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#endif
//Settings
#define SLOW_BOOT 0
#define HOSTNAME "ESPUITest"
#define FORCE_USE_HOTSPOT 0
//Function Prototypes
void connectWifi();
void setUpUI();
void enterWifiDetailsCallback(Control *sender, int type);
void textCallback(Control *sender, int type);
void generalCallback(Control *sender, int type);
void scrambleCallback(Control *sender, int type);
void styleCallback(Control *sender, int type);
void updateCallback(Control *sender, int type);
void getTimeCallback(Control *sender, int type);
void graphAddCallback(Control *sender, int type);
void graphClearCallback(Control *sender, int type);
void randomString(char *buf, int len);
void extendedCallback(Control* sender, int type, void* param);
//UI handles
uint16_t wifi_ssid_text, wifi_pass_text;
uint16_t mainLabel, mainSwitcher, mainSlider, mainText, mainNumber, mainScrambleButton, mainTime;
uint16_t styleButton, styleLabel, styleSwitcher, styleSlider, styleButton2, styleLabel2, styleSlider2;
uint16_t graph;
volatile bool updates = false;
// This is the main function which builds our GUI
void setUpUI() {
//Turn off verbose debugging
ESPUI.setVerbosity(Verbosity::Quiet);
//Make sliders continually report their position as they are being dragged.
ESPUI.sliderContinuous = true;
//This GUI is going to be a tabbed GUI, so we are adding most controls using ESPUI.addControl
//which allows us to set a parent control. If we didn't need tabs we could use the simpler add
//functions like:
// ESPUI.button()
// ESPUI.label()
/*
* Tab: Basic Controls
* This tab contains all the basic ESPUI controls, and shows how to read and update them at runtime.
*-----------------------------------------------------------------------------------------------------------*/
auto maintab = ESPUI.addControl(Tab, "", "Basic controls");
ESPUI.addControl(Separator, "General controls", "", None, maintab);
ESPUI.addControl(Button, "Button", "Button 1", Alizarin, maintab, extendedCallback, (void*)19);
mainLabel = ESPUI.addControl(Label, "Label", "Label text", Emerald, maintab, generalCallback);
mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback);
//Sliders default to being 0 to 100, but if you want different limits you can add a Min and Max control
mainSlider = ESPUI.addControl(Slider, "Slider", "200", Turquoise, maintab, generalCallback);
ESPUI.addControl(Min, "", "10", None, mainSlider);
ESPUI.addControl(Max, "", "400", None, mainSlider);
//These are the values for the selector's options. (Note that they *must* be declared static
//so that the storage is allocated in global memory and not just on the stack of this function.)
static String optionValues[] {"Value 1", "Value 2", "Value 3", "Value 4", "Value 5"};
auto mainselector = ESPUI.addControl(Select, "Selector", "Selector", Wetasphalt, maintab, generalCallback);
for(auto const& v : optionValues) {
ESPUI.addControl(Option, v.c_str(), v, None, mainselector);
}
mainText = ESPUI.addControl(Text, "Text Input", "Initial value", Alizarin, maintab, generalCallback);
//Number inputs also accept Min and Max components, but you should still validate the values.
mainNumber = ESPUI.addControl(Number, "Number Input", "42", Emerald, maintab, generalCallback);
ESPUI.addControl(Min, "", "10", None, mainNumber);
ESPUI.addControl(Max, "", "50", None, mainNumber);
ESPUI.addControl(Separator, "Updates", "", None, maintab);
//This button will update all the updatable controls on this tab to random values
mainScrambleButton = ESPUI.addControl(Button, "Scramble Values", "Scramble Values", Carrot, maintab, scrambleCallback);
ESPUI.addControl(Switcher, "Constant updates", "0", Carrot, maintab, updateCallback);
mainTime = ESPUI.addControl(Time, "", "", None, 0, generalCallback);
ESPUI.addControl(Button, "Get Time", "Get Time", Carrot, maintab, getTimeCallback);
ESPUI.addControl(Separator, "Control Pads", "", None, maintab);
ESPUI.addControl(Pad, "Normal", "", Peterriver, maintab, generalCallback);
ESPUI.addControl(PadWithCenter, "With center", "", Peterriver, maintab, generalCallback);
/*
* Tab: Colours
* This tab shows all the basic colours
*-----------------------------------------------------------------------------------------------------------*/
auto colourtab = ESPUI.addControl(Tab, "", "Colours");
ESPUI.addControl(Button, "Alizarin", "Alizarin", Alizarin, colourtab, generalCallback);
ESPUI.addControl(Button, "Turquoise", "Turquoise", Turquoise, colourtab, generalCallback);
ESPUI.addControl(Button, "Emerald", "Emerald", Emerald, colourtab, generalCallback);
ESPUI.addControl(Button, "Peterriver", "Peterriver", Peterriver, colourtab, generalCallback);
ESPUI.addControl(Button, "Wetasphalt", "Wetasphalt", Wetasphalt, colourtab, generalCallback);
ESPUI.addControl(Button, "Sunflower", "Sunflower", Sunflower, colourtab, generalCallback);
ESPUI.addControl(Button, "Carrot", "Carrot", Carrot, colourtab, generalCallback);
ESPUI.addControl(Button, "Dark", "Dark", Dark, colourtab, generalCallback);
/*
* Tab: Styled controls
* This tab shows off how inline CSS styles can be applied to elements and panels in order
* to customise the look of the UI.
*-----------------------------------------------------------------------------------------------------------*/
auto styletab = ESPUI.addControl(Tab, "", "Styled controls");
styleButton = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback);
styleLabel = ESPUI.addControl(Label, "Styled Label", "This is a label", Alizarin, styletab, generalCallback);
styleSwitcher = ESPUI.addControl(Switcher, "Styled Switcher", "1", Alizarin, styletab, generalCallback);
styleSlider = ESPUI.addControl(Slider, "Styled Slider", "0", Alizarin, styletab, generalCallback);
//This button will randomise the colours of the above controls to show updating of inline styles
ESPUI.addControl(Button, "Randomise Colours", "Randomise Colours", Sunflower, styletab, styleCallback);
ESPUI.addControl(Separator, "Other styling examples", "", None, styletab);
styleButton2 = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback);
ESPUI.setPanelStyle(styleButton2, "background: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%); border-bottom: #555;");
ESPUI.setElementStyle(styleButton2, "border-radius: 2em; border: 3px solid black; width: 30%; background-color: #8df;");
styleSlider2 = ESPUI.addControl(Slider, "Styled Slider", "0", Dark, styletab, generalCallback);
ESPUI.setElementStyle(styleSlider2, "background: linear-gradient(to right, red, orange, yellow, green, blue);");
styleLabel2 = ESPUI.addControl(Label, "Styled Label", "This is a label", Dark, styletab, generalCallback);
ESPUI.setElementStyle(styleLabel2, "text-shadow: 3px 3px #74b1ff, 6px 6px #c64ad7; font-size: 60px; font-variant-caps: small-caps; background-color: unset; color: #c4f0bb; -webkit-text-stroke: 1px black;");
/*
* Tab: Grouped controls
* This tab shows how multiple control can be grouped into the same panel through the use of the
* parentControl value. This also shows how to add labels to grouped controls, and how to use vertical controls.
*-----------------------------------------------------------------------------------------------------------*/
auto grouptab = ESPUI.addControl(Tab, "", "Grouped controls");
//The parent of this button is a tab, so it will create a new panel with one control.
auto groupbutton = ESPUI.addControl(Button, "Button Group", "Button A", Dark, grouptab, generalCallback);
//However the parent of this button is another control, so therefore no new panel is
//created and the button is added to the existing panel.
ESPUI.addControl(Button, "", "Button B", Alizarin, groupbutton, generalCallback);
ESPUI.addControl(Button, "", "Button C", Alizarin, groupbutton, generalCallback);
//Sliders can be grouped as well
//To label each slider in the group, we are going add additional labels and give them custom CSS styles
//We need this CSS style rule, which will remove the label's background and ensure that it takes up the entire width of the panel
String clearLabelStyle = "background-color: unset; width: 100%;";
//First we add the main slider to create a panel
auto groupsliders = ESPUI.addControl(Slider, "Slider Group", "10", Dark, grouptab, generalCallback);
//Then we add a label and set its style to the clearLabelStyle. Here we've just given it the name "A"
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupsliders), clearLabelStyle);
//We can now continue to add additional sliders and labels
ESPUI.addControl(Slider, "", "20", None, groupsliders, generalCallback);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupsliders), clearLabelStyle);
ESPUI.addControl(Slider, "", "30", None, groupsliders, generalCallback);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupsliders), clearLabelStyle);
//We can also usefully group switchers.
auto groupswitcher = ESPUI.addControl(Switcher, "Switcher Group", "0", Dark, grouptab, generalCallback);
ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback);
ESPUI.addControl(Switcher, "", "0", Sunflower, groupswitcher, generalCallback);
ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback);
//To label these switchers we need to first go onto a "new line" below the line of switchers
//To do this we add an empty label set to be clear and full width (with our clearLabelStyle)
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, groupswitcher), clearLabelStyle);
//We will now need another label style. This one sets its width to the same as a switcher (and turns off the background)
String switcherLabelStyle = "width: 60px; margin-left: .3rem; margin-right: .3rem; background-color: unset;";
//We can now just add the styled labels.
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, groupswitcher), switcherLabelStyle);
//You can mix and match different control types, but the results might sometimes
//need additional styling to lay out nicely.
auto grouplabel = ESPUI.addControl(Label, "Mixed Group", "Main label", Dark, grouptab);
auto grouplabel2 = ESPUI.addControl(Label, "", "Secondary label", Emerald, grouplabel);
ESPUI.addControl(Button, "", "Button D", Alizarin, grouplabel, generalCallback);
ESPUI.addControl(Switcher, "", "1", Sunflower, grouplabel, generalCallback);
ESPUI.setElementStyle(grouplabel2, "font-size: x-large; font-family: serif;");
//Some controls can even support vertical orientation, currently Switchers and Sliders
ESPUI.addControl(Separator, "Vertical controls", "", None, grouptab);
auto vertgroupswitcher = ESPUI.addControl(Switcher, "Vertical Switcher Group", "0", Dark, grouptab, generalCallback);
ESPUI.setVertical(vertgroupswitcher);
//On the following lines we wrap the value returned from addControl and send it straight to setVertical
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
//The mechanism for labelling vertical switchers is the same as we used above for horizontal ones
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupswitcher), clearLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupswitcher), switcherLabelStyle);
auto vertgroupslider = ESPUI.addControl(Slider, "Vertical Slider Group", "15", Dark, grouptab, generalCallback);
ESPUI.setVertical(vertgroupslider);
ESPUI.setVertical(ESPUI.addControl(Slider, "", "25", None, vertgroupslider, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Slider, "", "35", None, vertgroupslider, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Slider, "", "45", None, vertgroupslider, generalCallback));
//The mechanism for labelling vertical sliders is the same as we used above for switchers
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupslider), clearLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupslider), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupslider), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupslider), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupslider), switcherLabelStyle);
//Note that combining vertical and horizontal sliders is going to result in very messy layout!
/*
* Tab: Example UI
* An example UI for the documentation
*-----------------------------------------------------------------------------------------------------------*/
auto exampletab = ESPUI.addControl(Tab, "Example", "Example");
ESPUI.addControl(Separator, "Control and Status", "", None, exampletab);
ESPUI.addControl(Switcher, "Power", "1", Alizarin, exampletab, generalCallback);
ESPUI.addControl(Label, "Status", "System status: OK", Wetasphalt, exampletab, generalCallback);
ESPUI.addControl(Separator, "Settings", "", None, exampletab);
ESPUI.addControl(PadWithCenter, "Attitude Control", "", Dark, exampletab, generalCallback);
auto examplegroup1 = ESPUI.addControl(Button, "Activate Features", "Feature A", Carrot, exampletab, generalCallback);
ESPUI.addControl(Button, "Activate Features", "Feature B", Carrot, examplegroup1, generalCallback);
ESPUI.addControl(Button, "Activate Features", "Feature C", Carrot, examplegroup1, generalCallback);
ESPUI.addControl(Slider, "Value control", "45", Peterriver, exampletab, generalCallback);
/*
* Tab: WiFi Credentials
* You use this tab to enter the SSID and password of a wifi network to autoconnect to.
*-----------------------------------------------------------------------------------------------------------*/
auto wifitab = ESPUI.addControl(Tab, "", "WiFi Credentials");
wifi_ssid_text = ESPUI.addControl(Text, "SSID", "", Alizarin, wifitab, textCallback);
//Note that adding a "Max" control to a text control sets the max length
ESPUI.addControl(Max, "", "32", None, wifi_ssid_text);
wifi_pass_text = ESPUI.addControl(Text, "Password", "", Alizarin, wifitab, textCallback);
ESPUI.addControl(Max, "", "64", None, wifi_pass_text);
ESPUI.addControl(Button, "Save", "Save", Peterriver, wifitab, enterWifiDetailsCallback);
//Finally, start up the UI.
//This should only be called once we are connected to WiFi.
ESPUI.begin(HOSTNAME);
}
//This callback generates and applies inline styles to a bunch of controls to change their colour.
//The styles created are of the form:
// "border-bottom: #999 3px solid; background-color: #aabbcc;"
// "background-color: #aabbcc;"
void styleCallback(Control *sender, int type) {
//Declare space for style strings. These have to be static so that they are always available
//to the websocket layer. If we'd not made them static they'd be allocated on the heap and
//will be unavailable when we leave this function.
static char stylecol1[60], stylecol2[30];
if(type == B_UP) {
//Generate two random HTML hex colour codes, and print them into CSS style rules
sprintf(stylecol1, "border-bottom: #999 3px solid; background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
sprintf(stylecol2, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
//Apply those styles to various elements to show how controls react to styling
ESPUI.setPanelStyle(styleButton, stylecol1);
ESPUI.setElementStyle(styleButton, stylecol2);
ESPUI.setPanelStyle(styleLabel, stylecol1);
ESPUI.setElementStyle(styleLabel, stylecol2);
ESPUI.setPanelStyle(styleSwitcher, stylecol1);
ESPUI.setElementStyle(styleSwitcher, stylecol2);
ESPUI.setPanelStyle(styleSlider, stylecol1);
ESPUI.setElementStyle(styleSlider, stylecol2);
}
}
//This callback updates the "values" of a bunch of controls
void scrambleCallback(Control *sender, int type) {
static char rndString1[10];
static char rndString2[20];
static bool scText = false;
if(type == B_UP) { //Button callbacks generate events for both UP and DOWN.
//Generate some random text
randomString(rndString1, 10);
randomString(rndString2, 20);
//Set the various controls to random value to show how controls can be updated at runtime
ESPUI.updateLabel(mainLabel, String(rndString1));
ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true);
ESPUI.updateSlider(mainSlider, random(10, 400));
ESPUI.updateText(mainText, String(rndString2));
ESPUI.updateNumber(mainNumber, random(100000));
ESPUI.updateButton(mainScrambleButton, scText ? "Scrambled!" : "Scrambled.");
scText = !scText;
}
}
void updateCallback(Control *sender, int type) {
updates = (sender->value.toInt() > 0);
}
void getTimeCallback(Control *sender, int type) {
if(type == B_UP) {
ESPUI.updateTime(mainTime);
}
}
void graphAddCallback(Control *sender, int type) {
if(type == B_UP) {
ESPUI.addGraphPoint(graph, random(1, 50));
}
}
void graphClearCallback(Control *sender, int type) {
if(type == B_UP) {
ESPUI.clearGraph(graph);
}
}
//Most elements in this test UI are assigned this generic callback which prints some
//basic information. Event types are defined in ESPUI.h
void generalCallback(Control *sender, int type) {
Serial.print("CB: id(");
Serial.print(sender->id);
Serial.print(") Type(");
Serial.print(type);
Serial.print(") '");
Serial.print(sender->label);
Serial.print("' = ");
Serial.println(sender->value);
}
// Most elements in this test UI are assigned this generic callback which prints some
// basic information. Event types are defined in ESPUI.h
// The extended param can be used to hold a pointer to additional information
// or for C++ it can be used to return a this pointer for quick access
// using a lambda function
void extendedCallback(Control* sender, int type, void* param)
{
Serial.print("CB: id(");
Serial.print(sender->id);
Serial.print(") Type(");
Serial.print(type);
Serial.print(") '");
Serial.print(sender->label);
Serial.print("' = ");
Serial.println(sender->value);
Serial.print("param = ");
Serial.println((long)param);
}
void setup() {
randomSeed(0);
Serial.begin(115200);
while(!Serial);
if(SLOW_BOOT) delay(5000); //Delay booting to give time to connect a serial monitor
connectWifi();
#if defined(ESP32)
WiFi.setSleep(false); //For the ESP32: turn off sleeping to increase UI responsivness (at the cost of power use)
#endif
setUpUI();
}
void loop() {
static long unsigned lastTime = 0;
//Send periodic updates if switcher is turned on
if(updates && millis() > lastTime + 500) {
static uint16_t sliderVal = 10;
//Flick this switcher on and off
ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true);
sliderVal += 10;
if(sliderVal > 400) sliderVal = 10;
//Sliders, numbers, and labels can all be updated at will
ESPUI.updateSlider(mainSlider, sliderVal);
ESPUI.updateNumber(mainNumber, random(100000));
ESPUI.updateLabel(mainLabel, String(sliderVal));
lastTime = millis();
}
//Simple debug UART interface
if(Serial.available()) {
switch(Serial.read()) {
case 'w': //Print IP details
Serial.println(WiFi.localIP());
break;
case 'W': //Reconnect wifi
connectWifi();
break;
case 'C': //Force a crash (for testing exception decoder)
#if !defined(ESP32)
((void (*)())0xf00fdead)();
#endif
break;
default:
Serial.print('#');
break;
}
}
#if !defined(ESP32)
//We don't need to call this explicitly on ESP32 but we do on 8266
MDNS.update();
#endif
}
//Utilities
//
//If you are here just to see examples of how to use ESPUI, you can ignore the following functions
//------------------------------------------------------------------------------------------------
void readStringFromEEPROM(String& buf, int baseaddress, int size) {
buf.reserve(size);
for (int i = baseaddress; i < baseaddress+size; i++) {
char c = EEPROM.read(i);
buf += c;
if(!c) break;
}
}
void connectWifi() {
int connect_timeout;
#if defined(ESP32)
WiFi.setHostname(HOSTNAME);
#else
WiFi.hostname(HOSTNAME);
#endif
Serial.println("Begin wifi...");
//Load credentials from EEPROM
if(!(FORCE_USE_HOTSPOT)) {
yield();
EEPROM.begin(100);
String stored_ssid, stored_pass;
readStringFromEEPROM(stored_ssid, 0, 32);
readStringFromEEPROM(stored_pass, 32, 96);
EEPROM.end();
//Try to connect with stored credentials, fire up an access point if they don't work.
#if defined(ESP32)
WiFi.begin(stored_ssid.c_str(), stored_pass.c_str());
#else
WiFi.begin(stored_ssid, stored_pass);
#endif
connect_timeout = 28; //7 seconds
while (WiFi.status() != WL_CONNECTED && connect_timeout > 0) {
delay(250);
Serial.print(".");
connect_timeout--;
}
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(WiFi.localIP());
Serial.println("Wifi started");
if (!MDNS.begin(HOSTNAME)) {
Serial.println("Error setting up MDNS responder!");
}
} else {
Serial.println("\nCreating access point...");
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
WiFi.softAP(HOSTNAME);
connect_timeout = 20;
do {
delay(250);
Serial.print(",");
connect_timeout--;
} while(connect_timeout);
}
}
void enterWifiDetailsCallback(Control *sender, int type) {
if(type == B_UP) {
Serial.println("Saving credentials to EPROM...");
Serial.println(ESPUI.getControl(wifi_ssid_text)->value);
Serial.println(ESPUI.getControl(wifi_pass_text)->value);
unsigned int i;
EEPROM.begin(100);
for(i = 0; i < ESPUI.getControl(wifi_ssid_text)->value.length(); i++) {
EEPROM.write(i, ESPUI.getControl(wifi_ssid_text)->value.charAt(i));
if(i==30) break; //Even though we provided a max length, user input should never be trusted
}
EEPROM.write(i, '\0');
for(i = 0; i < ESPUI.getControl(wifi_pass_text)->value.length(); i++) {
EEPROM.write(i + 32, ESPUI.getControl(wifi_pass_text)->value.charAt(i));
if(i==94) break; //Even though we provided a max length, user input should never be trusted
}
EEPROM.write(i + 32, '\0');
EEPROM.end();
}
}
void textCallback(Control *sender, int type) {
//This callback is needed to handle the changed values, even though it doesn't do anything itself.
}
void randomString(char *buf, int len) {
for(auto i = 0; i < len-1; i++)
buf[i] = random(0, 26) + 'A';
buf[len-1] = '\0';
}

View File

@@ -0,0 +1,43 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = ./src
data_dir = ../../data
[env]
framework = arduino
board_build.filesystem = littlefs
lib_extra_dirs = ../../
lib_deps =
; bblanchon/ArduinoJson @ ^6.18.5
bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bmedici/ESPAsyncWebServer ; Use a fork of the library that has a bugfix for the compile.... https://github.com/esphome/ESPAsyncWebServer/pull/17
lib_ignore =
ESP Async WebServer ; force the use of the esphome version
AsyncTCP ; force the use of the esphome version
LittleFS_esp32 ; force the use of the ESP32 built into the core version
[env:esp8266]
platform = espressif8266
board = nodemcuv2
monitor_speed = 115200
[env:esp32]
platform = espressif32
board = esp32dev
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout
lib_deps =
${env.lib_deps}
me-no-dev/AsyncTCP
monitor_speed = 115200

View File

@@ -0,0 +1,524 @@
/**
* @file completeLambda.cpp
* @author Ian Gray @iangray1000
*
* This is an example GUI to show off all of the features of ESPUI.
* This can be built using the Arduino IDE, or PlatformIO.
*
* ---------------------------------------------------------------------------------------
* If you just want to see examples of the ESPUI code, jump down to the setUpUI() function
* ---------------------------------------------------------------------------------------
*
* When this program boots, it will load an SSID and password from the EEPROM.
* The SSID is a null-terminated C string stored at EEPROM addresses 0-31
* The password is a null-terminated C string stored at EEPROM addresses 32-95.
* If these credentials do not work for some reason, the ESP will create an Access
* Point wifi with the SSID HOSTNAME (defined below). You can then connect and use
* the controls on the "Wifi Credentials" tab to store credentials into the EEPROM.
*
* Version with lambdas. Comparing to version with only callbacks:
* diff -u ../completeExample/completeExample.cpp completeLambda.cpp|less
*
*/
#include <Arduino.h>
#include <EEPROM.h>
#include <ESPUI.h>
#if defined(ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#endif
//Settings
#define SLOW_BOOT 0
#define HOSTNAME "ESPUITest"
#define FORCE_USE_HOTSPOT 0
//Function Prototypes
void connectWifi();
void setUpUI();
void textCallback(Control *sender, int type);
void generalCallback(Control *sender, int type);
void randomString(char *buf, int len);
void paramCallback(Control* sender, int type, int param);
//UI handles
uint16_t wifi_ssid_text, wifi_pass_text;
uint16_t mainLabel, mainSwitcher, mainSlider, mainText, mainNumber, mainScrambleButton, mainTime;
uint16_t styleButton, styleLabel, styleSwitcher, styleSlider, styleButton2, styleLabel2, styleSlider2;
uint16_t graph;
volatile bool updates = false;
// This is the main function which builds our GUI
void setUpUI() {
//Turn off verbose debugging
ESPUI.setVerbosity(Verbosity::Quiet);
//Make sliders continually report their position as they are being dragged.
ESPUI.sliderContinuous = true;
//This GUI is going to be a tabbed GUI, so we are adding most controls using ESPUI.addControl
//which allows us to set a parent control. If we didn't need tabs we could use the simpler add
//functions like:
// ESPUI.button()
// ESPUI.label()
/*
* Tab: Basic Controls
* This tab contains all the basic ESPUI controls, and shows how to read and update them at runtime.
*-----------------------------------------------------------------------------------------------------------*/
auto maintab = ESPUI.addControl(Tab, "", "Basic controls");
ESPUI.addControl(Separator, "General controls", "", None, maintab);
ESPUI.addControl(Button, "Button", "Button 1", Alizarin, maintab, [](Control *sender, int type){ paramCallback(sender, type, 19); });
mainLabel = ESPUI.addControl(Label, "Label", "Label text", Emerald, maintab, generalCallback);
mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback);
//Sliders default to being 0 to 100, but if you want different limits you can add a Min and Max control
mainSlider = ESPUI.addControl(Slider, "Slider", "200", Turquoise, maintab, generalCallback);
ESPUI.addControl(Min, "", "10", None, mainSlider);
ESPUI.addControl(Max, "", "400", None, mainSlider);
//These are the values for the selector's options. (Note that they *must* be declared static
//so that the storage is allocated in global memory and not just on the stack of this function.)
static String optionValues[] {"Value 1", "Value 2", "Value 3", "Value 4", "Value 5"};
auto mainselector = ESPUI.addControl(Select, "Selector", "Selector", Wetasphalt, maintab, generalCallback);
for(auto const& v : optionValues) {
ESPUI.addControl(Option, v.c_str(), v, None, mainselector);
}
mainText = ESPUI.addControl(Text, "Text Input", "Initial value", Alizarin, maintab, generalCallback);
//Number inputs also accept Min and Max components, but you should still validate the values.
mainNumber = ESPUI.addControl(Number, "Number Input", "42", Emerald, maintab, generalCallback);
ESPUI.addControl(Min, "", "10", None, mainNumber);
ESPUI.addControl(Max, "", "50", None, mainNumber);
ESPUI.addControl(Separator, "Updates", "", None, maintab);
//This button will update all the updatable controls on this tab to random values
mainScrambleButton = ESPUI.addControl(Button, "Scramble Values", "Scramble Values", Carrot, maintab,
//This callback updates the "values" of a bunch of controls
[](Control *sender, int type) {
static char rndString1[10];
static char rndString2[20];
static bool scText = false;
if(type == B_UP) { //Button callbacks generate events for both UP and DOWN.
//Generate some random text
randomString(rndString1, 10);
randomString(rndString2, 20);
//Set the various controls to random value to show how controls can be updated at runtime
ESPUI.updateLabel(mainLabel, String(rndString1));
ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true);
ESPUI.updateSlider(mainSlider, random(10, 400));
ESPUI.updateText(mainText, String(rndString2));
ESPUI.updateNumber(mainNumber, random(100000));
ESPUI.updateButton(mainScrambleButton, scText ? "Scrambled!" : "Scrambled.");
scText = !scText;
}
});
ESPUI.addControl(Switcher, "Constant updates", "0", Carrot, maintab,
[](Control *sender, int type) {
updates = (sender->value.toInt() > 0);
});
mainTime = ESPUI.addControl(Time, "", "", None, 0, generalCallback);
ESPUI.addControl(Button, "Get Time", "Get Time", Carrot, maintab,
[](Control *sender, int type) {
if(type == B_UP) {
ESPUI.updateTime(mainTime);
}
});
ESPUI.addControl(Separator, "Control Pads", "", None, maintab);
ESPUI.addControl(Pad, "Normal", "", Peterriver, maintab, generalCallback);
ESPUI.addControl(PadWithCenter, "With center", "", Peterriver, maintab, generalCallback);
/*
* Tab: Colours
* This tab shows all the basic colours
*-----------------------------------------------------------------------------------------------------------*/
auto colourtab = ESPUI.addControl(Tab, "", "Colours");
ESPUI.addControl(Button, "Alizarin", "Alizarin", Alizarin, colourtab, generalCallback);
ESPUI.addControl(Button, "Turquoise", "Turquoise", Turquoise, colourtab, generalCallback);
ESPUI.addControl(Button, "Emerald", "Emerald", Emerald, colourtab, generalCallback);
ESPUI.addControl(Button, "Peterriver", "Peterriver", Peterriver, colourtab, generalCallback);
ESPUI.addControl(Button, "Wetasphalt", "Wetasphalt", Wetasphalt, colourtab, generalCallback);
ESPUI.addControl(Button, "Sunflower", "Sunflower", Sunflower, colourtab, generalCallback);
ESPUI.addControl(Button, "Carrot", "Carrot", Carrot, colourtab, generalCallback);
ESPUI.addControl(Button, "Dark", "Dark", Dark, colourtab, generalCallback);
/*
* Tab: Styled controls
* This tab shows off how inline CSS styles can be applied to elements and panels in order
* to customise the look of the UI.
*-----------------------------------------------------------------------------------------------------------*/
auto styletab = ESPUI.addControl(Tab, "", "Styled controls");
styleButton = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback);
styleLabel = ESPUI.addControl(Label, "Styled Label", "This is a label", Alizarin, styletab, generalCallback);
styleSwitcher = ESPUI.addControl(Switcher, "Styled Switcher", "1", Alizarin, styletab, generalCallback);
styleSlider = ESPUI.addControl(Slider, "Styled Slider", "0", Alizarin, styletab, generalCallback);
//This button will randomise the colours of the above controls to show updating of inline styles
ESPUI.addControl(Button, "Randomise Colours", "Randomise Colours", Sunflower, styletab,
//This callback generates and applies inline styles to a bunch of controls to change their colour.
//The styles created are of the form:
// "border-bottom: #999 3px solid; background-color: #aabbcc;"
// "background-color: #aabbcc;"
[](Control *sender, int type) {
//Declare space for style strings. These have to be static so that they are always available
//to the websocket layer. If we'd not made them static they'd be allocated on the heap and
//will be unavailable when we leave this function.
static char stylecol1[60], stylecol2[30];
if(type == B_UP) {
//Generate two random HTML hex colour codes, and print them into CSS style rules
sprintf(stylecol1, "border-bottom: #999 3px solid; background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
sprintf(stylecol2, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
//Apply those styles to various elements to show how controls react to styling
ESPUI.setPanelStyle(styleButton, stylecol1);
ESPUI.setElementStyle(styleButton, stylecol2);
ESPUI.setPanelStyle(styleLabel, stylecol1);
ESPUI.setElementStyle(styleLabel, stylecol2);
ESPUI.setPanelStyle(styleSwitcher, stylecol1);
ESPUI.setElementStyle(styleSwitcher, stylecol2);
ESPUI.setPanelStyle(styleSlider, stylecol1);
ESPUI.setElementStyle(styleSlider, stylecol2);
}
});
ESPUI.addControl(Separator, "Other styling examples", "", None, styletab);
styleButton2 = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback);
ESPUI.setPanelStyle(styleButton2, "background: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%); border-bottom: #555;");
ESPUI.setElementStyle(styleButton2, "border-radius: 2em; border: 3px solid black; width: 30%; background-color: #8df;");
styleSlider2 = ESPUI.addControl(Slider, "Styled Slider", "0", Dark, styletab, generalCallback);
ESPUI.setElementStyle(styleSlider2, "background: linear-gradient(to right, red, orange, yellow, green, blue);");
styleLabel2 = ESPUI.addControl(Label, "Styled Label", "This is a label", Dark, styletab, generalCallback);
ESPUI.setElementStyle(styleLabel2, "text-shadow: 3px 3px #74b1ff, 6px 6px #c64ad7; font-size: 60px; font-variant-caps: small-caps; background-color: unset; color: #c4f0bb; -webkit-text-stroke: 1px black;");
/*
* Tab: Grouped controls
* This tab shows how multiple control can be grouped into the same panel through the use of the
* parentControl value. This also shows how to add labels to grouped controls, and how to use vertical controls.
*-----------------------------------------------------------------------------------------------------------*/
auto grouptab = ESPUI.addControl(Tab, "", "Grouped controls");
//The parent of this button is a tab, so it will create a new panel with one control.
auto groupbutton = ESPUI.addControl(Button, "Button Group", "Button A", Dark, grouptab, generalCallback);
//However the parent of this button is another control, so therefore no new panel is
//created and the button is added to the existing panel.
ESPUI.addControl(Button, "", "Button B", Alizarin, groupbutton, generalCallback);
ESPUI.addControl(Button, "", "Button C", Alizarin, groupbutton, generalCallback);
//Sliders can be grouped as well
//To label each slider in the group, we are going add additional labels and give them custom CSS styles
//We need this CSS style rule, which will remove the label's background and ensure that it takes up the entire width of the panel
String clearLabelStyle = "background-color: unset; width: 100%;";
//First we add the main slider to create a panel
auto groupsliders = ESPUI.addControl(Slider, "Slider Group", "10", Dark, grouptab, generalCallback);
//Then we add a label and set its style to the clearLabelStyle. Here we've just given it the name "A"
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupsliders), clearLabelStyle);
//We can now continue to add additional sliders and labels
ESPUI.addControl(Slider, "", "20", None, groupsliders, generalCallback);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupsliders), clearLabelStyle);
ESPUI.addControl(Slider, "", "30", None, groupsliders, generalCallback);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupsliders), clearLabelStyle);
//We can also usefully group switchers.
auto groupswitcher = ESPUI.addControl(Switcher, "Switcher Group", "0", Dark, grouptab, generalCallback);
ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback);
ESPUI.addControl(Switcher, "", "0", Sunflower, groupswitcher, generalCallback);
ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback);
//To label these switchers we need to first go onto a "new line" below the line of switchers
//To do this we add an empty label set to be clear and full width (with our clearLabelStyle)
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, groupswitcher), clearLabelStyle);
//We will now need another label style. This one sets its width to the same as a switcher (and turns off the background)
String switcherLabelStyle = "width: 60px; margin-left: .3rem; margin-right: .3rem; background-color: unset;";
//We can now just add the styled labels.
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, groupswitcher), switcherLabelStyle);
//You can mix and match different control types, but the results might sometimes
//need additional styling to lay out nicely.
auto grouplabel = ESPUI.addControl(Label, "Mixed Group", "Main label", Dark, grouptab);
auto grouplabel2 = ESPUI.addControl(Label, "", "Secondary label", Emerald, grouplabel);
ESPUI.addControl(Button, "", "Button D", Alizarin, grouplabel, generalCallback);
ESPUI.addControl(Switcher, "", "1", Sunflower, grouplabel, generalCallback);
ESPUI.setElementStyle(grouplabel2, "font-size: x-large; font-family: serif;");
//Some controls can even support vertical orientation, currently Switchers and Sliders
ESPUI.addControl(Separator, "Vertical controls", "", None, grouptab);
auto vertgroupswitcher = ESPUI.addControl(Switcher, "Vertical Switcher Group", "0", Dark, grouptab, generalCallback);
ESPUI.setVertical(vertgroupswitcher);
//On the following lines we wrap the value returned from addControl and send it straight to setVertical
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
//The mechanism for labelling vertical switchers is the same as we used above for horizontal ones
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupswitcher), clearLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupswitcher), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupswitcher), switcherLabelStyle);
auto vertgroupslider = ESPUI.addControl(Slider, "Vertical Slider Group", "15", Dark, grouptab, generalCallback);
ESPUI.setVertical(vertgroupslider);
ESPUI.setVertical(ESPUI.addControl(Slider, "", "25", None, vertgroupslider, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Slider, "", "35", None, vertgroupslider, generalCallback));
ESPUI.setVertical(ESPUI.addControl(Slider, "", "45", None, vertgroupslider, generalCallback));
//The mechanism for labelling vertical sliders is the same as we used above for switchers
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupslider), clearLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupslider), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupslider), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupslider), switcherLabelStyle);
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupslider), switcherLabelStyle);
//Note that combining vertical and horizontal sliders is going to result in very messy layout!
/*
* Tab: Example UI
* An example UI for the documentation
*-----------------------------------------------------------------------------------------------------------*/
auto exampletab = ESPUI.addControl(Tab, "Example", "Example");
ESPUI.addControl(Separator, "Control and Status", "", None, exampletab);
ESPUI.addControl(Switcher, "Power", "1", Alizarin, exampletab, generalCallback);
ESPUI.addControl(Label, "Status", "System status: OK", Wetasphalt, exampletab, generalCallback);
ESPUI.addControl(Separator, "Settings", "", None, exampletab);
ESPUI.addControl(PadWithCenter, "Attitude Control", "", Dark, exampletab, generalCallback);
auto examplegroup1 = ESPUI.addControl(Button, "Activate Features", "Feature A", Carrot, exampletab, generalCallback);
ESPUI.addControl(Button, "Activate Features", "Feature B", Carrot, examplegroup1, generalCallback);
ESPUI.addControl(Button, "Activate Features", "Feature C", Carrot, examplegroup1, generalCallback);
ESPUI.addControl(Slider, "Value control", "45", Peterriver, exampletab, generalCallback);
/*
* Tab: WiFi Credentials
* You use this tab to enter the SSID and password of a wifi network to autoconnect to.
*-----------------------------------------------------------------------------------------------------------*/
auto wifitab = ESPUI.addControl(Tab, "", "WiFi Credentials");
wifi_ssid_text = ESPUI.addControl(Text, "SSID", "", Alizarin, wifitab, textCallback);
//Note that adding a "Max" control to a text control sets the max length
ESPUI.addControl(Max, "", "32", None, wifi_ssid_text);
wifi_pass_text = ESPUI.addControl(Text, "Password", "", Alizarin, wifitab, textCallback);
ESPUI.addControl(Max, "", "64", None, wifi_pass_text);
ESPUI.addControl(Button, "Save", "Save", Peterriver, wifitab,
[](Control *sender, int type) {
if(type == B_UP) {
Serial.println("Saving credentials to EPROM...");
Serial.println(ESPUI.getControl(wifi_ssid_text)->value);
Serial.println(ESPUI.getControl(wifi_pass_text)->value);
unsigned int i;
EEPROM.begin(100);
for(i = 0; i < ESPUI.getControl(wifi_ssid_text)->value.length(); i++) {
EEPROM.write(i, ESPUI.getControl(wifi_ssid_text)->value.charAt(i));
if(i==30) break; //Even though we provided a max length, user input should never be trusted
}
EEPROM.write(i, '\0');
for(i = 0; i < ESPUI.getControl(wifi_pass_text)->value.length(); i++) {
EEPROM.write(i + 32, ESPUI.getControl(wifi_pass_text)->value.charAt(i));
if(i==94) break; //Even though we provided a max length, user input should never be trusted
}
EEPROM.write(i + 32, '\0');
EEPROM.end();
}
});
//Finally, start up the UI.
//This should only be called once we are connected to WiFi.
ESPUI.begin(HOSTNAME);
}
//Most elements in this test UI are assigned this generic callback which prints some
//basic information. Event types are defined in ESPUI.h
void generalCallback(Control *sender, int type) {
Serial.print("CB: id(");
Serial.print(sender->id);
Serial.print(") Type(");
Serial.print(type);
Serial.print(") '");
Serial.print(sender->label);
Serial.print("' = ");
Serial.println(sender->value);
}
// Most elements in this test UI are assigned this generic callback which prints some
// basic information. Event types are defined in ESPUI.h
// The extended param can be used to pass additional information
void paramCallback(Control* sender, int type, int param)
{
Serial.print("CB: id(");
Serial.print(sender->id);
Serial.print(") Type(");
Serial.print(type);
Serial.print(") '");
Serial.print(sender->label);
Serial.print("' = ");
Serial.println(sender->value);
Serial.print("param = ");
Serial.println(param);
}
void setup() {
randomSeed(0);
Serial.begin(115200);
while(!Serial);
if(SLOW_BOOT) delay(5000); //Delay booting to give time to connect a serial monitor
connectWifi();
#if defined(ESP32)
WiFi.setSleep(false); //For the ESP32: turn off sleeping to increase UI responsivness (at the cost of power use)
#endif
setUpUI();
}
void loop() {
static long unsigned lastTime = 0;
//Send periodic updates if switcher is turned on
if(updates && millis() > lastTime + 500) {
static uint16_t sliderVal = 10;
//Flick this switcher on and off
ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true);
sliderVal += 10;
if(sliderVal > 400) sliderVal = 10;
//Sliders, numbers, and labels can all be updated at will
ESPUI.updateSlider(mainSlider, sliderVal);
ESPUI.updateNumber(mainNumber, random(100000));
ESPUI.updateLabel(mainLabel, String(sliderVal));
lastTime = millis();
}
//Simple debug UART interface
if(Serial.available()) {
switch(Serial.read()) {
case 'w': //Print IP details
Serial.println(WiFi.localIP());
break;
case 'W': //Reconnect wifi
connectWifi();
break;
case 'C': //Force a crash (for testing exception decoder)
#if !defined(ESP32)
((void (*)())0xf00fdead)();
#endif
break;
default:
Serial.print('#');
break;
}
}
#if !defined(ESP32)
//We don't need to call this explicitly on ESP32 but we do on 8266
MDNS.update();
#endif
}
//Utilities
//
//If you are here just to see examples of how to use ESPUI, you can ignore the following functions
//------------------------------------------------------------------------------------------------
void readStringFromEEPROM(String& buf, int baseaddress, int size) {
buf.reserve(size);
for (int i = baseaddress; i < baseaddress+size; i++) {
char c = EEPROM.read(i);
buf += c;
if(!c) break;
}
}
void connectWifi() {
int connect_timeout;
#if defined(ESP32)
WiFi.setHostname(HOSTNAME);
#else
WiFi.hostname(HOSTNAME);
#endif
Serial.println("Begin wifi...");
//Load credentials from EEPROM
if(!(FORCE_USE_HOTSPOT)) {
yield();
EEPROM.begin(100);
String stored_ssid, stored_pass;
readStringFromEEPROM(stored_ssid, 0, 32);
readStringFromEEPROM(stored_pass, 32, 96);
EEPROM.end();
//Try to connect with stored credentials, fire up an access point if they don't work.
#if defined(ESP32)
WiFi.begin(stored_ssid.c_str(), stored_pass.c_str());
#else
WiFi.begin(stored_ssid, stored_pass);
#endif
connect_timeout = 28; //7 seconds
while (WiFi.status() != WL_CONNECTED && connect_timeout > 0) {
delay(250);
Serial.print(".");
connect_timeout--;
}
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(WiFi.localIP());
Serial.println("Wifi started");
if (!MDNS.begin(HOSTNAME)) {
Serial.println("Error setting up MDNS responder!");
}
} else {
Serial.println("\nCreating access point...");
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
WiFi.softAP(HOSTNAME);
connect_timeout = 20;
do {
delay(250);
Serial.print(",");
connect_timeout--;
} while(connect_timeout);
}
}
void textCallback(Control *sender, int type) {
//This callback is needed to handle the changed values, even though it doesn't do anything itself.
}
void randomString(char *buf, int len) {
for(auto i = 0; i < len-1; i++)
buf[i] = random(0, 26) + 'A';
buf[len-1] = '\0';
}

View File

@@ -0,0 +1,43 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = ./src
data_dir = ../../data
[env]
framework = arduino
board_build.filesystem = littlefs
lib_extra_dirs = ../../
lib_deps =
; bblanchon/ArduinoJson @ ^6.18.5
bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bmedici/ESPAsyncWebServer ; Use a fork of the library that has a bugfix for the compile.... https://github.com/esphome/ESPAsyncWebServer/pull/17
lib_ignore =
ESP Async WebServer ; force the use of the esphome version
AsyncTCP ; force the use of the esphome version
LittleFS_esp32 ; force the use of the ESP32 built into the core version
[env:esp8266]
platform = espressif8266
board = nodemcuv2
monitor_speed = 115200
[env:esp32]
platform = espressif32
board = esp32dev
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout
lib_deps =
${env.lib_deps}
me-no-dev/AsyncTCP
monitor_speed = 115200

View File

@@ -0,0 +1,303 @@
#include <DNSServer.h>
#include <ESPUI.h>
const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 4, 1);
DNSServer dnsServer;
#if defined(ESP32)
#include <WiFi.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#endif
const char* ssid = "ESPUI";
const char* password = "espui";
const char* hostname = "espui";
uint16_t status;
uint16_t button1;
uint16_t millisLabelId;
uint16_t switchOne;
void numberCall(Control* sender, int type)
{
Serial.println(sender->value);
}
void textCall(Control* sender, int type)
{
Serial.print("Text: ID: ");
Serial.print(sender->id);
Serial.print(", Value: ");
Serial.println(sender->value);
}
void slider(Control* sender, int type)
{
Serial.print("Slider: ID: ");
Serial.print(sender->id);
Serial.print(", Value: ");
Serial.println(sender->value);
}
void buttonCallback(Control* sender, int type)
{
switch (type)
{
case B_DOWN:
Serial.println("Button DOWN");
break;
case B_UP:
Serial.println("Button UP");
break;
}
}
void buttonExample(Control* sender, int type, void* param)
{
Serial.print("param: ");
Serial.println((long)param);
switch (type)
{
case B_DOWN:
Serial.println("Status: Start");
ESPUI.updateControlValue(status, "Start");
ESPUI.getControl(button1)->color = ControlColor::Carrot;
ESPUI.updateControl(button1);
break;
case B_UP:
Serial.println("Status: Stop");
ESPUI.updateControlValue(status, "Stop");
ESPUI.getControl(button1)->color = ControlColor::Peterriver;
ESPUI.updateControl(button1);
break;
}
}
void padExample(Control* sender, int value)
{
switch (value)
{
case P_LEFT_DOWN:
Serial.print("left down");
break;
case P_LEFT_UP:
Serial.print("left up");
break;
case P_RIGHT_DOWN:
Serial.print("right down");
break;
case P_RIGHT_UP:
Serial.print("right up");
break;
case P_FOR_DOWN:
Serial.print("for down");
break;
case P_FOR_UP:
Serial.print("for up");
break;
case P_BACK_DOWN:
Serial.print("back down");
break;
case P_BACK_UP:
Serial.print("back up");
break;
case P_CENTER_DOWN:
Serial.print("center down");
break;
case P_CENTER_UP:
Serial.print("center up");
break;
}
Serial.print(" ");
Serial.println(sender->id);
}
void switchExample(Control* sender, int value)
{
switch (value)
{
case S_ACTIVE:
Serial.print("Active:");
break;
case S_INACTIVE:
Serial.print("Inactive");
break;
}
Serial.print(" ");
Serial.println(sender->id);
}
void selectExample(Control* sender, int value)
{
Serial.print("Select: ID: ");
Serial.print(sender->id);
Serial.print(", Value: ");
Serial.println(sender->value);
}
void otherSwitchExample(Control* sender, int value)
{
switch (value)
{
case S_ACTIVE:
Serial.print("Active:");
break;
case S_INACTIVE:
Serial.print("Inactive");
break;
}
Serial.print(" ");
Serial.println(sender->id);
}
void setup(void)
{
ESPUI.setVerbosity(Verbosity::VerboseJSON);
Serial.begin(115200);
#if defined(ESP32)
WiFi.setHostname(hostname);
#else
WiFi.hostname(hostname);
#endif
// try to connect to existing network
WiFi.begin(ssid, password);
Serial.print("\n\nTry to connect to existing network");
{
uint8_t timeout = 10;
// Wait for connection, 5s timeout
do
{
delay(500);
Serial.print(".");
timeout--;
} while (timeout && WiFi.status() != WL_CONNECTED);
// not connected -> create hotspot
if (WiFi.status() != WL_CONNECTED)
{
Serial.print("\n\nCreating hotspot");
WiFi.mode(WIFI_AP);
delay(100);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
#if defined(ESP32)
uint32_t chipid = 0;
for (int i = 0; i < 17; i = i + 8)
{
chipid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
#else
uint32_t chipid = ESP.getChipId();
#endif
char ap_ssid[25];
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
WiFi.softAP(ap_ssid);
timeout = 5;
do
{
delay(500);
Serial.print(".");
timeout--;
} while (timeout);
}
}
dnsServer.start(DNS_PORT, "*", apIP);
Serial.println("\n\nWiFi parameters:");
Serial.print("Mode: ");
Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
Serial.print("IP address: ");
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
uint16_t select1 = ESPUI.addControl(
ControlType::Select, "Select:", "", ControlColor::Alizarin, Control::noParent, &selectExample);
ESPUI.addControl(ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1);
ESPUI.addControl(ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1);
ESPUI.addControl(ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1);
ESPUI.addControl(
ControlType::Text, "Text Test:", "a Text Field", ControlColor::Alizarin, Control::noParent, &textCall);
millisLabelId = ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, Control::noParent);
button1 = ESPUI.addControl(
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
ESPUI.addControl(
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample, (void*)19);
ESPUI.addControl(
ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
switchOne = ESPUI.addControl(
ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switchExample);
ESPUI.addControl(
ControlType::Switcher, "Switch two", "", ControlColor::None, Control::noParent, &otherSwitchExample);
ESPUI.addControl(ControlType::Slider, "Slider one", "30", ControlColor::Alizarin, Control::noParent, &slider);
ESPUI.addControl(ControlType::Slider, "Slider two", "100", ControlColor::Alizarin, Control::noParent, &slider);
ESPUI.addControl(ControlType::Number, "Number:", "50", ControlColor::Alizarin, Control::noParent, &numberCall);
/*
* .begin loads and serves all files from PROGMEM directly.
* If you want to serve the files from LITTLEFS use ESPUI.beginLITTLEFS
* (.prepareFileSystem has to be run in an empty sketch before)
*/
// Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
// ESPUI.sliderContinuous = true;
/*
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
* SECURE way of limiting access.
* Anyone who is able to sniff traffic will be able to intercept your password
* since it is transmitted in cleartext. Just add a string as username and
* password, for example begin("ESPUI Control", "username", "password")
*/
ESPUI.begin("ESPUI Control");
}
void loop(void)
{
dnsServer.processNextRequest();
static long oldTime = 0;
static bool testSwitchState = false;
if (millis() - oldTime > 5000)
{
ESPUI.updateControlValue(millisLabelId, String(millis()));
testSwitchState = !testSwitchState;
ESPUI.updateControlValue(switchOne, testSwitchState ? "1" : "0");
oldTime = millis();
}
}

View File

@@ -0,0 +1,43 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = ./src
data_dir = ../../data
[env]
framework = arduino
board_build.filesystem = littlefs
lib_extra_dirs = ../../
lib_deps =
; bblanchon/ArduinoJson @ ^6.18.5
bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bmedici/ESPAsyncWebServer ; Use a fork of the library that has a bugfix for the compile.... https://github.com/esphome/ESPAsyncWebServer/pull/17
lib_ignore =
ESP Async WebServer ; force the use of the esphome version
AsyncTCP ; force the use of the esphome version
LittleFS_esp32 ; force the use of the ESP32 built into the core version
[env:esp8266]
platform = espressif8266
board = nodemcuv2
monitor_speed = 115200
[env:esp32]
platform = espressif32
board = esp32dev
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout
lib_deps =
${env.lib_deps}
me-no-dev/AsyncTCP
monitor_speed = 115200

View File

@@ -0,0 +1,17 @@
#include <Arduino.h>
#include <ESPUI.h>
void setup(void)
{
Serial.begin(115200);
ESPUI.setVerbosity(Verbosity::Verbose); //Enable verbose output so you see the files in LittleFS
delay(500); //Delay to allow Serial Monitor to start after a reset
Serial.println(F("\nPreparing filesystem with ESPUI resources"));
ESPUI.prepareFileSystem(); //Copy across current version of ESPUI resources
Serial.println(F("Done, files..."));
ESPUI.list(); //List all files on LittleFS, for info
}
void loop()
{
}

View File

@@ -0,0 +1,43 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = ./src
data_dir = ../../data
[env]
framework = arduino
board_build.filesystem = littlefs
lib_extra_dirs = ../../
lib_deps =
; bblanchon/ArduinoJson @ ^6.18.5
bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bmedici/ESPAsyncWebServer ; Use a fork of the library that has a bugfix for the compile.... https://github.com/esphome/ESPAsyncWebServer/pull/17
lib_ignore =
ESP Async WebServer ; force the use of the esphome version
AsyncTCP ; force the use of the esphome version
LittleFS_esp32 ; force the use of the ESP32 built into the core version
[env:esp8266]
platform = espressif8266
board = nodemcuv2
monitor_speed = 115200
[env:esp32]
platform = espressif32
board = esp32dev
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout
lib_deps =
${env.lib_deps}
me-no-dev/AsyncTCP
monitor_speed = 115200

View File

@@ -0,0 +1,300 @@
#include <DNSServer.h>
#include <ESPUI.h>
const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 4, 1);
DNSServer dnsServer;
#if defined(ESP32)
#include <WiFi.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#endif
const char* ssid = "ESPUI";
const char* password = "espui";
const char* hostname = "espui";
uint16_t button1;
uint16_t switchOne;
uint16_t status;
void numberCall(Control* sender, int type)
{
Serial.println(sender->value);
}
void textCall(Control* sender, int type)
{
Serial.print("Text: ID: ");
Serial.print(sender->id);
Serial.print(", Value: ");
Serial.println(sender->value);
}
void slider(Control* sender, int type)
{
Serial.print("Slider: ID: ");
Serial.print(sender->id);
Serial.print(", Value: ");
Serial.println(sender->value);
}
void buttonCallback(Control* sender, int type)
{
switch (type)
{
case B_DOWN:
Serial.println("Button DOWN");
break;
case B_UP:
Serial.println("Button UP");
break;
}
}
void buttonExample(Control* sender, int type, void* param)
{
Serial.print("param: ");
Serial.println((long)param);
switch (type)
{
case B_DOWN:
Serial.println("Status: Start");
ESPUI.updateControlValue(status, "Start");
ESPUI.getControl(button1)->color = ControlColor::Carrot;
ESPUI.updateControl(button1);
break;
case B_UP:
Serial.println("Status: Stop");
ESPUI.updateControlValue(status, "Stop");
ESPUI.getControl(button1)->color = ControlColor::Peterriver;
ESPUI.updateControl(button1);
break;
}
}
void padExample(Control* sender, int value)
{
switch (value)
{
case P_LEFT_DOWN:
Serial.print("left down");
break;
case P_LEFT_UP:
Serial.print("left up");
break;
case P_RIGHT_DOWN:
Serial.print("right down");
break;
case P_RIGHT_UP:
Serial.print("right up");
break;
case P_FOR_DOWN:
Serial.print("for down");
break;
case P_FOR_UP:
Serial.print("for up");
break;
case P_BACK_DOWN:
Serial.print("back down");
break;
case P_BACK_UP:
Serial.print("back up");
break;
case P_CENTER_DOWN:
Serial.print("center down");
break;
case P_CENTER_UP:
Serial.print("center up");
break;
}
Serial.print(" ");
Serial.println(sender->id);
}
void switchExample(Control* sender, int value)
{
switch (value)
{
case S_ACTIVE:
Serial.print("Active:");
break;
case S_INACTIVE:
Serial.print("Inactive");
break;
}
Serial.print(" ");
Serial.println(sender->id);
}
void selectExample(Control* sender, int value)
{
Serial.print("Select: ID: ");
Serial.print(sender->id);
Serial.print(", Value: ");
Serial.println(sender->value);
}
void otherSwitchExample(Control* sender, int value)
{
switch (value)
{
case S_ACTIVE:
Serial.print("Active:");
break;
case S_INACTIVE:
Serial.print("Inactive");
break;
}
Serial.print(" ");
Serial.println(sender->id);
}
void setup(void)
{
Serial.begin(115200);
#if defined(ESP32)
WiFi.setHostname(hostname);
#else
WiFi.hostname(hostname);
#endif
// try to connect to existing network
WiFi.begin(ssid, password);
Serial.print("\n\nTry to connect to existing network");
{
uint8_t timeout = 10;
// Wait for connection, 5s timeout
do
{
delay(500);
Serial.print(".");
timeout--;
} while (timeout && WiFi.status() != WL_CONNECTED);
// not connected -> create hotspot
if (WiFi.status() != WL_CONNECTED)
{
Serial.print("\n\nCreating hotspot");
WiFi.mode(WIFI_AP);
delay(100);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
#if defined(ESP32)
uint32_t chipid = 0;
for (int i = 0; i < 17; i = i + 8)
{
chipid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
#else
uint32_t chipid = ESP.getChipId();
#endif
char ap_ssid[25];
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
WiFi.softAP(ap_ssid);
timeout = 5;
do
{
delay(500);
Serial.print(".");
timeout--;
} while (timeout);
}
}
dnsServer.start(DNS_PORT, "*", apIP);
Serial.println("\n\nWiFi parameters:");
Serial.print("Mode: ");
Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
Serial.print("IP address: ");
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
uint16_t tab1 = ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1");
uint16_t tab2 = ESPUI.addControl(ControlType::Tab, "Settings 2", "Settings 2");
uint16_t tab3 = ESPUI.addControl(ControlType::Tab, "Settings 3", "Settings 3");
// shown above all tabs
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
uint16_t select1
= ESPUI.addControl(ControlType::Select, "Select:", "", ControlColor::Alizarin, tab1, &selectExample);
ESPUI.addControl(ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1);
ESPUI.addControl(ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1);
ESPUI.addControl(ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1);
ESPUI.addControl(ControlType::Text, "Text Test:", "a Text Field", ControlColor::Alizarin, tab1, &textCall);
// tabbed controls
ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1);
button1 = ESPUI.addControl(
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback);
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample, (void*)19);
ESPUI.addControl(ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, tab2, &padExample);
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample);
switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample);
ESPUI.addControl(ControlType::Switcher, "Switch two", "", ControlColor::None, tab3, &otherSwitchExample);
ESPUI.addControl(ControlType::Slider, "Slider one", "30", ControlColor::Alizarin, tab1, &slider);
ESPUI.addControl(ControlType::Slider, "Slider two", "100", ControlColor::Alizarin, tab3, &slider);
ESPUI.addControl(ControlType::Number, "Number:", "50", ControlColor::Alizarin, tab3, &numberCall);
/*
* .begin loads and serves all files from PROGMEM directly.
* If you want to serve the files from LITTLEFS use ESPUI.beginLITTLEFS
* (.prepareFileSystem has to be run in an empty sketch before)
*/
// Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
// ESPUI.sliderContinuous = true;
/*
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
* SECURE way of limiting access.
* Anyone who is able to sniff traffic will be able to intercept your password
* since it is transmitted in cleartext. Just add a string as username and
* password, for example begin("ESPUI Control", "username", "password")
*/
ESPUI.begin("ESPUI Control");
}
void loop(void)
{
dnsServer.processNextRequest();
static long oldTime = 0;
static bool switchi = false;
if (millis() - oldTime > 5000)
{
switchi = !switchi;
ESPUI.updateControlValue(switchOne, switchi ? "1" : "0");
oldTime = millis();
}
}

View File

@@ -17,6 +17,30 @@
#include <umm_malloc/umm_heap_select.h> #include <umm_malloc/umm_heap_select.h>
#endif #endif
// Optional user-defined JavaScript to be included in the UI.
// Served at /js/custom.js, which is automatically included in index.htm.
// js: JavaScript code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
static const char* customJS = nullptr;
// Optional user-defined CSS to be included in the UI.
// Served at /css/custom.css, which is automatically included in index.htm.
// css: CSS code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
static const char* customCSS = nullptr;
// Set custom JavaScript to be included in the UI.
// js: JavaScript code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
void ESPUIClass::setCustomJS(const char* js)
{
customJS = js;
}
// Set custom CSS to be included in the UI.
// css: CSS code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
void ESPUIClass::setCustomCSS(const char* css)
{
customCSS = css;
}
static String heapInfo(const __FlashStringHelper* mode) static String heapInfo(const __FlashStringHelper* mode)
{ {
String result; String result;
@@ -660,7 +684,7 @@ uint16_t ESPUIClass::gauge(const char* label, ControlColor color, int number, in
uint16_t ESPUIClass::separator(const char* label) uint16_t ESPUIClass::separator(const char* label)
{ {
return addControl(ControlType::Separator, label, "", ControlColor::Alizarin, Control::noParent, nullptr); return addControl(ControlType::Separator, label, "", ControlColor::Alizarin);
} }
uint16_t ESPUIClass::fileDisplay(const char* label, ControlColor color, String filename) uint16_t ESPUIClass::fileDisplay(const char* label, ControlColor color, String filename)
@@ -745,6 +769,16 @@ void ESPUIClass::setPanelStyle(uint16_t id, const String& style, int clientId)
} }
} }
void ESPUIClass::setPanelClass(uint16_t id, const String& pClass, int clientId)
{
Control* control = getControl(id);
if (control)
{
control->panelClass = pClass;
updateControl(control, clientId);
}
}
void ESPUIClass::setElementStyle(uint16_t id, const String& style, int clientId) void ESPUIClass::setElementStyle(uint16_t id, const String& style, int clientId)
{ {
Control* control = getControl(id); Control* control = getControl(id);
@@ -1264,7 +1298,6 @@ void ESPUIClass::begin(const char* _title, const char* username, const char* pas
responseText += ("</body></html><head><meta http-equiv=\"Refresh\" content=\"0; URL='http://" + WiFi.softAPIP().toString() + "'\" /></head>"); responseText += ("</body></html><head><meta http-equiv=\"Refresh\" content=\"0; URL='http://" + WiFi.softAPIP().toString() + "'\" /></head>");
response->write(responseText.c_str(), responseText.length()); response->write(responseText.c_str(), responseText.length());
request->send(response); request->send(response);
request->redirect("/");
} }
else else
{ {
@@ -1273,6 +1306,24 @@ void ESPUIClass::begin(const char* _title, const char* username, const char* pas
yield(); yield();
}); });
server->on("/js/custom.js", HTTP_GET, [](AsyncWebServerRequest* request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword))
{
return request->requestAuthentication();
}
request->send(200, "application/javascript", customJS ? customJS : "");
});
server->on("/css/custom.css", HTTP_GET, [](AsyncWebServerRequest* request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword))
{
return request->requestAuthentication();
}
request->send(200, "text/css", customCSS ? customCSS : "");
});
server->begin(); server->begin();
#if defined(DEBUG_ESPUI) #if defined(DEBUG_ESPUI)

View File

@@ -192,6 +192,7 @@ public:
void addGraphPoint(uint16_t id, int nValue, int clientId = -1); void addGraphPoint(uint16_t id, int nValue, int clientId = -1);
void setPanelStyle(uint16_t id, const String& style, int clientId = -1); void setPanelStyle(uint16_t id, const String& style, int clientId = -1);
void setPanelClass(uint16_t id, const String& pClass, int clientId = -1);
void setElementStyle(uint16_t id, const String& style, int clientId = -1); void setElementStyle(uint16_t id, const String& style, int clientId = -1);
void setInputType(uint16_t id, const String& type, int clientId = -1); void setInputType(uint16_t id, const String& type, int clientId = -1);
@@ -201,6 +202,16 @@ public:
void updateVisibility(uint16_t id, bool visibility, int clientId = -1); void updateVisibility(uint16_t id, bool visibility, int clientId = -1);
// Set optional user-defined JavaScript to be included in the UI.
// js: JavaScript code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
// This is intentionally not a String to avoid dynamic memory allocation.
void setCustomJS(const char* js);
// Set optional user-defined CSS to be included in the UI.
// css: CSS code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
// This is intentionally not a String to avoid dynamic memory allocation.
void setCustomCSS(const char* css);
// Variables // Variables
const char* ui_title = "ESPUI"; // Store UI Title and Header Name const char* ui_title = "ESPUI"; // Store UI Title and Header Name
Control* controls = nullptr; Control* controls = nullptr;
@@ -249,6 +260,7 @@ public:
AsyncWebServer* WebServer() {return server;} AsyncWebServer* WebServer() {return server;}
AsyncWebSocket* WebSocket() {return ws;} AsyncWebSocket* WebSocket() {return ws;}
size_t clientCount() const {return MapOfClients.size();}
#if defined(ESP32) #if defined(ESP32)
# if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR >= 4) || ESP_IDF_VERSION_MAJOR > 4 # if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR >= 4) || ESP_IDF_VERSION_MAJOR > 4
@@ -282,7 +294,6 @@ protected:
#define ClientUpdateType_t ESPUIclient::ClientUpdateType_t #define ClientUpdateType_t ESPUIclient::ClientUpdateType_t
void NotifyClients(ClientUpdateType_t newState); void NotifyClients(ClientUpdateType_t newState);
void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState);
bool SendJsonDocToWebSocket(ArduinoJson::JsonDocument& document, uint16_t clientId); bool SendJsonDocToWebSocket(ArduinoJson::JsonDocument& document, uint16_t clientId);

View File

@@ -310,14 +310,14 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
break; break;
} }
if(!FragmentRequest.containsKey(F("id"))) if(!FragmentRequest["id"].is<JsonVariant>())
{ {
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a control ID")); Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a control ID"));
break; break;
} }
uint16_t ControlId = uint16_t(FragmentRequest[F("id")]); uint16_t ControlId = uint16_t(FragmentRequest[F("id")]);
if(!FragmentRequest.containsKey(F("offset"))) if(!FragmentRequest["offset"].is<JsonVariant>())
{ {
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a starting offset")); Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a starting offset"));
break; break;

View File

@@ -154,6 +154,7 @@ bool Control::MarshalControl(JsonObject & _item,
item[F("enabled")] = enabled; item[F("enabled")] = enabled;
if (!panelStyle.isEmpty()) {item[F("panelStyle")] = panelStyle;} if (!panelStyle.isEmpty()) {item[F("panelStyle")] = panelStyle;}
if (!panelClass.isEmpty()) {item[F("panelClass")] = panelClass;}
if (!elementStyle.isEmpty()) {item[F("elementStyle")] = elementStyle;} if (!elementStyle.isEmpty()) {item[F("elementStyle")] = elementStyle;}
if (!inputType.isEmpty()) {item[F("inputType")] = inputType;} if (!inputType.isEmpty()) {item[F("inputType")] = inputType;}
if (wide == true) {item[F("wide")] = true;} if (wide == true) {item[F("wide")] = true;}
@@ -207,6 +208,20 @@ void Control::onWsEvent(String & cmd, String& data)
do // once do // once
{ {
// Serial.println(String(F("Control::onWsEvent"))); // Serial.println(String(F("Control::onWsEvent")));
// Handle time response separately - it should not mark the control as changed
// because it's a one-shot response to an updateTime() request, not a state change
// that needs to be broadcast to other clients
if (cmd.equals(F("time")))
{
if (HasCallback())
{
value = data;
SendCallback(TM_VALUE);
}
break;
}
SetControlChangedId(ESPUI.GetNextControlChangeId()); SetControlChangedId(ESPUI.GetNextControlChangeId());
if (!HasCallback()) if (!HasCallback())
{ {
@@ -317,12 +332,6 @@ void Control::onWsEvent(String & cmd, String& data)
// updateControl(c, client->id()); // updateControl(c, client->id());
SendCallback(S_VALUE); SendCallback(S_VALUE);
} }
else if (cmd.equals(F("time")))
{
value = data;
// updateControl(c, client->id());
SendCallback(TM_VALUE);
}
else else
{ {
#if defined(DEBUG_ESPUI) #if defined(DEBUG_ESPUI)

View File

@@ -65,7 +65,9 @@ public:
bool enabled; bool enabled;
uint16_t parentControl; uint16_t parentControl;
String panelStyle; String panelStyle;
String panelClass;
String elementStyle; String elementStyle;
String inputType; String inputType;
Control* next; Control* next;

File diff suppressed because one or more lines are too long

View File

@@ -1,19 +1,5 @@
const char JS_GRAPH[] PROGMEM = R"=====( const char JS_GRAPH[] PROGMEM = R"=====(
function lineGraph(parent,xAccessor,yAccessor){const width=620;const height=420;const gutter=40;const pixelsPerTick=30;function numericTransformer(dataMin,dataMax,pxMin,pxMax){var dataDiff=dataMax-dataMin,pxDiff=pxMax-pxMin,dataRatio=pxDiff/dataDiff,coordRatio=dataDiff/pxDiff;return{toCoord:function(data){return(data-dataMin)*dataRatio+pxMin;},toData:function(coord){return(coord-pxMin)*coordRatio+dataMin;}};} function lineGraph(e,t,n){const o=620,i=420,s=40,a=30;function r(e,t,n,s){var o=t-e,i=s-n,a=i/o,r=o/i;return{toCoord:function(t){return(t-e)*a+n},toData:function(t){return(t-n)*r+e}}}function c(t,n){var r,c,l,d,h,m,p,g,u=document.createElementNS("http://www.w3.org/2000/svg","g"),f=document.createElementNS("http://www.w3.org/2000/svg","path");if(u.setAttribute("class",t+"-axis"),l=s,h=o-s,d=i-s,p=s,t==="x"){f.setAttribute("d","M "+l+" "+d+" L "+h+" "+d);for(r=l;r<=h;r++)(r-l)%(a*3)===0&&r!==l&&(c=document.createElementNS("http://www.w3.org/2000/svg","text"),c.innerHTML=new Date(Math.floor(n(r))).toLocaleTimeString(),c.setAttribute("x",r),c.setAttribute("y",d),c.setAttribute("dy","1em"),u.appendChild(c))}else{f.setAttribute("d","M "+l+" "+d+" L "+l+" "+p);for(r=p;r<=d;r++)(r-d)%a===0&&r!==d&&(m=document.createElementNS("http://www.w3.org/2000/svg","g"),g=document.createElementNS("http://www.w3.org/2000/svg","path"),c=document.createElementNS("http://www.w3.org/2000/svg","text"),c.innerHTML=Math.floor(n(r)),c.setAttribute("x",l),c.setAttribute("y",r),c.setAttribute("dx","-.5em"),c.setAttribute("dy",".3em"),g.setAttribute("d","M "+l+" "+r+" L "+h+" "+r),m.appendChild(g),m.appendChild(c),u.appendChild(m))}u.appendChild(f),e.appendChild(u)}function l(t,n,s,o){var a,i=document.createElementNS("http://www.w3.org/2000/svg","path");if(t.reset(),n.reset(),!t.hasNext()||!n.hasNext())return;for(a="M "+s(t.next())+" "+o(n.next());t.hasNext()&&n.hasNext();)a+=" L "+s(t.next())+" "+o(n.next());i.setAttribute("class","series"),i.setAttribute("d",a),e.appendChild(i)}function d(t,n,s,o){var i,a,c,l,d,u,r=document.createElementNS("http://www.w3.org/2000/svg","g");if(r.setAttribute("class","data-points"),t.reset(),n.reset(),!t.hasNext()||!n.hasNext())return;for(;t.hasNext()&&n.hasNext();)c=t.next(),l=s(c),d=n.next(),u=o(d),a=document.createElementNS("http://www.w3.org/2000/svg","circle"),a.setAttribute("cx",l),a.setAttribute("cy",u),a.setAttribute("r","4"),i=document.createElementNS("http://www.w3.org/2000/svg","text"),i.innerHTML=new Date(Math.floor(c)).toLocaleTimeString()+" / "+Math.floor(d),i.setAttribute("x",l),i.setAttribute("y",u),i.setAttribute("dx","1em"),i.setAttribute("dy","-.7em"),r.appendChild(a),r.appendChild(i);e.appendChild(r)}xTransform=r(t.min(),t.max(),0+s,o-s),yTransform=r(n.min(),n.max(),i-s,0+s),c("x",xTransform.toData),c("y",yTransform.toData),l(t,n,xTransform.toCoord,yTransform.toCoord),d(t,n,xTransform.toCoord,yTransform.toCoord)}function renderGraphSvg(e,t){for(var n,s=document.getElementById(t);s.hasChildNodes();)s.removeChild(s.lastChild);n=document.createElementNS("http://www.w3.org/2000/svg","svg"),n.setAttribute("viewBox","0 0 640 440"),n.setAttribute("preserveAspectRatio","xMidYMid meet"),lineGraph(n,function(e,t,n){var s=0;return{hasNext:function(){return s<e.length},next:function(){return e[s++].x},reset:function(){s=0},min:function(){return t},max:function(){return n}}}(e,Math.min.apply(Math,e.map(function(e){return e.x})),Math.max.apply(Math,e.map(function(e){return e.x}))),function(e,t,n){var s=0;return{hasNext:function(){return s<e.length},next:function(){return e[s++].y},reset:function(){s=0},min:function(){return t},max:function(){return n}}}(e,Math.min.apply(Math,e.map(function(e){return e.y})),Math.max.apply(Math,e.map(function(e){return e.y})))),s.appendChild(n)}
function axisRenderer(orientation,transform){var axisGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var axisPath=document.createElementNS("http://www.w3.org/2000/svg","path");axisGroup.setAttribute("class",orientation+"-axis");var xMin=gutter;var xMax=width-gutter;var yMin=height-gutter;var yMax=gutter;if(orientation==="x"){axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMax+" "+yMin);for(var i=xMin;i<=xMax;i++){if((i-xMin)%(pixelsPerTick*3)===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=new Date(Math.floor(transform(i))).toLocaleTimeString();text.setAttribute("x",i);text.setAttribute("y",yMin);text.setAttribute("dy","1em");axisGroup.appendChild(text);}}}else{axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMin+" "+yMax);for(var i=yMax;i<=yMin;i++){if((i-yMin)%pixelsPerTick===0&&i!==yMin){var tickGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var gridLine=document.createElementNS("http://www.w3.org/2000/svg","path");text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",xMin);text.setAttribute("y",i);text.setAttribute("dx","-.5em");text.setAttribute("dy",".3em");gridLine.setAttribute("d","M "+xMin+" "+i+" L "+xMax+" "+i);tickGroup.appendChild(gridLine);tickGroup.appendChild(text);axisGroup.appendChild(tickGroup);}}} )=====";
axisGroup.appendChild(axisPath);parent.appendChild(axisGroup);}
function lineRenderer(xAccessor,yAccessor,xTransform,yTransform){var line=document.createElementNS("http://www.w3.org/2000/svg","path");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;} const uint8_t JS_GRAPH_GZIP[1027] PROGMEM = { 31,139,8,0,0,0,0,0,0,255,196,86,75,111,227,54,16,254,43,90,2,107,112,86,99,89,77,220,45,176,94,30,246,81,180,5,146,28,154,92,138,162,7,86,164,45,2,18,41,144,148,45,195,171,255,94,80,242,75,182,55,237,58,69,123,9,196,121,48,156,111,190,249,60,243,90,103,94,25,29,21,74,203,159,44,175,114,42,209,163,134,77,102,180,243,145,97,111,111,82,84,108,122,147,162,99,211,20,57,187,77,103,251,44,219,71,163,131,205,146,219,200,48,63,150,168,152,27,107,228,76,77,12,90,102,38,106,102,165,175,173,222,120,243,201,24,43,222,237,210,169,135,77,239,162,126,44,225,13,143,117,139,222,124,230,158,95,142,209,240,198,198,178,109,219,253,3,50,218,61,54,252,115,139,25,22,40,48,199,18,43,92,96,205,132,201,234,82,106,159,100,86,114,47,127,44,100,56,61,60,82,146,123,95,189,155,76,86,171,85,178,186,77,140,93,76,110,210,52,157,184,229,130,32,89,16,192,249,181,201,21,247,57,129,153,154,211,58,113,210,127,240,222,170,63,107,47,41,201,10,238,28,65,31,147,49,111,148,35,128,5,115,152,51,51,118,40,152,26,59,172,152,67,207,24,35,13,129,205,252,36,93,16,36,247,17,137,139,152,68,36,22,49,137,238,34,18,231,253,9,102,115,99,169,101,197,204,190,103,249,204,198,49,80,59,46,224,53,229,111,110,129,49,150,142,70,246,21,99,197,104,68,179,107,75,243,178,241,4,48,75,148,214,210,254,252,116,127,199,180,92,69,159,185,151,244,158,251,60,153,23,198,88,170,169,5,128,196,155,59,147,241,66,62,169,82,62,122,171,244,130,134,220,97,81,13,65,123,110,93,19,20,231,86,177,38,72,190,147,37,1,172,19,94,85,82,139,79,185,42,4,205,0,90,89,56,249,15,33,235,79,213,14,178,42,64,38,118,144,9,120,205,15,112,137,209,136,150,47,161,209,226,101,52,194,127,179,87,167,45,186,212,140,226,98,51,46,180,72,52,4,201,56,249,190,107,199,197,78,37,183,157,111,241,108,79,236,128,198,22,176,28,116,118,113,106,200,78,123,95,2,180,67,203,28,80,14,12,53,28,228,162,160,157,90,161,233,37,131,163,122,249,156,251,196,74,39,61,5,212,251,175,87,62,201,185,123,144,141,167,240,229,203,43,125,56,65,47,102,29,249,56,235,160,112,212,39,186,119,118,48,24,170,119,231,217,209,61,163,209,209,53,51,224,49,235,177,123,46,93,93,214,32,226,164,85,50,72,208,105,128,32,200,79,1,84,71,0,138,33,128,10,249,86,117,107,180,47,24,148,128,163,253,202,91,5,247,124,92,25,165,125,120,240,245,104,63,131,101,198,118,16,6,77,14,44,19,108,7,34,214,204,80,1,200,175,45,47,83,54,43,36,1,228,167,5,246,3,119,102,94,19,172,207,205,150,32,153,134,142,189,80,18,212,223,200,119,246,21,237,142,73,52,137,72,124,20,41,206,233,211,87,116,106,237,11,58,163,90,179,215,243,51,215,186,83,151,31,58,167,29,144,145,159,26,20,204,134,116,181,208,54,79,150,107,55,55,182,100,150,250,164,84,154,6,230,148,188,161,128,105,236,208,140,29,224,250,56,74,111,163,244,54,42,252,30,167,177,3,204,186,170,14,55,38,253,130,210,57,214,228,232,146,189,163,23,153,65,70,183,247,12,99,59,19,160,248,134,224,195,24,90,169,133,180,221,190,246,184,92,132,37,12,54,129,227,97,38,53,186,3,69,22,210,111,249,241,113,253,139,160,30,102,46,16,191,67,234,193,8,233,2,253,93,98,101,105,150,178,199,207,37,5,119,190,251,134,153,190,150,109,225,111,64,115,216,216,165,146,171,143,38,52,62,141,210,232,237,52,141,166,211,244,66,92,21,70,219,46,229,7,87,201,204,255,202,189,50,4,73,115,175,196,111,247,74,68,165,148,129,202,135,157,85,227,126,95,220,174,175,1,9,199,210,221,234,185,29,246,195,90,185,219,42,35,247,94,38,133,212,11,159,183,168,47,135,200,223,93,28,255,145,52,45,118,138,115,28,225,88,218,98,169,244,133,44,223,98,201,155,11,14,221,182,45,149,216,77,82,169,116,32,111,177,238,70,16,101,82,242,138,30,106,57,60,33,105,90,128,109,14,111,190,33,7,254,11,104,214,255,43,52,235,43,160,9,57,0,232,6,210,161,161,253,43,0,0,255,255,190,73,2,131,17,13,0,0 };
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+
xTransform(xAccessor.next())+
" "+
yTransform(yAccessor.next());}
line.setAttribute("class","series");line.setAttribute("d",pathString);parent.appendChild(line);}
function pointRenderer(xAccessor,yAccessor,xTransform,yTransform){var pointGroup=document.createElementNS("http://www.w3.org/2000/svg","g");pointGroup.setAttribute("class","data-points");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
while(xAccessor.hasNext()&&yAccessor.hasNext()){var xDataValue=xAccessor.next();var x=xTransform(xDataValue);var yDataValue=yAccessor.next();var y=yTransform(yDataValue);var circle=document.createElementNS("http://www.w3.org/2000/svg","circle");circle.setAttribute("cx",x);circle.setAttribute("cy",y);circle.setAttribute("r","4");var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(xDataValue)+" / "+Math.floor(yDataValue);text.setAttribute("x",x);text.setAttribute("y",y);text.setAttribute("dx","1em");text.setAttribute("dy","-.7em");pointGroup.appendChild(circle);pointGroup.appendChild(text);}
parent.appendChild(pointGroup);}
xTransform=numericTransformer(xAccessor.min(),xAccessor.max(),0+gutter,width-gutter);yTransform=numericTransformer(yAccessor.min(),yAccessor.max(),height-gutter,0+gutter);axisRenderer("x",xTransform.toData);axisRenderer("y",yTransform.toData);lineRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);pointRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);}
function renderGraphSvg(dataArray,renderId){var figure=document.getElementById(renderId);while(figure.hasChildNodes()){figure.removeChild(figure.lastChild);}
var svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 640 440");svg.setAttribute("preserveAspectRatio","xMidYMid meet");lineGraph(svg,(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].x;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.x;})),Math.max.apply(Math,dataArray.map(function(o){return o.x;}))),(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].y;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.y;})),Math.max.apply(Math,dataArray.map(function(o){return o.y;}))));figure.appendChild(svg);}
)=====";
const uint8_t JS_GRAPH_GZIP[1280] PROGMEM = { 31,139,8,0,3,199,199,101,2,255,205,87,95,111,219,54,16,127,247,167,112,4,52,16,99,89,86,27,175,3,170,240,33,109,135,174,64,18,20,77,48,96,24,246,192,73,180,76,76,150,4,138,182,69,184,254,238,59,146,162,36,59,82,134,58,41,182,135,56,34,239,47,239,126,119,71,46,214,89,36,88,158,141,83,150,209,79,156,20,75,183,32,156,102,194,171,174,163,136,150,101,206,61,105,191,208,46,202,179,82,140,183,44,22,75,252,246,77,16,154,245,146,178,100,41,240,188,217,72,214,66,80,142,231,118,93,176,138,166,229,23,202,31,88,244,55,190,12,194,133,53,155,173,87,148,179,232,129,147,172,92,228,28,22,110,76,4,185,101,153,167,255,147,202,43,42,181,130,95,82,161,221,134,240,177,34,124,100,139,5,174,57,166,86,162,168,244,182,102,157,26,49,69,250,74,192,20,54,196,153,21,246,162,60,231,177,33,217,189,153,225,9,57,21,107,158,237,68,254,65,241,188,179,206,106,207,208,206,80,245,194,90,70,23,141,157,137,182,27,238,61,145,127,132,189,86,88,219,107,164,245,202,248,136,46,90,87,38,181,194,112,191,15,247,163,38,74,164,98,229,87,154,197,148,67,124,114,206,32,63,138,59,243,132,141,155,137,140,226,251,196,243,117,129,227,60,130,200,102,194,143,56,37,130,254,146,82,181,186,187,119,157,165,16,197,187,217,108,187,221,250,219,75,63,231,201,236,77,16,4,179,114,147,56,158,147,56,40,180,138,190,16,72,242,137,122,10,144,5,85,141,63,126,73,197,181,16,156,253,181,22,212,117,162,148,148,165,227,117,78,50,113,166,138,185,54,175,194,130,13,136,234,53,169,176,70,221,180,179,43,21,151,193,222,225,54,48,215,107,182,232,134,11,99,236,84,14,218,217,211,29,121,21,131,227,183,99,103,162,172,79,28,248,144,230,227,70,239,145,170,217,67,33,132,220,85,182,24,214,217,102,87,88,49,132,108,50,65,59,176,233,178,169,206,236,43,247,0,249,23,151,8,92,8,206,207,217,25,214,130,38,107,130,86,226,212,64,43,89,8,154,250,231,179,44,163,252,215,135,219,27,156,209,237,24,208,71,221,91,117,202,69,10,248,114,27,168,184,12,33,228,139,252,38,143,72,74,31,216,138,222,67,8,178,196,173,213,28,6,165,114,60,214,75,144,142,103,98,209,67,139,129,232,188,166,171,3,8,144,162,0,8,127,88,178,52,118,149,12,2,144,239,33,56,244,180,124,52,123,208,22,58,249,144,58,15,87,88,234,188,180,249,208,190,190,58,108,68,77,46,100,155,11,216,127,145,10,74,56,139,111,160,167,62,179,130,94,30,27,67,144,24,72,126,53,148,99,57,4,140,24,164,156,169,255,147,78,255,16,56,252,75,77,182,81,250,183,204,179,227,50,84,166,109,170,14,144,101,53,14,209,13,242,6,80,105,37,52,52,71,253,76,22,172,40,52,131,242,17,209,106,104,219,183,154,173,77,251,238,153,171,94,213,204,63,79,62,28,182,244,244,249,24,106,44,250,156,66,156,161,206,229,163,29,40,146,179,150,111,73,202,59,136,147,139,190,125,59,147,143,119,237,20,131,51,42,23,149,25,211,66,176,201,90,115,132,246,176,126,102,36,77,205,182,12,242,152,33,220,66,32,169,219,227,203,249,121,175,43,173,245,9,54,32,25,61,233,192,72,121,48,122,210,133,253,40,125,140,201,122,102,57,37,220,87,168,26,84,105,47,110,91,119,122,1,146,106,104,118,176,81,228,44,19,167,130,67,11,63,187,93,181,90,6,142,172,175,58,154,171,252,193,112,250,222,228,235,203,129,186,103,253,70,210,53,197,199,217,54,151,7,220,197,67,195,108,136,178,21,150,125,194,18,119,145,114,36,28,49,30,165,39,87,167,145,134,128,154,143,227,216,171,222,59,68,83,179,119,128,198,65,243,188,158,65,63,116,120,116,34,9,69,61,131,162,234,16,187,145,26,152,43,131,151,138,225,161,242,250,201,145,50,245,127,214,244,14,156,187,149,103,162,53,72,174,111,36,163,158,154,109,37,20,67,11,38,220,243,120,105,17,184,98,153,139,188,206,154,84,176,14,38,230,102,234,117,111,179,80,65,79,42,149,71,74,229,145,210,131,59,112,99,194,76,185,166,181,232,160,55,90,125,243,60,57,230,81,241,127,204,243,29,243,203,175,31,77,135,106,244,86,29,250,151,80,212,233,159,92,171,211,47,215,251,77,162,95,101,215,156,19,233,25,194,231,216,52,137,5,75,214,188,83,170,9,21,117,37,188,151,159,99,183,97,174,199,143,97,87,157,70,67,224,46,143,105,169,250,77,189,207,233,42,223,80,131,142,122,11,90,165,208,27,168,158,138,80,70,167,214,158,250,69,33,252,30,193,124,195,232,246,125,174,42,33,24,7,227,183,243,96,60,159,7,189,156,133,106,196,124,67,175,203,130,70,66,63,45,65,10,174,83,241,239,240,55,94,81,42,234,9,102,222,252,160,193,115,15,222,185,30,64,205,91,217,39,55,195,129,125,21,215,221,183,125,216,218,246,61,102,87,74,208,79,105,150,136,37,188,128,179,126,54,197,244,7,220,204,255,244,43,96,210,19,163,203,165,76,237,149,245,30,209,149,126,89,131,87,125,52,184,247,171,87,51,234,96,64,119,36,16,82,229,156,74,253,26,242,26,42,20,79,209,158,57,111,20,229,202,47,132,106,97,82,157,34,140,254,131,104,202,255,105,52,229,115,162,169,133,225,74,86,87,89,183,45,3,102,161,214,254,1,223,145,42,131,193,18,0,0 };

View File

@@ -1,5 +1,5 @@
const char HTML_INDEX[] PROGMEM = R"=====( const char HTML_INDEX[] PROGMEM = R"=====(
<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html> <!doctype html><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width,initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><link rel=stylesheet href=/css/custom.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script><script src=/js/custom.js></script><body onload=start()><div><h4><div id=mainHeader>Control</div><span id=conStatus class=label>Offline</span></h4></div><hr><div class=container><div id=row class="row u-full-width"></div><ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul><div id=tabscontent class="tabscontent u-full-width"></div></div>
)====="; )=====";
const uint8_t HTML_INDEX_GZIP[916] PROGMEM = { 31,139,8,0,3,199,199,101,2,255,133,148,235,115,162,58,20,192,255,21,174,159,238,157,221,22,95,181,237,174,56,19,20,108,85,68,64,240,241,45,64,42,193,240,40,9,162,254,245,155,128,157,238,157,189,211,235,12,201,201,57,191,243,200,17,206,240,175,137,57,94,239,86,154,20,177,132,140,134,245,42,13,35,4,195,209,48,65,12,74,65,4,11,138,152,82,178,183,187,167,209,144,97,70,208,104,156,165,172,200,200,80,110,142,13,153,194,4,41,39,140,170,60,43,152,20,112,4,165,76,105,85,56,100,145,18,162,19,14,208,93,125,248,46,225,20,51,12,201,29,13,32,65,74,167,53,26,18,156,30,165,2,17,165,69,35,238,30,148,76,194,60,68,75,138,10,244,166,132,144,193,31,56,129,7,36,231,233,225,167,15,41,26,244,191,99,79,53,237,170,61,159,30,50,192,127,75,199,141,52,247,192,37,85,28,129,53,6,134,216,179,133,245,188,22,130,58,13,213,181,171,1,176,152,174,198,242,57,82,45,174,28,171,177,163,207,150,220,58,152,113,223,195,43,87,174,68,188,49,24,240,53,20,158,102,46,162,14,18,190,232,189,113,80,18,237,73,196,91,233,170,225,105,110,205,118,188,141,221,237,188,91,93,130,237,158,254,46,30,167,231,149,118,207,227,114,179,155,221,78,226,116,59,165,83,219,248,195,229,89,119,58,183,184,31,231,57,163,139,188,170,220,155,150,143,34,190,150,218,206,154,24,64,205,64,239,184,82,171,235,19,126,152,95,113,118,62,244,151,94,224,246,74,109,246,82,223,116,51,91,216,109,11,96,224,134,78,173,32,149,163,147,171,232,73,167,2,96,18,6,170,133,177,143,106,91,216,118,220,142,174,126,211,116,45,10,52,227,219,75,190,171,68,35,212,157,227,218,170,167,198,235,104,39,207,38,20,28,198,154,195,250,36,223,202,167,231,120,97,237,103,131,119,253,153,245,142,17,184,196,157,243,214,157,251,86,95,245,178,106,82,189,155,187,131,118,62,30,55,125,188,27,144,21,212,219,3,251,221,120,218,189,50,179,180,125,3,94,7,231,216,167,37,157,128,141,28,22,57,81,15,253,199,120,53,120,30,4,91,237,201,95,129,14,114,39,104,222,21,213,205,182,182,190,121,177,143,187,173,77,204,100,121,217,111,244,246,222,2,23,99,162,245,22,107,208,89,172,181,190,55,121,189,154,49,104,155,177,123,158,95,193,133,203,213,89,123,120,70,149,248,43,188,182,237,69,237,253,148,251,173,115,230,119,237,124,159,30,129,17,131,243,242,210,174,150,78,251,108,234,214,197,184,102,213,114,146,117,12,135,86,70,156,85,198,194,93,159,131,64,148,176,15,53,111,23,234,203,211,62,181,123,187,237,140,128,151,176,23,94,30,114,63,97,215,93,87,175,246,206,195,41,72,144,255,24,87,176,110,169,70,244,245,209,41,173,100,60,254,237,77,166,236,66,16,141,16,98,205,75,44,7,148,202,105,86,36,144,224,43,186,231,167,255,131,107,101,3,210,160,192,57,147,104,17,40,114,76,229,43,202,89,118,159,224,244,62,230,70,185,177,254,65,81,130,67,84,124,137,28,10,152,71,95,18,65,243,173,211,47,33,6,125,31,133,183,111,254,223,164,92,15,19,105,232,103,225,69,202,82,146,193,80,137,225,9,54,246,31,148,193,130,253,253,207,79,78,132,248,36,102,79,191,17,37,28,42,9,196,233,11,119,71,197,231,200,105,40,154,195,84,16,60,165,195,32,43,169,20,16,72,169,66,160,143,200,200,124,123,227,157,69,188,8,142,113,90,174,131,222,92,163,226,150,160,241,16,69,243,52,168,248,76,91,100,213,205,216,18,98,121,247,86,18,210,204,46,62,170,110,97,74,34,80,126,113,154,194,143,88,45,46,226,3,100,56,75,165,79,241,78,64,127,68,41,201,103,66,1,220,154,247,17,233,119,213,127,23,240,177,137,198,214,119,172,103,247,47,103,53,186,226,210,5,0,0 }; const uint8_t HTML_INDEX_GZIP[911] PROGMEM = { 31,139,8,0,0,0,0,0,0,255,140,148,223,147,162,56,16,199,255,21,207,167,187,218,157,69,196,113,103,238,212,170,70,97,102,29,81,1,193,31,111,33,100,32,24,8,146,32,234,95,127,21,157,217,221,171,189,154,187,7,77,167,251,147,111,119,154,84,15,126,139,57,150,231,146,180,82,153,179,209,32,39,18,181,112,138,42,65,228,176,150,175,119,15,163,129,164,146,145,209,152,23,178,226,108,160,221,182,55,178,64,57,25,30,41,105,74,94,201,22,230,133,36,133,28,182,27,26,203,116,24,147,35,197,228,238,186,249,76,11,42,41,98,119,2,35,70,134,122,123,52,96,180,216,183,42,194,134,109,145,242,74,226,90,182,40,230,69,187,149,86,228,117,24,35,137,254,164,57,74,136,86,22,201,95,17,18,164,223,251,76,67,115,225,53,157,151,167,132,3,0,204,253,32,181,130,4,0,76,181,5,119,12,142,90,249,204,125,92,41,195,124,138,205,85,96,1,204,158,150,99,237,148,154,46,0,140,205,204,183,167,115,0,179,63,77,0,146,111,46,192,82,233,141,161,15,0,177,58,185,40,149,106,63,7,0,219,24,227,154,89,15,74,111,105,155,78,104,5,87,86,15,215,94,87,63,184,93,70,61,195,62,168,159,111,132,181,103,132,7,239,109,93,116,245,220,239,234,181,127,141,217,7,101,79,187,79,47,110,87,87,124,237,25,182,202,107,106,198,83,253,85,233,91,133,231,175,152,3,38,7,99,191,52,155,203,3,189,127,185,80,126,74,122,243,16,7,70,109,77,159,175,55,93,79,103,94,199,5,10,65,236,95,29,172,241,109,118,81,61,209,27,128,73,140,77,151,210,136,92,99,113,199,15,116,219,252,100,217,86,138,45,231,211,115,185,109,84,35,204,173,31,120,102,104,102,171,116,171,77,39,2,146,177,229,203,30,43,55,218,241,49,155,185,187,105,255,96,63,74,99,159,194,57,211,79,155,224,37,114,123,102,200,155,73,115,88,108,19,235,180,223,175,123,116,219,103,75,100,119,250,222,193,121,216,126,147,139,218,139,28,116,233,159,178,72,212,98,2,107,45,174,74,102,38,189,175,217,178,255,216,199,27,235,33,90,130,78,130,9,121,233,170,234,166,27,207,94,63,123,251,237,198,99,139,124,126,222,173,237,206,206,133,179,51,177,140,217,10,244,217,202,234,133,147,111,151,69,6,157,69,22,156,94,46,112,94,100,208,156,172,251,71,210,168,79,17,118,188,48,237,236,158,236,206,110,85,202,168,235,149,187,98,15,78,6,167,249,185,211,204,253,206,105,97,187,103,231,194,155,249,132,235,142,47,26,39,227,141,51,11,86,39,140,85,9,187,216,10,183,177,61,63,238,10,207,216,110,166,12,158,99,35,62,223,151,81,46,47,219,174,221,236,252,251,35,206,73,244,53,107,208,181,165,22,179,87,123,191,118,243,241,248,167,151,44,228,153,17,145,18,34,111,143,88,195,66,104,5,175,114,196,232,133,124,193,66,252,23,124,117,254,31,16,215,66,242,252,70,10,92,209,82,182,68,133,135,90,38,180,11,41,37,255,146,211,226,75,38,70,3,237,22,253,133,18,140,198,164,250,16,73,42,84,166,31,18,248,54,20,196,135,144,68,81,68,226,183,225,240,177,220,237,82,255,64,34,30,159,91,188,96,28,197,67,33,81,37,127,255,99,52,136,233,113,52,72,123,87,163,69,227,97,142,104,241,76,80,76,170,31,115,234,202,136,18,21,10,192,188,240,37,146,181,104,97,134,132,24,50,20,17,54,90,188,190,50,90,144,129,166,176,209,64,83,138,183,115,105,117,211,190,209,170,118,68,11,82,125,79,88,241,230,45,214,86,102,125,247,90,51,118,27,117,237,119,141,154,41,82,162,72,20,232,93,169,93,160,35,77,144,164,188,104,253,48,239,20,244,139,72,205,190,167,83,241,183,6,190,11,253,236,250,215,244,215,255,191,3,0,0,255,255,255,40,204,240,224,5,0,0 };

View File

@@ -1,5 +1,5 @@
const char CSS_NORMALIZE[] PROGMEM = R"=====( const char CSS_NORMALIZE[] PROGMEM = R"=====(
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:visible}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:initial}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:visible}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
)====="; )=====";
const uint8_t CSS_NORMALIZE_GZIP[859] PROGMEM = { 31,139,8,0,3,199,199,101,2,255,149,84,237,110,163,58,16,125,149,168,171,74,187,146,137,104,247,163,87,70,247,73,162,252,24,236,1,124,227,47,217,38,77,22,241,238,59,6,66,147,54,93,233,254,2,6,123,230,204,57,103,166,75,70,15,141,179,169,104,192,40,125,230,17,108,44,34,6,213,84,133,137,69,194,83,42,162,250,141,5,200,255,250,152,248,83,89,62,86,197,43,214,7,149,238,255,29,107,39,207,131,129,208,42,203,203,17,66,82,66,35,131,168,36,50,137,9,148,142,172,81,173,0,159,148,179,249,181,15,200,26,231,18,6,214,33,200,252,104,131,235,61,51,160,44,51,104,123,102,225,200,34,138,233,70,236,13,165,63,15,82,69,175,225,204,107,237,196,97,132,94,42,199,4,216,35,68,230,131,107,3,198,200,142,84,213,173,39,149,213,202,98,49,93,168,142,152,161,129,46,64,171,214,242,26,34,230,191,115,34,110,93,250,186,19,196,76,112,58,238,191,173,41,172,179,88,117,168,218,46,81,119,187,78,73,137,118,207,18,26,250,157,240,230,220,8,67,13,226,144,123,177,178,16,78,187,192,83,32,134,61,4,180,105,4,14,212,209,145,200,225,157,35,56,131,235,83,134,144,105,171,235,176,75,42,105,220,15,181,11,196,73,81,187,148,156,225,79,254,180,145,244,138,114,172,89,36,120,182,157,21,124,157,65,213,78,203,81,54,118,14,198,116,214,200,85,162,30,197,216,61,45,65,146,140,63,163,169,22,149,182,191,94,208,108,202,145,62,15,87,136,249,151,166,41,171,25,246,151,178,44,199,104,64,235,171,20,255,144,218,177,39,20,189,191,138,190,252,124,172,38,154,47,44,85,222,69,149,149,227,1,137,35,106,248,83,238,115,166,228,60,47,182,63,209,228,220,195,210,117,177,125,206,17,101,218,133,14,226,40,30,219,73,38,30,200,59,223,134,204,96,163,221,43,63,170,168,106,141,227,236,172,139,21,159,168,197,31,165,63,141,93,24,10,227,126,19,159,167,12,88,217,150,103,157,73,144,28,170,62,9,175,146,123,74,185,150,130,62,185,81,56,114,246,161,150,228,58,100,17,140,191,153,40,227,172,35,193,5,178,245,173,122,35,139,80,141,117,79,45,90,166,172,239,19,115,62,205,222,39,70,200,239,44,207,24,185,5,134,89,7,101,59,26,206,52,101,88,63,214,97,155,51,125,100,98,169,48,167,28,166,177,157,124,216,184,96,102,167,46,39,58,218,7,155,9,200,46,157,61,254,251,48,199,31,246,236,58,72,147,133,233,93,140,180,50,138,130,195,101,57,128,247,8,84,68,32,159,147,84,162,15,145,90,240,78,17,173,97,41,185,163,129,1,194,40,247,215,197,215,224,176,92,146,216,64,175,211,114,137,243,73,193,198,137,62,22,202,90,218,24,211,189,143,241,213,45,149,7,41,179,168,229,56,29,29,174,45,106,137,7,208,227,117,63,162,67,113,32,225,223,183,14,180,28,30,242,76,174,46,89,199,243,244,190,198,114,199,246,166,198,240,176,39,116,11,55,19,180,34,122,101,139,107,241,63,61,79,123,225,246,252,176,0,159,252,119,35,3,113,46,186,251,50,100,221,27,133,90,86,127,243,255,229,226,255,26,143,187,24,222,240,207,145,66,100,24,250,94,203,159,94,145,40,92,128,188,60,238,117,52,89,119,106,137,12,121,145,58,47,200,232,180,146,155,168,52,77,194,58,30,155,103,255,38,209,246,59,237,147,205,246,215,243,244,120,201,203,69,99,139,86,222,115,204,58,132,183,131,127,153,213,143,251,55,101,247,94,22,55,77,174,6,31,145,95,94,170,229,71,94,6,75,1,201,82,55,188,21,252,3,26,45,63,124,152,7,0,0 }; const uint8_t CSS_NORMALIZE_GZIP[846] PROGMEM = { 31,139,8,0,0,0,0,0,0,255,148,84,209,110,235,54,12,253,149,0,23,5,118,1,185,112,187,245,118,144,177,47,41,242,64,91,180,205,69,18,5,137,78,147,26,254,247,65,182,227,38,107,58,96,79,113,40,145,58,228,57,135,189,56,59,182,236,165,104,193,145,61,235,4,62,21,9,35,181,85,225,82,33,120,146,34,209,7,22,96,254,30,146,232,167,178,124,168,138,119,172,15,36,247,79,167,154,205,121,116,16,59,242,186,156,32,10,53,22,21,36,50,168,12,10,144,77,170,165,174,129,32,196,62,127,14,17,85,203,44,24,85,143,96,242,79,23,121,8,202,1,121,229,208,15,202,195,81,37,108,230,140,52,56,7,241,60,26,74,193,194,89,215,150,155,195,4,131,33,86,13,248,35,36,21,34,119,17,83,82,71,50,200,219,77,242,150,60,22,115,66,117,196,12,13,108,1,150,58,175,107,72,152,79,151,66,218,179,252,246,214,176,151,200,54,237,127,110,37,60,123,172,122,164,174,23,93,78,111,61,25,131,126,175,4,93,176,32,120,115,111,130,177,134,230,144,123,241,166,104,216,114,212,228,73,8,236,4,26,26,161,35,42,208,61,31,49,142,60,72,126,62,143,172,174,227,155,144,88,220,143,53,71,131,177,168,89,132,157,126,10,167,157,97,17,52,83,173,146,68,246,221,194,222,251,2,232,181,44,39,211,250,37,150,228,108,81,147,128,165,102,234,159,214,32,125,160,126,70,87,173,4,61,254,122,69,183,43,39,7,241,112,5,86,255,104,219,178,90,16,255,40,203,114,74,14,172,189,42,241,103,249,48,165,161,86,105,8,87,209,215,151,135,106,158,240,101,64,85,224,68,153,52,29,209,66,238,247,219,177,231,74,194,65,23,143,47,232,114,237,113,109,186,120,124,206,17,114,221,58,13,93,78,233,216,205,12,233,200,44,63,199,60,192,214,242,187,62,82,162,218,226,180,136,234,162,194,39,116,187,63,202,112,154,250,56,22,142,63,138,154,79,25,48,249,78,103,138,209,75,14,85,223,132,55,182,67,196,207,167,96,16,158,26,54,168,14,181,81,33,162,74,224,194,141,153,28,123,78,1,26,84,219,87,245,57,172,39,116,83,61,136,176,87,228,195,32,138,131,44,178,79,104,177,17,149,237,5,17,97,188,40,167,199,72,50,87,216,254,108,62,91,42,125,157,196,250,194,82,114,156,29,43,17,124,106,57,186,69,164,235,141,188,10,118,51,144,55,57,7,252,107,9,239,213,85,40,98,66,185,137,164,161,118,36,251,241,178,17,32,4,132,8,190,65,189,228,87,205,16,19,71,29,152,188,96,92,31,123,51,148,160,182,104,246,215,207,110,193,113,77,50,216,194,96,101,77,210,122,230,174,229,102,72,5,121,143,113,65,242,53,190,233,164,10,96,76,166,179,156,230,171,227,181,56,61,71,7,118,186,234,166,233,177,57,212,124,186,109,26,12,113,54,226,166,141,205,147,167,127,215,95,50,252,224,106,140,123,173,47,83,153,65,21,41,144,47,174,9,255,230,54,15,114,123,123,92,1,207,138,187,30,62,66,108,250,187,195,207,60,183,132,214,84,255,165,247,75,226,255,178,195,29,4,159,216,151,64,209,100,16,246,78,179,223,37,24,108,56,66,94,20,247,186,153,101,58,183,147,80,46,228,230,93,152,216,146,217,37,178,71,140,155,21,118,207,225,147,152,199,223,95,208,237,30,127,61,207,63,175,121,145,88,236,208,155,123,26,217,12,119,107,242,139,47,191,172,90,201,114,189,172,232,134,173,133,144,80,95,62,170,245,32,251,126,173,111,148,244,227,231,123,255,4,0,0,255,255,87,222,197,247,126,7,0,0 };

View File

@@ -1,15 +1,5 @@
const char JS_SLIDER[] PROGMEM = R"=====( const char JS_SLIDER[] PROGMEM = R"=====(
function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find(".slider");slider_fill=slider.find(".slider-fill");slider_handle=slider.find(".slider-handle");slider_label=slider.find(".slider-label");var range_val=parseInt(range.val());slider_fill.css("width",range_val+"%");slider_handle.css("left",range_val+"%");slider_label.find("span").text(range_val);});self.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2){return false;} function rkmd_rangeSlider(e){var n,s,o,t=$(e),a=t.width(),r=t.offset().left,i=t;i.each(function(){n=$(this),n.append(sliderDiscrete_tmplt()),o=n.find('input[type="range"]'),s=n.find(".slider"),slider_fill=s.find(".slider-fill"),slider_handle=s.find(".slider-handle"),slider_label=s.find(".slider-label");var i=parseInt(o.val());slider_fill.css("width",i+"%"),slider_handle.css("left",i+"%"),slider_label.find("span").text(i)}),t.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2)return!1;var n,s,o,t=$(this).parents(".rkmd-slider"),i=t.width(),a=t.offset().left,r=t.find('input[type="range"]').is(":disabled");if(r===!0)return!1;$(this).addClass("is-active"),n=function(e){var s=e.pageX||e.changedTouches[0].pageX,n=s-a;n<=i&&!(n<"0")&&slider_move(t,n,i,!0)},s=function(){$(this).off(o),t.find(".is-active").removeClass("is-active")},o={mousemove:n,touchmove:n,mouseup:s,touchend:s},$(document).on(o)}),t.on("mousedown touchstart",".slider",function(e){if(e.button===2)return!1;var n,s,o,t=$(this).parents(".rkmd-slider"),i=t.width(),a=t.offset().left,r=t.find('input[type="range"]').is(":disabled");if(r===!0)return!1;n=e.pageX-a,n<=i&&!(n<"0")&&slider_move(t,n,i,!0),s=function(){$(this).off(o)},o={mouseup:s,touchend:s},$(document).on(o)})}function sliderDiscrete_tmplt(){var e='<div class="slider"><div class="slider-fill"></div><div class="slider-handle"><div class="slider-label"><span>0</span></div></div></div>';return e}function slider_move(e,t,n,s){var a,o=parseInt(Math.round(t/n*100)),r=e.find(".slider-fill"),c=e.find(".slider-handle"),i=e.find('input[type="range"]');i.next().html(t),r.css("width",o+"%"),c.css({left:o+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none"}),i.val(o),e.find(".slider-handle span").text()!=o&&(e.find(".slider-handle span").text(o),a=e.attr("id").substring(2),s&&websock.send("slvalue:"+o+":"+a))}
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;} )=====";
$(this).addClass("is-active");var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find(".is-active").removeClass("is-active");};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu,};$(document).on(handlers);});self.on("mousedown touchstart",".slider",function(e){if(e.button===2){return false;}
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;} const uint8_t JS_SLIDER_GZIP[730] PROGMEM = { 31,139,8,0,0,0,0,0,0,255,212,84,193,110,227,54,16,253,21,155,72,21,78,67,51,222,61,74,102,46,237,165,135,158,218,67,129,197,34,160,197,81,68,68,30,10,228,200,105,235,245,191,23,148,236,216,137,141,32,215,94,108,97,222,96,248,248,222,240,53,3,213,236,3,205,226,243,198,61,70,75,79,248,71,231,29,70,137,176,219,218,56,35,149,84,80,108,110,36,130,178,134,245,139,119,220,74,80,209,176,14,77,147,144,37,232,14,27,86,222,112,229,53,218,186,149,199,177,18,118,100,110,36,183,62,129,34,109,251,30,201,201,52,158,240,171,79,117,68,198,71,222,244,29,75,0,21,12,233,198,147,147,183,158,250,129,191,241,63,61,26,49,146,18,223,111,65,165,35,46,244,52,66,128,154,62,30,27,223,117,38,189,69,23,185,120,106,105,45,185,14,47,154,166,242,169,173,179,107,188,28,53,86,5,84,89,18,111,122,27,19,254,70,44,131,222,218,78,2,84,103,52,116,157,146,20,163,76,66,249,59,241,211,123,10,83,67,150,236,61,62,158,114,56,57,245,150,4,104,198,191,89,122,216,131,98,29,72,138,77,24,18,186,240,66,51,14,67,221,38,182,145,133,122,127,27,245,106,0,194,206,55,18,245,122,96,14,100,140,249,10,17,121,136,52,255,82,189,245,119,52,73,247,54,34,113,146,66,231,141,88,188,234,236,207,172,183,23,214,231,101,248,192,57,237,147,20,165,243,201,174,59,116,2,42,223,200,104,140,153,47,79,100,142,4,172,115,191,116,54,43,228,211,194,214,236,183,217,28,50,231,55,202,196,147,65,221,219,39,252,235,199,15,212,117,155,143,114,127,102,73,48,125,91,126,159,32,69,38,45,108,69,43,227,139,98,46,105,37,150,2,138,226,160,246,38,108,81,178,34,229,213,124,9,123,149,204,217,218,30,233,132,166,145,33,139,127,216,135,51,82,58,98,30,113,201,118,175,130,217,141,70,101,188,36,53,90,117,248,30,235,67,95,166,169,138,228,202,180,87,55,210,133,122,216,32,49,100,155,195,103,13,255,63,58,77,71,231,22,86,125,202,154,143,156,57,105,253,25,77,247,175,121,119,61,131,198,205,66,115,187,114,126,59,171,179,177,70,28,132,121,184,172,77,1,243,176,186,119,126,123,13,62,60,198,107,208,148,39,15,171,252,202,31,150,171,251,241,255,48,232,236,247,182,154,84,155,225,123,230,147,68,168,178,72,105,226,109,85,56,37,211,239,150,91,29,195,64,78,242,61,253,252,101,185,132,156,217,120,61,34,235,11,224,53,22,253,17,186,110,120,229,53,229,136,2,221,242,166,147,12,42,190,9,192,48,5,92,61,22,119,121,133,202,177,164,56,90,74,62,223,168,20,20,8,133,18,139,23,92,63,123,94,156,32,113,194,54,225,223,43,192,30,148,31,51,56,128,186,126,133,217,121,144,194,220,132,162,144,159,232,12,121,249,81,91,230,40,133,119,2,116,26,214,137,163,167,39,249,21,84,42,138,23,92,167,80,63,235,132,99,92,119,91,219,13,88,138,187,112,39,74,113,103,1,246,255,5,0,0,255,255,129,21,153,100,93,7,0,0 };
var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}
var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu,};$(document).on(handlers);});}
function sliderDiscrete_tmplt(){var tmplt='<div class="slider">'+
'<div class="slider-fill"></div>'+
'<div class="slider-handle"><div class="slider-label"><span>0</span></div></div>'+
"</div>";return tmplt;}
function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round((newW/sliderW)*100));var slider_fill=parents.find(".slider-fill");var slider_handle=parents.find(".slider-handle");var range=parents.find('input[type="range"]');range.next().html(newW);slider_fill.css("width",slider_new_val+"%");slider_handle.css({left:slider_new_val+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none",});range.val(slider_new_val);if(parents.find(".slider-handle span").text()!=slider_new_val){parents.find(".slider-handle span").text(slider_new_val);var number=parents.attr("id").substring(2);if(send)websock.send("slvalue:"+slider_new_val+":"+number);}}
)=====";
const uint8_t JS_SLIDER_GZIP[881] PROGMEM = { 31,139,8,0,3,199,199,101,2,255,237,86,77,143,218,48,16,189,243,43,88,107,187,196,93,240,210,61,18,204,165,85,165,30,122,106,165,86,90,173,144,73,156,141,69,112,162,216,129,182,44,255,189,227,143,132,36,192,106,219,83,15,61,37,246,60,143,223,204,60,123,156,84,50,210,34,151,195,114,189,137,151,37,147,79,252,75,38,98,94,6,138,103,60,210,121,137,247,91,86,14,97,148,140,149,181,44,119,34,214,105,61,200,147,68,113,61,142,170,82,106,63,247,65,168,168,228,154,143,173,59,63,25,26,15,244,250,232,54,108,123,163,198,74,236,111,208,88,156,107,103,114,255,1,38,25,79,116,216,221,199,34,122,115,132,179,40,13,18,31,93,32,198,91,188,183,28,129,130,78,133,194,161,29,17,86,20,92,198,65,119,241,82,111,138,12,54,195,161,141,128,58,104,34,0,56,18,178,168,244,131,254,89,112,138,172,21,61,142,106,198,109,32,34,110,14,53,225,36,34,203,168,251,239,66,38,198,114,196,165,76,198,25,63,143,116,182,35,54,99,43,126,193,169,53,1,210,84,207,18,93,110,89,70,11,86,42,254,73,234,192,78,17,152,50,97,182,24,146,72,169,0,217,74,160,113,179,238,22,189,233,19,116,64,83,142,75,56,203,192,179,82,5,147,8,19,205,127,248,173,13,26,135,7,28,186,242,202,0,109,242,74,241,56,223,201,161,206,171,40,85,154,149,224,186,31,250,184,169,41,199,123,145,4,156,172,42,173,115,73,41,189,199,123,168,30,148,96,152,176,76,241,240,48,48,177,67,196,92,106,85,215,157,248,49,36,202,72,126,210,84,201,170,188,45,73,15,108,84,217,2,120,101,214,136,174,56,13,46,74,121,180,118,199,169,65,189,160,31,34,128,207,44,22,138,173,50,30,3,25,8,172,237,130,82,93,86,252,36,188,58,36,22,199,239,51,102,202,33,212,132,65,118,182,220,7,180,201,183,252,99,69,219,57,115,57,121,226,223,41,39,246,251,252,12,181,76,205,62,241,87,147,120,174,30,166,143,206,212,142,89,242,93,147,24,48,77,58,169,48,132,251,184,57,109,167,243,230,230,234,20,129,166,8,227,189,159,54,92,3,159,172,113,31,218,189,123,108,54,194,195,193,242,171,138,94,132,117,90,128,90,224,84,83,194,121,239,148,1,145,86,166,72,201,205,222,103,82,232,54,168,125,208,189,149,168,193,206,92,98,199,86,169,237,9,139,168,138,153,33,229,172,112,189,184,209,33,188,14,226,60,170,54,64,3,27,197,31,185,189,254,24,252,215,127,47,188,19,117,122,85,255,11,250,28,252,129,60,207,74,237,111,132,116,24,212,155,13,207,119,53,123,3,216,127,58,154,199,98,59,140,140,240,41,242,74,88,140,110,7,103,230,93,147,90,204,239,192,114,9,226,175,232,197,25,147,235,70,139,185,233,3,139,233,252,206,126,157,179,198,37,114,127,40,244,101,182,28,79,227,233,22,2,42,240,205,39,31,190,144,35,255,94,57,214,167,211,246,62,51,157,146,50,175,64,138,129,89,122,231,151,226,183,239,166,83,220,17,185,237,215,189,91,163,219,176,91,96,223,180,207,195,155,174,221,244,226,215,28,10,247,248,32,210,180,76,76,82,189,201,44,227,203,221,186,27,243,165,150,189,55,135,116,118,138,5,209,50,169,132,73,244,12,201,92,66,171,69,147,29,95,173,133,158,28,77,232,104,219,228,191,206,25,14,53,113,243,182,232,110,99,207,245,75,41,26,182,159,9,248,138,246,150,239,95,189,182,191,175,73,188,172,54,43,120,163,213,62,152,214,37,220,246,112,219,16,85,173,148,46,133,124,10,238,45,69,43,35,8,93,229,209,154,152,1,188,95,50,240,83,241,25,186,237,103,14,166,156,103,211,144,126,3,128,124,107,46,79,11,0,0 };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

96
tools/README.md Normal file
View File

@@ -0,0 +1,96 @@
# ESPUI Build Tools
This directory contains tools for preparing ESPUI's static web assets (HTML, CSS, JavaScript) for embedding in the ESP firmware.
## prepare_static_ui_sources
This tool processes frontend files from the `data/` directory:
1. **Minifies** HTML, CSS, and JavaScript files to reduce size
2. **Compresses** the minified content using gzip
3. **Generates** C header files with `PROGMEM` constants for the `src/` directory
The generated headers contain both raw minified strings and gzipped byte arrays, allowing ESPUI to serve web content directly from flash memory.
### Prerequisites
**Go version:**
```bash
# Dependencies are managed via go.mod, installed automatically
go mod tidy
```
### Usage
Both versions support the same command-line interface:
```bash
# Auto mode - process all files in data/ and output to src/
# Go:
go run prepare_static_ui_sources.go -a
# Auto mode without writing intermediate .min.* files
# Go:
go run prepare_static_ui_sources.go -a -m
# Explicit source and target directories
# Go:
go run prepare_static_ui_sources.go -s ../data -t ../src
# Process a single file
# Go:
go run prepare_static_ui_sources.go -s ../data/js/controls.js -t ../src/dataControlsJS.h
```
### Command-Line Options
| Flag | Long Form | Description |
|------|-----------|-------------|
| `-a` | `--auto`, `--all` | Auto mode: find all source files in `data/` and write headers to `src/` |
| `-s` | `--source`, `--sources` | Source directory or file to process |
| `-t` | `--target` | Target directory or file for output |
| `-m` | `--nostoremini` | Skip writing intermediate `.min.*` files |
### Output Format
The tool generates C header files with this structure:
```c
const char JS_CONTROLS[] PROGMEM = R"=====(
// minified content here
)=====";
const uint8_t JS_CONTROLS_GZIP[4975] PROGMEM = { 31,139,8,0,... };
```
### File Naming Convention
Source files are mapped to header files as follows:
| Source File | Header File | Constants |
|-------------|-------------|-----------|
| `data/index.htm` | `src/dataIndexHTML.h` | `HTML_INDEX`, `HTML_INDEX_GZIP` |
| `data/css/style.css` | `src/dataStyleCSS.h` | `CSS_STYLE`, `CSS_STYLE_GZIP` |
| `data/js/controls.js` | `src/dataControlsJS.h` | `JS_CONTROLS`, `JS_CONTROLS_GZIP` |
### Building the Go Version
To compile the Go tool into a standalone binary:
```bash
cd tools
go build -o prepare_static_ui_sources prepare_static_ui_sources.go
./prepare_static_ui_sources -a
```
### Development Workflow
After modifying any file in `data/`:
1. Run the tool to regenerate headers:
```bash
cd tools
go run prepare_static_ui_sources.go -a
```
2. Commit both the modified source files and the regenerated headers in `src/`

7
tools/go.mod Normal file
View File

@@ -0,0 +1,7 @@
module github.com/me-no-dev/ESPUItools
go 1.21
require github.com/tdewolff/minify/v2 v2.21.2
require github.com/tdewolff/parse/v2 v2.7.19 // indirect

7
tools/go.sum Normal file
View File

@@ -0,0 +1,7 @@
github.com/tdewolff/minify/v2 v2.21.2 h1:VfTvmGVtBYhMTlUAeHtXM7XOsW0JT/6uMwUPPqgUs9k=
github.com/tdewolff/minify/v2 v2.21.2/go.mod h1:Olje3eHdBnrMjINKffDsil/3NV98Iv7MhWf7556WQVg=
github.com/tdewolff/parse/v2 v2.7.19 h1:7Ljh26yj+gdLFEq/7q9LT4SYyKtwQX4ocNrj45UCePg=
github.com/tdewolff/parse/v2 v2.7.19/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=

View File

@@ -0,0 +1,359 @@
// Package main provides a tool to prepare ESPUI header files by minifying
// and gzipping HTML, JS, and CSS source files.
//
// Usage:
//
// go run prepare_static_ui_sources.go -a
// go run prepare_static_ui_sources.go -s ../data -t ../src
//
// Or build and run:
//
// go build -o prepare_static_ui_sources prepare_static_ui_sources.go
// ./prepare_static_ui_sources -a
package main
import (
"bytes"
"compress/gzip"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/css"
"github.com/tdewolff/minify/v2/html"
"github.com/tdewolff/minify/v2/js"
)
const targetTemplate = `const char %s[] PROGMEM = R"=====(
%s
)=====";
const uint8_t %s_GZIP[%d] PROGMEM = { %s };
`
type context struct {
infile string
outfile string
outdir string
outfilename string
minifile string
constant string
fileType string
name string
dir string
minidata string
gzipdata string
gziplen int
}
func main() {
auto := flag.Bool("a", false, "Automatically find all source files in data/ and write C header files to src/")
flag.BoolVar(auto, "auto", false, "Automatically find all source files in data/ and write C header files to src/")
flag.BoolVar(auto, "all", false, "Automatically find all source files in data/ and write C header files to src/")
sources := flag.String("s", "", "Sources directory containing CSS or JS files OR one specific file to minify")
flag.StringVar(sources, "source", "", "Sources directory containing CSS or JS files OR one specific file to minify")
flag.StringVar(sources, "sources", "", "Sources directory containing CSS or JS files OR one specific file to minify")
target := flag.String("t", "", "Target directory containing C header files OR one C header file")
flag.StringVar(target, "target", "", "Target directory containing C header files OR one C header file")
storeMini := flag.Bool("storemini", true, "Store intermediate minified files next to the originals")
noStoreMini := flag.Bool("nostoremini", false, "Do not store intermediate minified files")
flag.BoolVar(noStoreMini, "m", false, "Do not store intermediate minified files")
flag.Parse()
// Handle nostoremini flag
if *noStoreMini {
*storeMini = false
}
if !*auto && (*sources == "" || *target == "") {
fmt.Println("ERROR: You need to specify either --auto/-a or both --source/-s and --target/-t")
fmt.Println()
flag.Usage()
os.Exit(1)
}
// Get the directory where this script is located
execPath, err := os.Executable()
if err != nil {
// Fallback to current working directory
execPath, _ = os.Getwd()
execPath = filepath.Join(execPath, "tools", "dummy")
}
scriptDir := filepath.Dir(execPath)
// For "go run", use the current working directory approach
if strings.Contains(execPath, "go-build") {
cwd, _ := os.Getwd()
scriptDir = cwd
}
// Set default paths if auto mode
if *sources == "" {
*sources = filepath.Join(scriptDir, "..", "data")
}
if *target == "" {
*target = filepath.Join(scriptDir, "..", "src")
}
// Resolve to absolute paths
*sources, _ = filepath.Abs(*sources)
*target, _ = filepath.Abs(*target)
if err := checkArgs(*sources, *target); err != nil {
fmt.Printf("ERROR: %v\n", err)
fmt.Println("Aborting.")
os.Exit(1)
}
info, err := os.Stat(*sources)
if err != nil {
fmt.Printf("ERROR: Cannot stat source: %v\n", err)
os.Exit(1)
}
if info.IsDir() {
fmt.Printf("Source %s is a directory, searching for files recursively...\n", *sources)
if err := processDir(*sources, *target, *storeMini); err != nil {
fmt.Printf("ERROR: %v\n", err)
os.Exit(1)
}
} else {
fmt.Printf("Source %s is a file, will process one file only.\n", *sources)
if err := processFile(*sources, *target, *storeMini); err != nil {
fmt.Printf("ERROR: %v\n", err)
os.Exit(1)
}
}
}
func checkArgs(sources, target string) error {
if _, err := os.Stat(sources); os.IsNotExist(err) {
return fmt.Errorf("source %s does not exist", sources)
}
targetParent := filepath.Dir(target)
if info, err := os.Stat(targetParent); err != nil || !info.IsDir() {
return fmt.Errorf("parent directory of target %s does not exist", target)
}
sourceInfo, _ := os.Stat(sources)
targetInfo, targetErr := os.Stat(target)
if sourceInfo.IsDir() && (targetErr != nil || !targetInfo.IsDir()) {
return fmt.Errorf("source %s is a directory, target %s is not", sources, target)
}
return nil
}
func getContext(infile, outfile string) (*context, error) {
infile, err := filepath.Abs(infile)
if err != nil {
return nil, err
}
// Extract directory, name, and type
dir := filepath.Base(filepath.Dir(infile))
base := filepath.Base(infile)
ext := filepath.Ext(base)
name := strings.TrimSuffix(base, ext)
// Remove any .min suffix from name for constant generation
name = strings.TrimSuffix(name, ".min")
fileType := strings.TrimPrefix(strings.ToLower(ext), ".")
// If directory name matches the file type, go up one level
if strings.ToLower(dir) == fileType {
dir = filepath.Base(filepath.Dir(filepath.Dir(infile)))
}
// Normalize htm to html
if fileType == "htm" {
fileType = "html"
}
indir := filepath.Dir(infile)
c := &context{
infile: infile,
fileType: fileType,
name: name,
dir: dir,
}
// Determine output file
info, err := os.Stat(outfile)
if err == nil && info.IsDir() {
c.outdir, _ = filepath.Abs(outfile)
c.outfilename = fmt.Sprintf("%s%s%s.h", dir, capitalize(name), strings.ToUpper(fileType))
c.outfile = filepath.Join(c.outdir, c.outfilename)
} else {
c.outfile, _ = filepath.Abs(outfile)
c.outdir = filepath.Dir(c.outfile)
c.outfilename = filepath.Base(c.outfile)
}
// Determine minified file path
if strings.Contains(infile, ".min.") {
c.minifile = infile
} else {
// Replace .ext with .min.ext
c.minifile = filepath.Join(indir, strings.TrimSuffix(base, ext)+".min"+ext)
}
// Generate constant name
c.constant = fmt.Sprintf("%s_%s", strings.ToUpper(fileType), strings.ToUpper(name))
return c, nil
}
func capitalize(s string) string {
if len(s) == 0 {
return s
}
return strings.ToUpper(s[:1]) + s[1:]
}
func performGzip(c *context) error {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
if _, err := gz.Write([]byte(c.minidata)); err != nil {
return err
}
if err := gz.Close(); err != nil {
return err
}
compressed := buf.Bytes()
c.gziplen = len(compressed)
// Convert bytes to comma-separated string
parts := make([]string, len(compressed))
for i, b := range compressed {
parts[i] = fmt.Sprintf("%d", b)
}
c.gzipdata = strings.Join(parts, ",")
fmt.Printf(" GZIP data length: %d\n", c.gziplen)
return nil
}
func performMinify(c *context) error {
data, err := os.ReadFile(c.infile)
if err != nil {
return err
}
m := minify.New()
m.AddFunc("text/css", css.Minify)
m.AddFunc("text/html", html.Minify)
m.AddFunc("application/javascript", js.Minify)
m.AddFunc("text/javascript", js.Minify)
var mediaType string
switch c.fileType {
case "css":
mediaType = "text/css"
case "js":
mediaType = "application/javascript"
case "html", "htm":
mediaType = "text/html"
default:
mediaType = "text/html"
}
fmt.Printf(" Using %s minifier\n", c.fileType)
minified, err := m.String(mediaType, string(data))
if err != nil {
return fmt.Errorf("minification failed: %w", err)
}
c.minidata = minified
return performGzip(c)
}
func processFile(infile, outdir string, storeMini bool) error {
fmt.Printf("Processing file %s\n", infile)
c, err := getContext(infile, outdir)
if err != nil {
return err
}
if err := performMinify(c); err != nil {
return err
}
if storeMini {
if c.infile == c.minifile {
fmt.Println(" Original file is already minified, refusing to overwrite it")
} else {
fmt.Printf(" Writing minified file %s\n", c.minifile)
if err := os.WriteFile(c.minifile, []byte(c.minidata), 0644); err != nil {
return err
}
}
}
fmt.Printf(" Using C constant names %s and %s_GZIP\n", c.constant, c.constant)
fmt.Printf(" Writing C header file %s\n", c.outfile)
output := fmt.Sprintf(targetTemplate, c.constant, c.minidata, c.constant, c.gziplen, c.gzipdata)
return os.WriteFile(c.outfile, []byte(output), 0644)
}
func processDir(sourceDir, outDir string, storeMini bool) error {
pattern := regexp.MustCompile(`(?i)\.(css|js|htm|html)$`)
processed := make(map[string]bool)
err := filepath.WalkDir(sourceDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if !pattern.MatchString(path) {
return nil
}
// Skip if already processed
if processed[path] {
return nil
}
// Check if this is a .min. file
if strings.Contains(filepath.Base(path), ".min.") {
// Only process .min. files if the non-minified version doesn't exist
nonMinPath := strings.Replace(path, ".min.", ".", 1)
if _, err := os.Stat(nonMinPath); err == nil {
// Non-minified version exists, skip this .min. file
return nil
}
} else {
// Mark corresponding .min. file as processed to avoid duplicate processing
ext := filepath.Ext(path)
minPath := strings.TrimSuffix(path, ext) + ".min" + ext
processed[minPath] = true
}
processed[path] = true
return processFile(path, outDir, storeMini)
})
return err
}

View File

@@ -1,7 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# script is kept for legacy reasons, please use go version instead!
from jsmin import jsmin as jsminify from jsmin import jsmin as jsminify
from htmlmin import minify as htmlminify try:
from htmlmin import minify as htmlminify
except ImportError:
# Fallback to minify_html for Python 3.13+ compatibility
import minify_html
def htmlminify(html):
return minify_html.minify(html, minify_js=False, minify_css=False)
from csscompressor import compress as cssminify from csscompressor import compress as cssminify
import gzip import gzip
import sys import sys