mirror of
https://github.com/s00500/ESPUI.git
synced 2026-03-13 06:02:40 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
c7b7edf7c9
|
|||
|
79581b6f5c
|
|||
|
fdd455b999
|
|||
|
458e21861d
|
|||
|
b7f18f9e2a
|
|||
| e0f06b4aeb | |||
|
|
92eba52600 | ||
| 32d51125ab | |||
|
0b47135a31
|
|||
|
|
f922db3502 | ||
|
|
c8c41aa077 | ||
|
|
f990016623 | ||
|
|
cea38b4b50 | ||
|
|
8a7195827b | ||
|
|
b68b7ce390 | ||
|
|
9a589296ad | ||
|
|
a05bbb6d9f | ||
|
|
dbb8774e93 | ||
|
|
289fdb2ad7 | ||
|
|
d9412d9d3c | ||
|
|
9a088c090f | ||
|
aed06457b3
|
|||
|
ed9d7d58a4
|
|||
|
bdfd7cd794
|
|||
|
c5e5c8323e
|
|||
|
4971f1c7f9
|
|||
| 415bc52416 | |||
| 4effb15b49 | |||
|
2a6fa24892
|
|||
|
|
368eeff3f8 | ||
|
|
6b28240d24 | ||
|
|
f6da0ed7e8 | ||
|
|
41c75cc41e | ||
|
|
a20fe6a4d6 | ||
|
|
0b16da9856 | ||
|
|
ebe72e769e | ||
|
|
73c3a97cf8 | ||
|
|
af53137b55 | ||
|
|
e54a2ff8e6 | ||
|
|
47c4430a86 | ||
| 4e32746159 | |||
|
|
027bc9c74a | ||
| ceffe2b7b3 | |||
|
|
c42c40d881 | ||
| 0ceb052a2e | |||
|
|
8c729ee0da | ||
|
|
8ca8241547 | ||
| 66340823db | |||
|
|
25250dd026 |
55
README.md
55
README.md
@@ -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
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
2
data/css/normalize.min.css
vendored
2
data/css/normalize.min.css
vendored
@@ -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}
|
||||||
@@ -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
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||||
|
|
||||||
|
|||||||
2
data/css/style.min.css
vendored
2
data/css/style.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -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();">
|
||||||
|
|||||||
@@ -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
45
data/js/controls.js
vendored
@@ -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:
|
||||||
|
|||||||
137
data/js/controls.min.js
vendored
137
data/js/controls.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -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
16
data/js/graph.min.js
vendored
@@ -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
12
data/js/slider.min.js
vendored
@@ -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);}}
|
|
||||||
36
data/js/tabbedcontent.min.js
vendored
36
data/js/tabbedcontent.min.js
vendored
@@ -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&¤t>=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&¤t<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);
|
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "ESP Async WebServer",
|
"name": "ESPAsyncWebServer",
|
||||||
"authors": "Hristo Gochkov",
|
"authors": "ESP32Async",
|
||||||
"frameworks": "arduino"
|
"frameworks": "arduino"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
43
pio_examples/completeExample/platformio.ini
Normal file
43
pio_examples/completeExample/platformio.ini
Normal 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
|
||||||
544
pio_examples/completeExample/src/completeExample.cpp
Normal file
544
pio_examples/completeExample/src/completeExample.cpp
Normal 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';
|
||||||
|
}
|
||||||
43
pio_examples/completeLambda/platformio.ini
Normal file
43
pio_examples/completeLambda/platformio.ini
Normal 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
|
||||||
524
pio_examples/completeLambda/src/completeLambda.cpp
Normal file
524
pio_examples/completeLambda/src/completeLambda.cpp
Normal 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';
|
||||||
|
}
|
||||||
43
pio_examples/gui-generic-api/platformio.ini
Normal file
43
pio_examples/gui-generic-api/platformio.ini
Normal 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
|
||||||
303
pio_examples/gui-generic-api/src/gui-generic-api.cpp
Normal file
303
pio_examples/gui-generic-api/src/gui-generic-api.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
pio_examples/prepareFilesystem/platformio.ini
Normal file
43
pio_examples/prepareFilesystem/platformio.ini
Normal 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
|
||||||
17
pio_examples/prepareFilesystem/src/prepareFilesystem.cpp
Normal file
17
pio_examples/prepareFilesystem/src/prepareFilesystem.cpp
Normal 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()
|
||||||
|
{
|
||||||
|
}
|
||||||
43
pio_examples/tabbedGui/platformio.ini
Normal file
43
pio_examples/tabbedGui/platformio.ini
Normal 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
|
||||||
300
pio_examples/tabbedGui/src/tabbedGui.cpp
Normal file
300
pio_examples/tabbedGui/src/tabbedGui.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
13
src/ESPUI.h
13
src/ESPUI.h
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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 };
|
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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
96
tools/README.md
Normal 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
7
tools/go.mod
Normal 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
7
tools/go.sum
Normal 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=
|
||||||
359
tools/prepare_static_ui_sources.go
Normal file
359
tools/prepare_static_ui_sources.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user