mirror of
https://github.com/s00500/ESPUI.git
synced 2025-07-04 06:10:18 +00:00
Compare commits
146 Commits
Author | SHA1 | Date | |
---|---|---|---|
4e32746159 | |||
027bc9c74a | |||
ceffe2b7b3 | |||
c42c40d881 | |||
0ceb052a2e | |||
8c729ee0da | |||
8ca8241547 | |||
66340823db | |||
25250dd026 | |||
356d5b1ffe
|
|||
2d023cce6d | |||
47db2ad6d8 | |||
8bca2e503b | |||
731e6c68d3 | |||
82460d4aed | |||
7e6390ba0a | |||
164be971e3 | |||
75bbece7b5 | |||
efda9b7140 | |||
9ba3f5a940 | |||
84ff52d8cb | |||
75bd3dc378 | |||
7dc51d1274 | |||
f2146309fe | |||
e6e4d6b09c | |||
09aede269b | |||
4944f0ff04 | |||
f472dc1158 | |||
6dcaf55c84 | |||
dd234135f2 | |||
6724abfef3 | |||
a448fdd180 | |||
ba7fa33dfb | |||
624663076f | |||
c43c2c7b72 | |||
ffe2ce7859 | |||
e9fc63f86d | |||
1ba92a3cd6 | |||
da20c8b0a5 | |||
707c2aa3bf | |||
9efd0c27aa | |||
6a18c59f31 | |||
6e281e302a | |||
d2ccf83874 | |||
ea8de92246 | |||
aa9a62bbdf | |||
f3b105f143 | |||
7508e034be | |||
5b36d79395 | |||
e4c6906723 | |||
dc2d5e1205 | |||
d3c6df5cfa | |||
46ad206a49 | |||
8b64b185a4 | |||
00841ce32d | |||
85ccff0ee0 | |||
0571f65f6f | |||
542ac03656 | |||
8f7cce59bf | |||
ae5b6d5c0a | |||
62b4b56a4d | |||
8a3865e42e | |||
0d9a874ba2 | |||
cf1f81bced | |||
0b22328bd8 | |||
09596a4e65 | |||
8d61abc47d | |||
adf2ba369e | |||
93d0bf7d27 | |||
794d54dc96 | |||
1deddad091 | |||
999d465c79 | |||
83dbb49697 | |||
290f0c6fc6 | |||
7e3de1c14f | |||
136b182813 | |||
43b8cdbfb2 | |||
488a6cb252 | |||
c7a9438149 | |||
03c30fa75c | |||
68c251226d | |||
ec41deab01 | |||
fae30398b2 | |||
de09ddbe09 | |||
679a3b5a8d
|
|||
f8fbcf887a
|
|||
fd1cc14e87
|
|||
372157b980 | |||
f08cd5c51a
|
|||
b45f4f7356 | |||
c3bf9c5d00 | |||
678a2ccf53 | |||
270f0dd16b | |||
be8cf73d55 | |||
cc69f8a469 | |||
48e947287a | |||
5cf0ce6afa | |||
35c2f5b6b1 | |||
b1b9fd2c79 | |||
0aaca5449b | |||
7b4ec5920a | |||
048f337d61 | |||
8e3c12935e | |||
f35d3a19d4 | |||
48594de29a | |||
d53fdb57a9 | |||
b8079cfb63 | |||
e3ccc216bb | |||
09a550e614 | |||
309e53dd1e | |||
4b489c7ce5 | |||
0c9586730e | |||
004a374d79 | |||
e3eea09e43 | |||
bce624b0f2 | |||
1595b3bd04 | |||
a18254b16b | |||
811e0ede4d | |||
ea4a0df4d0 | |||
a2923e501f | |||
fdffb9c041 | |||
05ab1734e6 | |||
79b9acf9f4 | |||
90ba90a717 | |||
95f2f9bdd0 | |||
e81cd1f7f5 | |||
a150d2db1e | |||
86c469f5cb | |||
70acbc1d2f | |||
9d9a165693 | |||
a553d32570 | |||
36f6932538 | |||
ba9f83a93b | |||
782d339f83 | |||
c303668a3c | |||
e0ff99f7b6 | |||
511840fa97 | |||
1419b2dec0 | |||
02e847a31e | |||
9cb962122c | |||
7056cdf044
|
|||
e3debb9689 | |||
e059fe5d79 | |||
83e1d0a1c9 | |||
b28752f9c2 | |||
c63537a358 |
98
README.md
98
README.md
@ -28,6 +28,7 @@ The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker,
|
|||||||
* [Slider](#slider)
|
* [Slider](#slider)
|
||||||
* [Number Input](#number-input)
|
* [Number Input](#number-input)
|
||||||
* [Text Input](#text-input)
|
* [Text Input](#text-input)
|
||||||
|
* [File Display](#filedisplay)
|
||||||
* [Date, Time, Colour and Password Input](#date-time-colour-and-password-input)
|
* [Date, Time, Colour and Password Input](#date-time-colour-and-password-input)
|
||||||
* [Select control](#select-control)
|
* [Select control](#select-control)
|
||||||
* [Getting the Time](#getting-the-time)
|
* [Getting the Time](#getting-the-time)
|
||||||
@ -43,6 +44,7 @@ The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker,
|
|||||||
* [Grouped controls](#grouped-controls)
|
* [Grouped controls](#grouped-controls)
|
||||||
* [Wide controls](#wide-controls)
|
* [Wide controls](#wide-controls)
|
||||||
* [Graph (Experimental)](#graph--experimental-)
|
* [Graph (Experimental)](#graph--experimental-)
|
||||||
|
* [Captive Portal](#captive-portal)
|
||||||
- [Notes for Development](#notes-for-development)
|
- [Notes for Development](#notes-for-development)
|
||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
|
|
||||||
@ -61,6 +63,10 @@ The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker,
|
|||||||
- Time control by @iangray001
|
- Time control by @iangray001
|
||||||
- Vertical controls by @iangray001
|
- Vertical controls by @iangray001
|
||||||
- Time/date/password/color input types by @pcbbc
|
- Time/date/password/color input types by @pcbbc
|
||||||
|
- Delayed response support @MartinMueller2003
|
||||||
|
- Fragmented control transfer @MartinMueller2003
|
||||||
|
- Extended Callback @MartinMueller2003
|
||||||
|
- Added a file display element @MartinMueller2003
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
@ -142,30 +148,58 @@ more program memory to work with.
|
|||||||
- Separator
|
- Separator
|
||||||
- Time
|
- Time
|
||||||
- Graph (partial implementation)
|
- Graph (partial implementation)
|
||||||
|
- File Display
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The heart of ESPUI is
|
The heart of ESPUI is [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer). ESPUI's frontend is based on [Skeleton CSS](http://getskeleton.com/) and jQuery-like lightweight [zepto.js](https://zeptojs.com/) for handling events. The communication between the ESP and the client browser works using web sockets. ESPUI does not need network access and can be used in standalone access point mode, all resources are loaded directly from the ESPs memory.
|
||||||
[ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer). ESPUI's
|
<br><br>
|
||||||
frontend is based on [Skeleton CSS](http://getskeleton.com/) and jQuery-like
|
This section will explain in detail how the Library is to be used from the Arduino code side. In the arduino `setup()` routine the interface can be customised by adding UI Elements. This is done by calling the corresponding library methods on the Library object `ESPUI`. Eg: `ESPUI.button("button", &myCallback);` creates a button in the interface that calls the `myCallback(Control *sender, int eventname)` function when changed. All buttons and items call their callback whenever there is a state change from them. This means the button will call the callback when it is pressed and also again when it is released. To separate different events, an integer number with the event name is passed to the callback function that can be handled in a `switch(){}case{}` statement.
|
||||||
lightweight [zepto.js](https://zeptojs.com/) for handling events. The
|
<br><br>
|
||||||
communication between the ESP and the client browser works using web
|
Alternativly you may use the extended callback funtion which provides three parameters to the callback function `myCallback(Control *sender, int eventname, void * UserParameter)`. The `UserParameter` is provided as part of the `ESPUI.addControl` method set and allows the user to define contextual information that is to be presented to the callback function in an unmodified form.
|
||||||
sockets. ESPUI does not need network access and can be used in standalone access
|
<br><br>
|
||||||
point mode, all resources are loaded directly from the ESPs memory.
|
It also possible to use a lambda function in the callback parameter. It also allows the user to define, in a more C++ way, contextual information in any form. This is shown by the [completeLambda](examples/completeLambda/completeLambda.ino) example.
|
||||||
|
<br><br>
|
||||||
This section will explain in detail how the Library is to be used from the
|
The below example creates a button and defines a lambda function to invoke a more specialized button callback handler:
|
||||||
Arduino code side. In the arduino `setup()` routine the interface can be customised by adding UI Elements.
|
```
|
||||||
This is done by calling the corresponding library methods on the Library object
|
void YourClassName::setup()
|
||||||
`ESPUI`. Eg: `ESPUI.button("button", &myCallback);` creates a button in the
|
{
|
||||||
interface that calls the `myCallback(Control *sender, int value)` function when changed. All buttons and
|
ButtonElementId = ESPUI.addControl(
|
||||||
items call their callback whenever there is a state change from them. This means
|
ControlType::Button,
|
||||||
the button will call the callback when it is pressed and also again when it is
|
ButtonLabel.c_str(),
|
||||||
released. To separate different events an integer number with the event name is
|
" Button Face Text ",
|
||||||
passed to the callback function that can be handled in a `switch(){}case{}`
|
ControlColor::None,
|
||||||
statement.
|
ParentElementId,
|
||||||
|
[&](Control *sender, int eventname)
|
||||||
|
{
|
||||||
|
myButtonCallback(sender, eventname); // class method
|
||||||
|
});
|
||||||
|
|
||||||
|
// or
|
||||||
|
ButtonElementId = ESPUI.button(
|
||||||
|
" Button Face Text ",
|
||||||
|
[&](Control *sender, int eventname)
|
||||||
|
{
|
||||||
|
myButtonCallback(sender, eventname); // class method
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
void YourClassName::myButtonCallback(Control* sender, int eventname)
|
||||||
|
{
|
||||||
|
if (eventname == B_DOWN)
|
||||||
|
{
|
||||||
|
// Handle the button down event
|
||||||
|
}
|
||||||
|
else if (eventname == B_UP)
|
||||||
|
{
|
||||||
|
// Handle the button up event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
#### Button
|
#### Button
|
||||||
|
|
||||||

|

|
||||||
@ -323,6 +357,19 @@ However even with a type set, user input should still be validated
|
|||||||
because it is easy to bypass client-side checks. Never trust user input.
|
because it is easy to bypass client-side checks. Never trust user input.
|
||||||
|
|
||||||
|
|
||||||
|
#### File Display
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The File Display control is used to upload a file from the ESP file system and display the contents on the UI. The content is Auto Scrolled
|
||||||
|
to the last line in the file. Syntax:
|
||||||
|
|
||||||
|
`fileDisplayId = ESPUI.fileDisplay("Filetest", ControlColor::Turquoise, FullyQualified FilePath);`
|
||||||
|
|
||||||
|
After updating the contents of the file, trigger a display update using:
|
||||||
|
`ESPUI.updateControl(fileDisplayId);`
|
||||||
|
|
||||||
|
|
||||||
#### Select control
|
#### Select control
|
||||||
|
|
||||||

|

|
||||||
@ -461,7 +508,7 @@ ESPUI includes a range of advanced features that can customise your UIs.
|
|||||||
|
|
||||||
### Dynamic Visibility
|
### Dynamic Visibility
|
||||||
|
|
||||||
Cotrols can be made visible or invisible at runtime with the `updateVisibility()` function.
|
Controls can be made visible or invisible at runtime with the `updateVisibility()` function.
|
||||||
|
|
||||||
```
|
```
|
||||||
ESPUI.updateVisibility(controlId, false);
|
ESPUI.updateVisibility(controlId, false);
|
||||||
@ -610,6 +657,17 @@ Graph points are saved in the browser in **localstorage** to be persistant, clea
|
|||||||
|
|
||||||
_There are many issues with the graph component currently and work is ongoing. Consider helping us out with development!_
|
_There are many issues with the graph component currently and work is ongoing. Consider helping us out with development!_
|
||||||
|
|
||||||
|
### Captive Portal
|
||||||
|
|
||||||
|
ESPUI will redirect all unknown URLs it is asked for to the 'root' of the local HTTP server instead of responding with an HTTP code 404. This makes it act as a simple 'captive portal'. Note you must also set up the ESP to be a DNS server that responds to all DNS requests with the IP address of the ESP. This only effective when the ESP is acting as a WiFi hotspot in AP mode and assigning itself as the DNS server to connected clients.
|
||||||
|
|
||||||
|
All the example sketches include the DNS related code and will work as captive portals when used as a hotspot. In the event you wish to disable this feature you can do so by removing the DNS server code and adding the code below.
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.captivePortal = false;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# Notes for Development
|
# Notes for Development
|
||||||
|
|
||||||
If you want to work on the HTML/CSS/JS files, do make changes in the _data_
|
If you want to work on the HTML/CSS/JS files, do make changes in the _data_
|
||||||
|
2
data/css/normalize.css
vendored
2
data/css/normalize.css
vendored
@ -111,7 +111,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
svg:not(:root) {
|
svg:not(:root) {
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grouping content
|
/* Grouping content
|
||||||
|
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:hidden}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: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}
|
365
data/js/controls.js
vendored
365
data/js/controls.js
vendored
@ -60,6 +60,11 @@ const UPDATE_SEPARATOR = 119;
|
|||||||
const UI_TIME = 20;
|
const UI_TIME = 20;
|
||||||
const UPDATE_TIME = 120;
|
const UPDATE_TIME = 120;
|
||||||
|
|
||||||
|
const UI_FILEDISPLAY = 21;
|
||||||
|
const UPDATE_FILEDISPLAY = 121;
|
||||||
|
|
||||||
|
const UI_FRAGMENT = 98;
|
||||||
|
|
||||||
const UP = 0;
|
const UP = 0;
|
||||||
const DOWN = 1;
|
const DOWN = 1;
|
||||||
const LEFT = 2;
|
const LEFT = 2;
|
||||||
@ -77,6 +82,8 @@ const C_ALIZARIN = 6;
|
|||||||
const C_DARK = 7;
|
const C_DARK = 7;
|
||||||
const C_NONE = 255;
|
const C_NONE = 255;
|
||||||
|
|
||||||
|
var controlAssemblyArray = new Object();
|
||||||
|
var FragmentAssemblyTimer = new Array();
|
||||||
var graphData = new Array();
|
var graphData = new Array();
|
||||||
var hasAccel = false;
|
var hasAccel = false;
|
||||||
var sliderContinuous = false;
|
var sliderContinuous = false;
|
||||||
@ -115,6 +122,7 @@ function colorClass(colorId) {
|
|||||||
|
|
||||||
var websock;
|
var websock;
|
||||||
var websockConnected = false;
|
var websockConnected = false;
|
||||||
|
var WebSocketTimer = null;
|
||||||
|
|
||||||
function requestOrientationPermission() {
|
function requestOrientationPermission() {
|
||||||
/*
|
/*
|
||||||
@ -175,7 +183,8 @@ function restoreGraphData(id) {
|
|||||||
var savedData = localStorage.getItem("espuigraphs", graphData);
|
var savedData = localStorage.getItem("espuigraphs", graphData);
|
||||||
if (savedData != null) {
|
if (savedData != null) {
|
||||||
savedData = JSON.parse(savedData);
|
savedData = JSON.parse(savedData);
|
||||||
return savedData[id];
|
let idData = savedData[id];
|
||||||
|
return Array.isArray(idData) ? idData : [];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -183,12 +192,20 @@ function restoreGraphData(id) {
|
|||||||
function restart() {
|
function restart() {
|
||||||
$(document).add("*").off();
|
$(document).add("*").off();
|
||||||
$("#row").html("");
|
$("#row").html("");
|
||||||
websock.close();
|
conStatusError();
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
function conStatusError() {
|
function conStatusError() {
|
||||||
|
FragmentAssemblyTimer.forEach(element => {
|
||||||
|
clearInterval(element);
|
||||||
|
});
|
||||||
|
FragmentAssemblyTimer = new Array();
|
||||||
|
controlAssemblyArray = new Array();
|
||||||
|
|
||||||
|
if (true === websockConnected) {
|
||||||
websockConnected = false;
|
websockConnected = false;
|
||||||
|
websock.close();
|
||||||
$("#conStatus").removeClass("color-green");
|
$("#conStatus").removeClass("color-green");
|
||||||
$("#conStatus").addClass("color-red");
|
$("#conStatus").addClass("color-red");
|
||||||
$("#conStatus").html("Error / No Connection ↻");
|
$("#conStatus").html("Error / No Connection ↻");
|
||||||
@ -196,6 +213,7 @@ function conStatusError() {
|
|||||||
$("#conStatus").on({
|
$("#conStatus").on({
|
||||||
click: restart,
|
click: restart,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleVisibilityChange() {
|
function handleVisibilityChange() {
|
||||||
@ -205,40 +223,88 @@ function handleVisibilityChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
let location = window.location.hostname;
|
||||||
|
let port = window.location.port;
|
||||||
|
// let location = "192.168.10.198";
|
||||||
|
// let port = "";
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", handleVisibilityChange, false);
|
document.addEventListener("visibilitychange", handleVisibilityChange, false);
|
||||||
if (
|
if (
|
||||||
window.location.port != "" ||
|
port != "" ||
|
||||||
window.location.port != 80 ||
|
port != 80 ||
|
||||||
window.location.port != 443
|
port != 443
|
||||||
) {
|
) {
|
||||||
websock = new WebSocket(
|
websock = new WebSocket( "ws://" + location + ":" + port + "/ws" );
|
||||||
"ws://" + window.location.hostname + ":" + window.location.port + "/ws"
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
websock = new WebSocket("ws://" + window.location.hostname + "/ws");
|
websock = new WebSocket("ws://" + location + "/ws");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is the timer running?
|
||||||
|
if (null === WebSocketTimer) {
|
||||||
|
// timer runs forever
|
||||||
|
WebSocketTimer = setInterval(function () {
|
||||||
|
// console.info("Periodic Timer has expired");
|
||||||
|
// is the socket closed?
|
||||||
|
if (websock.readyState === 3) {
|
||||||
|
// console.info("Web Socket Is Closed");
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
} // end timer was not running
|
||||||
|
|
||||||
websock.onopen = function (evt) {
|
websock.onopen = function (evt) {
|
||||||
console.log("websock open");
|
console.log("websock open");
|
||||||
$("#conStatus").addClass("color-green");
|
$("#conStatus").addClass("color-green");
|
||||||
$("#conStatus").text("Connected");
|
$("#conStatus").text("Connected");
|
||||||
websockConnected = true;
|
websockConnected = true;
|
||||||
|
FragmentAssemblyTimer.forEach(element => {
|
||||||
|
clearInterval(element);
|
||||||
|
});
|
||||||
|
FragmentAssemblyTimer = new Array();
|
||||||
|
controlAssemblyArray = new Array();
|
||||||
};
|
};
|
||||||
|
|
||||||
websock.onclose = function (evt) {
|
websock.onclose = function (evt) {
|
||||||
|
// console.log("Close evt: '" + evt + "'");
|
||||||
|
// console.log("Close reason: '" + evt.reason + "'");
|
||||||
|
// console.log("Close code: '" + evt.code + "'");
|
||||||
console.log("websock close");
|
console.log("websock close");
|
||||||
conStatusError();
|
conStatusError();
|
||||||
|
FragmentAssemblyTimer.forEach(element => {
|
||||||
|
clearInterval(element);
|
||||||
|
});
|
||||||
|
FragmentAssemblyTimer = new Array();
|
||||||
|
controlAssemblyArray = new Array();
|
||||||
};
|
};
|
||||||
|
|
||||||
websock.onerror = function (evt) {
|
websock.onerror = function (evt) {
|
||||||
console.log(evt);
|
console.log("websock Error");
|
||||||
conStatusError();
|
// console.log("Error evt: '" + evt + "'");
|
||||||
|
// console.log("Error data: '" + evt.data + "'");
|
||||||
|
|
||||||
|
restart();
|
||||||
|
FragmentAssemblyTimer.forEach(element => {
|
||||||
|
clearInterval(element);
|
||||||
|
});
|
||||||
|
FragmentAssemblyTimer = new Array();
|
||||||
|
controlAssemblyArray = new Array();
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleEvent = function (evt) {
|
var handleEvent = function (evt) {
|
||||||
console.log(evt);
|
// console.log("handleEvent:Data evt: '" + evt + "'");
|
||||||
|
// console.log("handleEvent:Data data: '" + evt.data + "'");
|
||||||
|
try {
|
||||||
var data = JSON.parse(evt.data);
|
var data = JSON.parse(evt.data);
|
||||||
|
}
|
||||||
|
catch (Event) {
|
||||||
|
console.error(Event);
|
||||||
|
// console.info("start the update over again");
|
||||||
|
websock.send("uiok:" + 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var e = document.body;
|
var e = document.body;
|
||||||
var center = "";
|
var center = "";
|
||||||
|
// console.info("data.type: '" + data.type + "'");
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case UI_INITIAL_GUI:
|
case UI_INITIAL_GUI:
|
||||||
@ -250,7 +316,9 @@ function start() {
|
|||||||
if (data.sliderContinuous) {
|
if (data.sliderContinuous) {
|
||||||
sliderContinuous = data.sliderContinuous;
|
sliderContinuous = data.sliderContinuous;
|
||||||
}
|
}
|
||||||
|
// console.info("UI_INITIAL_GUI:data record: '" + data + "'");
|
||||||
data.controls.forEach(element => {
|
data.controls.forEach(element => {
|
||||||
|
// console.info("element: '" + JSON.stringify(element) + "'");
|
||||||
var fauxEvent = {
|
var fauxEvent = {
|
||||||
data: JSON.stringify(element),
|
data: JSON.stringify(element),
|
||||||
};
|
};
|
||||||
@ -260,13 +328,15 @@ function start() {
|
|||||||
//If there are more elements in the complete UI, then request them
|
//If there are more elements in the complete UI, then request them
|
||||||
//Note: we subtract 1 from data.controls.length because the controls always
|
//Note: we subtract 1 from data.controls.length because the controls always
|
||||||
//includes the title element
|
//includes the title element
|
||||||
if(data.totalcontrols > (data.controls.length - 1)) {
|
if (data.totalcontrols > (data.controls.length - 1)) {
|
||||||
websock.send("uiok:" + (data.controls.length - 1));
|
websock.send("uiok:" + (data.controls.length - 1));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UI_EXTEND_GUI:
|
case UI_EXTEND_GUI:
|
||||||
|
// console.info("UI_EXTEND_GUI data record: '" + data + "'");
|
||||||
data.controls.forEach(element => {
|
data.controls.forEach(element => {
|
||||||
|
// console.info("UI_EXTEND_GUI:element: '" + JSON.stringify(element) + "'");
|
||||||
var fauxEvent = {
|
var fauxEvent = {
|
||||||
data: JSON.stringify(element),
|
data: JSON.stringify(element),
|
||||||
};
|
};
|
||||||
@ -274,7 +344,7 @@ function start() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Do we need to keep requesting more UI elements?
|
//Do we need to keep requesting more UI elements?
|
||||||
if(data.totalcontrols > data.startindex + (data.controls.length - 1)) {
|
if (data.totalcontrols > data.startindex + (data.controls.length - 1)) {
|
||||||
websock.send("uiok:" + (data.startindex + (data.controls.length - 1)));
|
websock.send("uiok:" + (data.startindex + (data.controls.length - 1)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -434,9 +504,9 @@ function start() {
|
|||||||
case UI_MIN:
|
case UI_MIN:
|
||||||
if (data.parentControl) {
|
if (data.parentControl) {
|
||||||
//Is it applied to a slider?
|
//Is it applied to a slider?
|
||||||
if($('#sl' + data.parentControl).length) {
|
if ($('#sl' + data.parentControl).length) {
|
||||||
$('#sl' + data.parentControl).attr("min", data.value);
|
$('#sl' + data.parentControl).attr("min", data.value);
|
||||||
} else if($('#num' + data.parentControl).length) {
|
} else if ($('#num' + data.parentControl).length) {
|
||||||
//Or a number
|
//Or a number
|
||||||
$('#num' + data.parentControl).attr("min", data.value);
|
$('#num' + data.parentControl).attr("min", data.value);
|
||||||
}
|
}
|
||||||
@ -446,12 +516,12 @@ function start() {
|
|||||||
case UI_MAX:
|
case UI_MAX:
|
||||||
if (data.parentControl) {
|
if (data.parentControl) {
|
||||||
//Is it applied to a slider?
|
//Is it applied to a slider?
|
||||||
if($('#sl' + data.parentControl).length) {
|
if ($('#sl' + data.parentControl).length) {
|
||||||
$('#sl' + data.parentControl).attr("max", data.value);
|
$('#sl' + data.parentControl).attr("max", data.value);
|
||||||
} else if($('#text' + data.parentControl).length) {
|
} else if ($('#text' + data.parentControl).length) {
|
||||||
//Is it a text element
|
//Is it a text element
|
||||||
$('#text' + data.parentControl).attr("maxlength", data.value);
|
$('#text' + data.parentControl).attr("maxlength", data.value);
|
||||||
} else if($('#num' + data.parentControl).length) {
|
} else if ($('#num' + data.parentControl).length) {
|
||||||
//Or a number
|
//Or a number
|
||||||
$('#num' + data.parentControl).attr("max", data.value);
|
$('#num' + data.parentControl).attr("max", data.value);
|
||||||
}
|
}
|
||||||
@ -460,9 +530,12 @@ function start() {
|
|||||||
|
|
||||||
case UI_STEP:
|
case UI_STEP:
|
||||||
if (data.parentControl) {
|
if (data.parentControl) {
|
||||||
var parent = $("#id" + data.parentControl + " input");
|
//Is it applied to a slider?
|
||||||
if (parent.size()) {
|
if ($('#sl' + data.parentControl).length) {
|
||||||
parent.attr("step", data.value);
|
$('#sl' + data.parentControl).attr("step", data.value);
|
||||||
|
} else if ($('#num' + data.parentControl).length) {
|
||||||
|
//Or a number
|
||||||
|
$('#num' + data.parentControl).attr("step", data.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -475,7 +548,7 @@ function start() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ADD_GRAPH_POINT:
|
case ADD_GRAPH_POINT:
|
||||||
var ts = Math.round(new Date().getTime() / 1000);
|
var ts = new Date().getTime();
|
||||||
graphData[data.id].push({ x: ts, y: data.value });
|
graphData[data.id].push({ x: ts, y: data.value });
|
||||||
saveGraphData();
|
saveGraphData();
|
||||||
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
||||||
@ -495,51 +568,59 @@ function start() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case UI_FILEDISPLAY:
|
||||||
|
if (data.visible)
|
||||||
|
{
|
||||||
|
addToHTML(data);
|
||||||
|
FileDisplayUploadFile(data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update messages change the value/style of a component without adding new HTML
|
* Update messages change the value/style of a component without adding new HTML
|
||||||
*/
|
*/
|
||||||
case UPDATE_LABEL:
|
case UPDATE_LABEL:
|
||||||
$("#l" + data.id).html(data.value);
|
$("#l" + data.id).html(data.value);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#l" + data.id).attr("style", data.elementStyle);
|
$("#l" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UPDATE_SWITCHER:
|
case UPDATE_SWITCHER:
|
||||||
switcher(data.id, data.value == "0" ? 0 : 1);
|
switcher(data.id, data.value == "0" ? 0 : 1);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#sl" + data.id).attr("style", data.elementStyle);
|
$("#sl" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UPDATE_SLIDER:
|
case UPDATE_SLIDER:
|
||||||
$("#sl" + data.id).attr("value", data.value)
|
$("#sl" + data.id).attr("value", data.value)
|
||||||
slider_move($("#id" + data.id), data.value, "100", false);
|
slider_move($("#sl" + data.id).parent().parent(), data.value, "100", false);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#sl" + data.id).attr("style", data.elementStyle);
|
$("#sl" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UPDATE_NUMBER:
|
case UPDATE_NUMBER:
|
||||||
$("#num" + data.id).val(data.value);
|
$("#num" + data.id).val(data.value);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#num" + data.id).attr("style", data.elementStyle);
|
$("#num" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UPDATE_TEXT_INPUT:
|
case UPDATE_TEXT_INPUT:
|
||||||
$("#text" + data.id).val(data.value);
|
$("#text" + data.id).val(data.value);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#text" + data.id).attr("style", data.elementStyle);
|
$("#text" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
if(data.hasOwnProperty('inputType')) {
|
if (data.hasOwnProperty('inputType')) {
|
||||||
$("#text" + data.id).attr("type", data.inputType);
|
$("#text" + data.id).attr("type", data.inputType);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UPDATE_SELECT:
|
case UPDATE_SELECT:
|
||||||
$("#select" + data.id).val(data.value);
|
$("#select" + data.id).val(data.value);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#select" + data.id).attr("style", data.elementStyle);
|
$("#select" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -547,7 +628,7 @@ function start() {
|
|||||||
case UPDATE_BUTTON:
|
case UPDATE_BUTTON:
|
||||||
$("#btn" + data.id).val(data.value);
|
$("#btn" + data.id).val(data.value);
|
||||||
$("#btn" + data.id).text(data.value);
|
$("#btn" + data.id).text(data.value);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#btn" + data.id).attr("style", data.elementStyle);
|
$("#btn" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -557,7 +638,7 @@ function start() {
|
|||||||
break;
|
break;
|
||||||
case UPDATE_GAUGE:
|
case UPDATE_GAUGE:
|
||||||
$("#gauge" + data.id).val(data.value);
|
$("#gauge" + data.id).val(data.value);
|
||||||
if(data.hasOwnProperty('elementStyle')) {
|
if (data.hasOwnProperty('elementStyle')) {
|
||||||
$("#gauge" + data.id).attr("style", data.elementStyle);
|
$("#gauge" + data.id).attr("style", data.elementStyle);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -569,6 +650,99 @@ function start() {
|
|||||||
websock.send("time:" + rv + ":" + data.id);
|
websock.send("time:" + rv + ":" + data.id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case UPDATE_FILEDISPLAY:
|
||||||
|
FileDisplayUploadFile(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_FRAGMENT:
|
||||||
|
// console.info("Starting Fragment Processing");
|
||||||
|
let FragmentLen = data.length;
|
||||||
|
let FragementOffset = data.offset;
|
||||||
|
let NextFragmentOffset = FragementOffset + FragmentLen;
|
||||||
|
let Total = data.total;
|
||||||
|
let Arrived = (FragmentLen + FragementOffset);
|
||||||
|
let FragmentFinal = Total === Arrived;
|
||||||
|
// console.info("UI_FRAGMENT:FragmentLen '" + FragmentLen + "'");
|
||||||
|
// console.info("UI_FRAGMENT:FragementOffset '" + FragementOffset + "'");
|
||||||
|
// console.info("UI_FRAGMENT:NextFragmentOffset '" + NextFragmentOffset + "'");
|
||||||
|
// console.info("UI_FRAGMENT:Total '" + Total + "'");
|
||||||
|
// console.info("UI_FRAGMENT:Arrived '" + Arrived + "'");
|
||||||
|
// console.info("UI_FRAGMENT:FragmentFinal '" + FragmentFinal + "'");
|
||||||
|
|
||||||
|
if (!data.hasOwnProperty('control'))
|
||||||
|
{
|
||||||
|
console.error("UI_FRAGMENT:Missing control record, skipping control");
|
||||||
|
// console.info("Done Fragment Processing");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let control = data.control;
|
||||||
|
StopFragmentAssemblyTimer(data.control.id);
|
||||||
|
|
||||||
|
// is this the first fragment?
|
||||||
|
if(0 === FragementOffset)
|
||||||
|
{
|
||||||
|
// console.info("Found first fragment");
|
||||||
|
controlAssemblyArray[control.id] = data;
|
||||||
|
// console.info("Value: " + controlAssemblyArray[control.id].control.value);
|
||||||
|
controlAssemblyArray[control.id].offset = NextFragmentOffset;
|
||||||
|
StartFragmentAssemblyTimer(control.id);
|
||||||
|
let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : NextFragmentOffset });
|
||||||
|
websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":");
|
||||||
|
// console.info("asked for fragment " + TotalRequest);
|
||||||
|
// console.info("Done Fragment Processing");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not first fragment. are we assembling this control?
|
||||||
|
if("undefined" === typeof controlAssemblyArray[control.id])
|
||||||
|
{
|
||||||
|
// it looks like we missed the first fragment. Start the control over
|
||||||
|
console.error("Missing first fragment for control: " + control.id);
|
||||||
|
StartFragmentAssemblyTimer(control.id);
|
||||||
|
let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : 0 });
|
||||||
|
websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":");
|
||||||
|
// console.info("asked for fragment " + TotalRequest);
|
||||||
|
// console.info("Done Fragment Processing");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is this the expected next fragment
|
||||||
|
if(FragementOffset !== controlAssemblyArray[control.id].offset)
|
||||||
|
{
|
||||||
|
console.error("Wrong next fragment. Expected: " + controlAssemblyArray[control.id].offset + " Got: " + FragementOffset);
|
||||||
|
StartFragmentAssemblyTimer(control.id);
|
||||||
|
let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : controlAssemblyArray[control.id].length + controlAssemblyArray[control.id].offset });
|
||||||
|
websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":");
|
||||||
|
// console.info("asked for the expected fragment: " + TotalRequest);
|
||||||
|
// console.info("Done Fragment Processing");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.info("Add to existing fragment");
|
||||||
|
controlAssemblyArray[control.id].control.value += control.value;
|
||||||
|
controlAssemblyArray[control.id].offset = NextFragmentOffset;
|
||||||
|
// console.info("Value: " + controlAssemblyArray[control.id].control.value);
|
||||||
|
|
||||||
|
if(true === FragmentFinal)
|
||||||
|
{
|
||||||
|
var fauxEvent = {
|
||||||
|
data: JSON.stringify(controlAssemblyArray[control.id].control),
|
||||||
|
};
|
||||||
|
handleEvent(fauxEvent);
|
||||||
|
controlAssemblyArray[control.id] = null;
|
||||||
|
// console.info("Found last fragment");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// console.info("Ask for next fragment.");
|
||||||
|
StartFragmentAssemblyTimer(control.id);
|
||||||
|
let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : NextFragmentOffset});
|
||||||
|
websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":");
|
||||||
|
// console.info("asked for the next fragment: " + TotalRequest);
|
||||||
|
}
|
||||||
|
// console.info("Done Fragment Processing");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error("Unknown type or event");
|
console.error("Unknown type or event");
|
||||||
break;
|
break;
|
||||||
@ -583,12 +757,12 @@ function start() {
|
|||||||
//An "update" message was just recieved and processed
|
//An "update" message was just recieved and processed
|
||||||
var element = $("#id" + data.id);
|
var element = $("#id" + data.id);
|
||||||
|
|
||||||
if(data.hasOwnProperty('panelStyle')) {
|
if (data.hasOwnProperty('panelStyle')) {
|
||||||
$("#id" + data.id).attr("style", data.panelStyle);
|
$("#id" + data.id).attr("style", data.panelStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data.hasOwnProperty('visible')) {
|
if (data.hasOwnProperty('visible')) {
|
||||||
if(data['visible'])
|
if (data['visible'])
|
||||||
$("#id" + data.id).show();
|
$("#id" + data.id).show();
|
||||||
else
|
else
|
||||||
$("#id" + data.id).hide();
|
$("#id" + data.id).hide();
|
||||||
@ -609,7 +783,7 @@ function start() {
|
|||||||
processEnabled(data);
|
processEnabled(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".range-slider__range").each(function(){
|
$(".range-slider__range").each(function () {
|
||||||
$(this)[0].value = $(this).attr("value");
|
$(this)[0].value = $(this).attr("value");
|
||||||
$(this).next().html($(this).attr("value"));
|
$(this).next().html($(this).attr("value"));
|
||||||
});
|
});
|
||||||
@ -618,11 +792,75 @@ function start() {
|
|||||||
websock.onmessage = handleEvent;
|
websock.onmessage = handleEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function FileDisplayUploadFile(data)
|
||||||
|
{
|
||||||
|
let text = await downloadFile(data.value);
|
||||||
|
let ItemToUpdateId = "fd" + data.id;
|
||||||
|
// console.info("ItemToUpdateId: " + ItemToUpdateId);
|
||||||
|
// console.info(" text: " + text);
|
||||||
|
// populate the text object
|
||||||
|
$("#" + ItemToUpdateId).val(text);
|
||||||
|
$("#" + ItemToUpdateId).css("textAlign", "left");
|
||||||
|
$("#" + ItemToUpdateId).css("white-space", "nowrap");
|
||||||
|
$("#" + ItemToUpdateId).css("overflow", "scroll");
|
||||||
|
$("#" + ItemToUpdateId).css("overflow-y", "scroll");
|
||||||
|
$("#" + ItemToUpdateId).css("overflow-x", "scroll");
|
||||||
|
$("#" + ItemToUpdateId).scrollTop($("#" + ItemToUpdateId).val().length);
|
||||||
|
|
||||||
|
// scroll the page to the updated control
|
||||||
|
// $("#" + ItemToUpdateId).focus();
|
||||||
|
|
||||||
|
} // FileDisplayUploadFile
|
||||||
|
|
||||||
|
async function downloadFile(filename)
|
||||||
|
{
|
||||||
|
let response = await fetch(filename);
|
||||||
|
|
||||||
|
if(response.status != 200) {
|
||||||
|
throw new Error("File Read Server Error: '" + response.status + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// read response stream as text
|
||||||
|
let text_data = await response.text();
|
||||||
|
|
||||||
|
return text_data;
|
||||||
|
} // downloadFile
|
||||||
|
|
||||||
|
function StartFragmentAssemblyTimer(Id)
|
||||||
|
{
|
||||||
|
StopFragmentAssemblyTimer(Id);
|
||||||
|
FragmentAssemblyTimer[Id] = setInterval(function(_Id)
|
||||||
|
{
|
||||||
|
// does the fragment assembly still exist?
|
||||||
|
if("undefined" !== typeof controlAssemblyArray[_Id])
|
||||||
|
{
|
||||||
|
if(null !== controlAssemblyArray[_Id])
|
||||||
|
{
|
||||||
|
// we have a valid control that is being assembled
|
||||||
|
// ask for the next part
|
||||||
|
let TotalRequest = JSON.stringify({ 'id' : controlAssemblyArray[_Id].control.id, 'offset' : controlAssemblyArray[_Id].offset});
|
||||||
|
websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000, Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StopFragmentAssemblyTimer(Id)
|
||||||
|
{
|
||||||
|
if("undefined" !== typeof FragmentAssemblyTimer[Id])
|
||||||
|
{
|
||||||
|
if(FragmentAssemblyTimer[Id])
|
||||||
|
{
|
||||||
|
clearInterval(FragmentAssemblyTimer[Id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function sliderchange(number) {
|
function sliderchange(number) {
|
||||||
var val = $("#sl" + number).val();
|
var val = $("#sl" + number).val();
|
||||||
websock.send("slvalue:" + val + ":" + number);
|
websock.send("slvalue:" + val + ":" + number);
|
||||||
|
|
||||||
$(".range-slider__range").each(function(){
|
$(".range-slider__range").each(function () {
|
||||||
$(this).attr("value", $(this)[0].value);
|
$(this).attr("value", $(this)[0].value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -653,7 +891,7 @@ function buttonclick(number, isdown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function padclick(type, number, isdown) {
|
function padclick(type, number, isdown) {
|
||||||
if($("#id" + number + " nav").hasClass("disabled")) {
|
if ($("#id" + number + " nav").hasClass("disabled")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -700,21 +938,23 @@ function switcher(number, state) {
|
|||||||
|
|
||||||
var rangeSlider = function (isDiscrete) {
|
var rangeSlider = function (isDiscrete) {
|
||||||
var range = $(".range-slider__range");
|
var range = $(".range-slider__range");
|
||||||
var slidercb = function() {
|
var slidercb = function () {
|
||||||
sliderchange($(this).attr("id").replace(/^\D+/g, ""));
|
sliderchange($(this).attr("id").replace(/^\D+/g, ""));
|
||||||
};
|
};
|
||||||
|
|
||||||
range.on({input: function() {
|
range.on({
|
||||||
$(this).next().html(this.value)}
|
input: function () {
|
||||||
|
$(this).next().html(this.value)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
range.each(function() {
|
range.each(function () {
|
||||||
$(this).next().html(this.value);
|
$(this).next().html(this.value);
|
||||||
if($(this).attr("callbackSet") != "true") {
|
if ($(this).attr("callbackSet") != "true") {
|
||||||
if (!isDiscrete) {
|
if (!isDiscrete) {
|
||||||
$(this).on({input: slidercb}); //input fires when dragging
|
$(this).on({ input: slidercb }); //input fires when dragging
|
||||||
} else {
|
} else {
|
||||||
$(this).on({change: slidercb}); //change fires only once released
|
$(this).on({ change: slidercb }); //change fires only once released
|
||||||
}
|
}
|
||||||
$(this).attr("callbackSet", "true");
|
$(this).attr("callbackSet", "true");
|
||||||
}
|
}
|
||||||
@ -722,18 +962,18 @@ 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 + "' " : "";
|
||||||
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) {
|
||||||
//We add the control with its own panel
|
//We add the control with its own panel
|
||||||
var parent = data.hasOwnProperty('parentControl') ?
|
var parent = data.hasOwnProperty('parentControl') ?
|
||||||
$("#tab" + data.parentControl) :
|
$("#tab" + data.parentControl) :
|
||||||
$("#row");
|
$("#row");
|
||||||
|
|
||||||
var html = "";
|
var html = "";
|
||||||
switch(data.type) {
|
switch (data.type) {
|
||||||
case UI_LABEL:
|
case UI_LABEL:
|
||||||
case UI_BUTTON:
|
case UI_BUTTON:
|
||||||
case UI_SWITCHER:
|
case UI_SWITCHER:
|
||||||
@ -746,11 +986,13 @@ var addToHTML = function(data) {
|
|||||||
case UI_GRAPH:
|
case UI_GRAPH:
|
||||||
case UI_GAUGE:
|
case UI_GAUGE:
|
||||||
case UI_ACCEL:
|
case UI_ACCEL:
|
||||||
|
case UI_FILEDISPLAY:
|
||||||
html = "<div id='id" + data.id + "' " + panelStyle + " class='two columns " + panelwide + " card tcenter " +
|
html = "<div id='id" + data.id + "' " + panelStyle + " class='two columns " + panelwide + " card tcenter " +
|
||||||
colorClass(data.color) + "'><h5>" + data.label + "</h5><hr/>" +
|
colorClass(data.color) + "'><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'>" +
|
html = "<div id='id" + data.id + "' " + panelStyle + " class='sectionbreak columns'>" +
|
||||||
"<h5>" + data.label + "</h5><hr/></div>";
|
"<h5>" + data.label + "</h5><hr/></div>";
|
||||||
@ -769,14 +1011,17 @@ var addToHTML = function(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var elementHTML = function(data) {
|
var elementHTML = function (data) {
|
||||||
var id = data.id
|
var id = data.id
|
||||||
var elementStyle = data.hasOwnProperty('elementStyle') ? " style='" + data.elementStyle + "' " : "";
|
var elementStyle = data.hasOwnProperty('elementStyle') ? " style='" + data.elementStyle + "' " : "";
|
||||||
var inputType = data.hasOwnProperty('inputType') ? " type='" + data.inputType + "' " : "";
|
var inputType = data.hasOwnProperty('inputType') ? " type='" + data.inputType + "' " : "";
|
||||||
switch(data.type) {
|
switch (data.type) {
|
||||||
case UI_LABEL:
|
case UI_LABEL:
|
||||||
return "<span id='l" + id + "' " + elementStyle +
|
return "<span id='l" + id + "' " + elementStyle +
|
||||||
" class='label label-wrap'>" + data.value + "</span>";
|
" class='label label-wrap'>" + data.value + "</span>";
|
||||||
|
case UI_FILEDISPLAY:
|
||||||
|
return "<textarea id='fd" + id + "' rows='4' " + elementStyle +
|
||||||
|
" class='label label-wrap'>" + "</textarea>";
|
||||||
case UI_BUTTON:
|
case UI_BUTTON:
|
||||||
return "<button id='btn" + id + "' " + elementStyle +
|
return "<button id='btn" + id + "' " + elementStyle +
|
||||||
" onmousedown='buttonclick(" + id + ", true)'" +
|
" onmousedown='buttonclick(" + id + ", true)'" +
|
||||||
@ -815,13 +1060,13 @@ var elementHTML = function(data) {
|
|||||||
elementStyle + " class='range-slider__range'><span class='range-slider__value'>" +
|
elementStyle + " class='range-slider__range'><span class='range-slider__value'>" +
|
||||||
data.value + "</span></div>";
|
data.value + "</span></div>";
|
||||||
case UI_NUMBER:
|
case UI_NUMBER:
|
||||||
return "<input style='color:black;' " + elementStyle + " id='num" + id +
|
return "<input style='color:black; " + data.elementStyle + "' id='num" + id +
|
||||||
"' type='number' value='" + data.value + "' onchange='numberchange(" + id + ")' />";
|
"' type='number' value='" + data.value + "' onchange='numberchange(" + id + ")' />";
|
||||||
case UI_TEXT_INPUT:
|
case UI_TEXT_INPUT:
|
||||||
return "<input " + inputType + "style='color:black;' " + elementStyle + " id='text" + id +
|
return "<input " + inputType + "style='color:black; " + data.elementStyle + "' id='text" + id +
|
||||||
"' value='" + data.value + "' onchange='textchange(" + id + ")' />";
|
"' value='" + data.value + "' onchange='textchange(" + id + ")' />";
|
||||||
case UI_SELECT:
|
case UI_SELECT:
|
||||||
return "<select style='color:black;' " + elementStyle + " id='select" + id +
|
return "<select style='color:black; " + data.elementStyle + "' id='select" + id +
|
||||||
"' onchange='selectchange(" + id + ")' />";
|
"' onchange='selectchange(" + id + ")' />";
|
||||||
case UI_GRAPH:
|
case UI_GRAPH:
|
||||||
return "<figure id='graph" + id + "'><figcaption>" + data.label + "</figcaption></figure>";
|
return "<figure id='graph" + id + "'><figcaption>" + data.label + "</figcaption></figure>";
|
||||||
@ -836,16 +1081,14 @@ var elementHTML = function(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var processEnabled = function (data) {
|
||||||
|
|
||||||
var processEnabled = function(data) {
|
|
||||||
//Handle the enabling and disabling of controls
|
//Handle the enabling and disabling of controls
|
||||||
//Most controls can be disabled through the use of $("#<item>").prop("disabled", true) and CSS will style it accordingly
|
//Most controls can be disabled through the use of $("#<item>").prop("disabled", true) and CSS will style it accordingly
|
||||||
//The switcher and pads also require the addition of the "disabled" class
|
//The switcher and pads also require the addition of the "disabled" class
|
||||||
switch(data.type) {
|
switch (data.type) {
|
||||||
case UI_SWITCHER:
|
case UI_SWITCHER:
|
||||||
case UPDATE_SWITCHER:
|
case UPDATE_SWITCHER:
|
||||||
if(data.enabled) {
|
if (data.enabled) {
|
||||||
$("#sl" + data.id).removeClass('disabled');
|
$("#sl" + data.id).removeClass('disabled');
|
||||||
$("#s" + data.id).prop("disabled", false);
|
$("#s" + data.id).prop("disabled", false);
|
||||||
} else {
|
} else {
|
||||||
@ -883,7 +1126,9 @@ var processEnabled = function(data) {
|
|||||||
case UI_CPAD:
|
case UI_CPAD:
|
||||||
case UPDATE_PAD:
|
case UPDATE_PAD:
|
||||||
case UPDATE_CPAD:
|
case UPDATE_CPAD:
|
||||||
if(data.enabled) {
|
case UI_FILEDISPLAY:
|
||||||
|
case UPDATE_FILEDISPLAY:
|
||||||
|
if (data.enabled) {
|
||||||
$("#id" + data.id + " nav").removeClass('disabled');
|
$("#id" + data.id + " nav").removeClass('disabled');
|
||||||
} else {
|
} else {
|
||||||
$("#id" + data.id + " nav").addClass('disabled');
|
$("#id" + data.id + " nav").addClass('disabled');
|
||||||
|
66
data/js/controls.min.js
vendored
66
data/js/controls.min.js
vendored
@ -1,13 +1,16 @@
|
|||||||
const UI_INITIAL_GUI=200;const UI_RELOAD=201;const UPDATE_OFFSET=100;const UI_EXTEND_GUI=210;const UI_TITEL=0;const UI_PAD=1;const UPDATE_PAD=101;const UI_CPAD=2;const UPDATE_CPAD=102;const UI_BUTTON=3;const UPDATE_BUTTON=103;const UI_LABEL=4;const UPDATE_LABEL=104;const UI_SWITCHER=5;const UPDATE_SWITCHER=105;const UI_SLIDER=6;const UPDATE_SLIDER=106;const UI_NUMBER=7;const UPDATE_NUMBER=107;const UI_TEXT_INPUT=8;const UPDATE_TEXT_INPUT=108;const UI_GRAPH=9;const ADD_GRAPH_POINT=10;const CLEAR_GRAPH=109;const UI_TAB=11;const UPDATE_TAB=111;const UI_SELECT=12;const UPDATE_SELECT=112;const UI_OPTION=13;const UPDATE_OPTION=113;const UI_MIN=14;const UPDATE_MIN=114;const UI_MAX=15;const UPDATE_MAX=115;const UI_STEP=16;const UPDATE_STEP=116;const UI_GAUGE=17;const UPDATE_GAUGE=117;const UI_ACCEL=18;const UPDATE_ACCEL=118;const UI_SEPARATOR=19;const UPDATE_SEPARATOR=119;const UI_TIME=20;const UPDATE_TIME=120;const UP=0;const DOWN=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_DARK=7;const C_NONE=255;var graphData=new Array();var hasAccel=false;var sliderContinuous=false;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";case C_EMERALD:return"emerald";case C_PETERRIVER:return"peterriver";case C_WETASPHALT:return"wetasphalt";case C_SUNFLOWER:return"sunflower";case C_CARROT:return"carrot";case C_ALIZARIN:return"alizarin";case C_DARK:case C_NONE:return"dark";default:return"";}}
|
const UI_INITIAL_GUI=200;const UI_RELOAD=201;const UPDATE_OFFSET=100;const UI_EXTEND_GUI=210;const UI_TITEL=0;const UI_PAD=1;const UPDATE_PAD=101;const UI_CPAD=2;const UPDATE_CPAD=102;const UI_BUTTON=3;const UPDATE_BUTTON=103;const UI_LABEL=4;const UPDATE_LABEL=104;const UI_SWITCHER=5;const UPDATE_SWITCHER=105;const UI_SLIDER=6;const UPDATE_SLIDER=106;const UI_NUMBER=7;const UPDATE_NUMBER=107;const UI_TEXT_INPUT=8;const UPDATE_TEXT_INPUT=108;const UI_GRAPH=9;const ADD_GRAPH_POINT=10;const CLEAR_GRAPH=109;const UI_TAB=11;const UPDATE_TAB=111;const UI_SELECT=12;const UPDATE_SELECT=112;const UI_OPTION=13;const UPDATE_OPTION=113;const UI_MIN=14;const UPDATE_MIN=114;const UI_MAX=15;const UPDATE_MAX=115;const UI_STEP=16;const UPDATE_STEP=116;const UI_GAUGE=17;const UPDATE_GAUGE=117;const UI_ACCEL=18;const UPDATE_ACCEL=118;const UI_SEPARATOR=19;const UPDATE_SEPARATOR=119;const UI_TIME=20;const UPDATE_TIME=120;const UI_FILEDISPLAY=21;const UPDATE_FILEDISPLAY=121;const UI_FRAGMENT=98;const UP=0;const DOWN=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_DARK=7;const C_NONE=255;var controlAssemblyArray=new Object();var FragmentAssemblyTimer=new Array();var graphData=new Array();var hasAccel=false;var sliderContinuous=false;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";case C_EMERALD:return"emerald";case C_PETERRIVER:return"peterriver";case C_WETASPHALT:return"wetasphalt";case C_SUNFLOWER:return"sunflower";case C_CARROT:return"carrot";case C_ALIZARIN:return"alizarin";case C_DARK:case C_NONE:return"dark";default:return"";}}
|
||||||
var websock;var websockConnected=false;function requestOrientationPermission(){}
|
var websock;var websockConnected=false;var WebSocketTimer=null;function requestOrientationPermission(){}
|
||||||
function saveGraphData(){localStorage.setItem("espuigraphs",JSON.stringify(graphData));}
|
function saveGraphData(){localStorage.setItem("espuigraphs",JSON.stringify(graphData));}
|
||||||
function restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);return savedData[id];}
|
function restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);let idData=savedData[id];return Array.isArray(idData)?idData:[];}
|
||||||
return[];}
|
return[];}
|
||||||
function restart(){$(document).add("*").off();$("#row").html("");websock.close();start();}
|
function restart(){$(document).add("*").off();$("#row").html("");conStatusError();start();}
|
||||||
function conStatusError(){websockConnected=false;$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection ↻");$("#conStatus").off();$("#conStatus").on({click:restart,});}
|
function conStatusError(){FragmentAssemblyTimer.forEach(element=>{clearInterval(element);});FragmentAssemblyTimer=new Array();controlAssemblyArray=new Array();if(true===websockConnected){websockConnected=false;websock.close();$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection ↻");$("#conStatus").off();$("#conStatus").on({click:restart,});}}
|
||||||
function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
|
function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
|
||||||
function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);if(window.location.port!=""||window.location.port!=80||window.location.port!=443){websock=new WebSocket("ws://"+window.location.hostname+":"+window.location.port+"/ws");}else{websock=new WebSocket("ws://"+window.location.hostname+"/ws");}
|
function start(){let location=window.location.hostname;let port=window.location.port;document.addEventListener("visibilitychange",handleVisibilityChange,false);if(port!=""||port!=80||port!=443){websock=new WebSocket("ws://"+location+":"+port+"/ws");}else{websock=new WebSocket("ws://"+location+"/ws");}
|
||||||
websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log(evt);conStatusError();};var handleEvent=function(evt){console.log(evt);var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:$("#row").html("");$("#tabsnav").html("");$("#tabscontent").html("");if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;}
|
if(null===WebSocketTimer){WebSocketTimer=setInterval(function(){if(websock.readyState===3){restart();}},5000);}
|
||||||
|
websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;FragmentAssemblyTimer.forEach(element=>{clearInterval(element);});FragmentAssemblyTimer=new Array();controlAssemblyArray=new Array();};websock.onclose=function(evt){console.log("websock close");conStatusError();FragmentAssemblyTimer.forEach(element=>{clearInterval(element);});FragmentAssemblyTimer=new Array();controlAssemblyArray=new Array();};websock.onerror=function(evt){console.log("websock Error");restart();FragmentAssemblyTimer.forEach(element=>{clearInterval(element);});FragmentAssemblyTimer=new Array();controlAssemblyArray=new Array();};var handleEvent=function(evt){try{var data=JSON.parse(evt.data);}
|
||||||
|
catch(Event){console.error(Event);websock.send("uiok:"+0);return;}
|
||||||
|
var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:$("#row").html("");$("#tabsnav").html("");$("#tabscontent").html("");if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;}
|
||||||
data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>(data.controls.length-1)){websock.send("uiok:"+(data.controls.length-1));}
|
data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>(data.controls.length-1)){websock.send("uiok:"+(data.controls.length-1));}
|
||||||
break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>data.startindex+(data.controls.length-1)){websock.send("uiok:"+(data.startindex+(data.controls.length-1)));}
|
break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>data.startindex+(data.controls.length-1)){websock.send("uiok:"+(data.startindex+(data.controls.length-1)));}
|
||||||
break;case UI_RELOAD:window.location.reload();break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GAUGE:case UI_SEPARATOR:if(data.visible)addToHTML(data);break;case UI_BUTTON:if(data.visible){addToHTML(data);$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);},});}
|
break;case UI_RELOAD:window.location.reload();break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GAUGE:case UI_SEPARATOR:if(data.visible)addToHTML(data);break;case UI_BUTTON:if(data.visible){addToHTML(data);$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);},});}
|
||||||
@ -27,20 +30,34 @@ data.label+
|
|||||||
"</option>");}
|
"</option>");}
|
||||||
break;case UI_MIN:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("min",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("min",data.value);}}
|
break;case UI_MIN:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("min",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("min",data.value);}}
|
||||||
break;case UI_MAX:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("max",data.value);}else if($('#text'+data.parentControl).length){$('#text'+data.parentControl).attr("maxlength",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("max",data.value);}}
|
break;case UI_MAX:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("max",data.value);}else if($('#text'+data.parentControl).length){$('#text'+data.parentControl).attr("maxlength",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("max",data.value);}}
|
||||||
break;case UI_STEP:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("step",data.value);}}
|
break;case UI_STEP:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("step",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("step",data.value);}}
|
||||||
break;case UI_GRAPH:if(data.visible){addToHTML(data);graphData[data.id]=restoreGraphData(data.id);renderGraphSvg(graphData[data.id],"graph"+data.id);}
|
break;case UI_GRAPH:if(data.visible){addToHTML(data);graphData[data.id]=restoreGraphData(data.id);renderGraphSvg(graphData[data.id],"graph"+data.id);}
|
||||||
break;case ADD_GRAPH_POINT:var ts=Math.round(new Date().getTime()/1000);graphData[data.id].push({x:ts,y:data.value});saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case CLEAR_GRAPH:graphData[data.id]=[];saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case UI_ACCEL:if(hasAccel)break;hasAccel=true;if(data.visible){addToHTML(data);requestOrientationPermission();}
|
break;case ADD_GRAPH_POINT:var ts=new Date().getTime();graphData[data.id].push({x:ts,y:data.value});saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case CLEAR_GRAPH:graphData[data.id]=[];saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case UI_ACCEL:if(hasAccel)break;hasAccel=true;if(data.visible){addToHTML(data);requestOrientationPermission();}
|
||||||
|
break;case UI_FILEDISPLAY:if(data.visible)
|
||||||
|
{addToHTML(data);FileDisplayUploadFile(data);}
|
||||||
break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);if(data.hasOwnProperty('elementStyle')){$("#l"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);if(data.hasOwnProperty('elementStyle')){$("#l"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_SWITCHER:switcher(data.id,data.value=="0"?0:1);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_SWITCHER:switcher(data.id,data.value=="0"?0:1);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_SLIDER:$("#sl"+data.id).attr("value",data.value)
|
break;case UPDATE_SLIDER:$("#sl"+data.id).attr("value",data.value)
|
||||||
slider_move($("#id"+data.id),data.value,"100",false);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
|
slider_move($("#sl"+data.id).parent().parent(),data.value,"100",false);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#num"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#num"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#text"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#text"+data.id).attr("style",data.elementStyle);}
|
||||||
if(data.hasOwnProperty('inputType')){$("#text"+data.id).attr("type",data.inputType);}
|
if(data.hasOwnProperty('inputType')){$("#text"+data.id).attr("type",data.inputType);}
|
||||||
break;case UPDATE_SELECT:$("#select"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#select"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_SELECT:$("#select"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#select"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_BUTTON:$("#btn"+data.id).val(data.value);$("#btn"+data.id).text(data.value);if(data.hasOwnProperty('elementStyle')){$("#btn"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_BUTTON:$("#btn"+data.id).val(data.value);$("#btn"+data.id).text(data.value);if(data.hasOwnProperty('elementStyle')){$("#btn"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#gauge"+data.id).attr("style",data.elementStyle);}
|
break;case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#gauge"+data.id).attr("style",data.elementStyle);}
|
||||||
break;case UPDATE_ACCEL:break;case UPDATE_TIME:var rv=new Date().toISOString();websock.send("time:"+rv+":"+data.id);break;default:console.error("Unknown type or event");break;}
|
break;case UPDATE_ACCEL:break;case UPDATE_TIME:var rv=new Date().toISOString();websock.send("time:"+rv+":"+data.id);break;case UPDATE_FILEDISPLAY:FileDisplayUploadFile(data);break;case UI_FRAGMENT:let FragmentLen=data.length;let FragementOffset=data.offset;let NextFragmentOffset=FragementOffset+FragmentLen;let Total=data.total;let Arrived=(FragmentLen+FragementOffset);let FragmentFinal=Total===Arrived;if(!data.hasOwnProperty('control'))
|
||||||
|
{console.error("UI_FRAGMENT:Missing control record, skipping control");break;}
|
||||||
|
let control=data.control;StopFragmentAssemblyTimer(data.control.id);if(0===FragementOffset)
|
||||||
|
{controlAssemblyArray[control.id]=data;controlAssemblyArray[control.id].offset=NextFragmentOffset;StartFragmentAssemblyTimer(control.id);let TotalRequest=JSON.stringify({'id':control.id,'offset':NextFragmentOffset});websock.send("uifragmentok:"+0+": "+TotalRequest+":");break;}
|
||||||
|
if("undefined"===typeof controlAssemblyArray[control.id])
|
||||||
|
{console.error("Missing first fragment for control: "+control.id);StartFragmentAssemblyTimer(control.id);let TotalRequest=JSON.stringify({'id':control.id,'offset':0});websock.send("uifragmentok:"+0+": "+TotalRequest+":");break;}
|
||||||
|
if(FragementOffset!==controlAssemblyArray[control.id].offset)
|
||||||
|
{console.error("Wrong next fragment. Expected: "+controlAssemblyArray[control.id].offset+" Got: "+FragementOffset);StartFragmentAssemblyTimer(control.id);let TotalRequest=JSON.stringify({'id':control.id,'offset':controlAssemblyArray[control.id].length+controlAssemblyArray[control.id].offset});websock.send("uifragmentok:"+0+": "+TotalRequest+":");break;}
|
||||||
|
controlAssemblyArray[control.id].control.value+=control.value;controlAssemblyArray[control.id].offset=NextFragmentOffset;if(true===FragmentFinal)
|
||||||
|
{var fauxEvent={data:JSON.stringify(controlAssemblyArray[control.id].control),};handleEvent(fauxEvent);controlAssemblyArray[control.id]=null;}
|
||||||
|
else
|
||||||
|
{StartFragmentAssemblyTimer(control.id);let TotalRequest=JSON.stringify({'id':control.id,'offset':NextFragmentOffset});websock.send("uifragmentok:"+0+": "+TotalRequest+":");}
|
||||||
|
break;default:console.error("Unknown type or event");break;}
|
||||||
if(data.type>=UI_TITEL&&data.type<UPDATE_OFFSET){processEnabled(data);}
|
if(data.type>=UI_TITEL&&data.type<UPDATE_OFFSET){processEnabled(data);}
|
||||||
if(data.type>=UPDATE_OFFSET&&data.type<UI_INITIAL_GUI){var element=$("#id"+data.id);if(data.hasOwnProperty('panelStyle')){$("#id"+data.id).attr("style",data.panelStyle);}
|
if(data.type>=UPDATE_OFFSET&&data.type<UI_INITIAL_GUI){var element=$("#id"+data.id);if(data.hasOwnProperty('panelStyle')){$("#id"+data.id).attr("style",data.panelStyle);}
|
||||||
if(data.hasOwnProperty('visible')){if(data['visible'])
|
if(data.hasOwnProperty('visible')){if(data['visible'])
|
||||||
@ -49,6 +66,20 @@ $("#id"+data.id).hide();}
|
|||||||
if(data.type==UPDATE_SLIDER){element.removeClass("slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin");element.addClass("slider-"+colorClass(data.color));}else{element.removeClass("turquoise emerald peterriver wetasphalt sunflower carrot alizarin");element.addClass(colorClass(data.color));}
|
if(data.type==UPDATE_SLIDER){element.removeClass("slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin");element.addClass("slider-"+colorClass(data.color));}else{element.removeClass("turquoise emerald peterriver wetasphalt sunflower carrot alizarin");element.addClass(colorClass(data.color));}
|
||||||
processEnabled(data);}
|
processEnabled(data);}
|
||||||
$(".range-slider__range").each(function(){$(this)[0].value=$(this).attr("value");$(this).next().html($(this).attr("value"));});};websock.onmessage=handleEvent;}
|
$(".range-slider__range").each(function(){$(this)[0].value=$(this).attr("value");$(this).next().html($(this).attr("value"));});};websock.onmessage=handleEvent;}
|
||||||
|
async function FileDisplayUploadFile(data)
|
||||||
|
{let text=await downloadFile(data.value);let ItemToUpdateId="fd"+data.id;$("#"+ItemToUpdateId).val(text);$("#"+ItemToUpdateId).css("textAlign","left");$("#"+ItemToUpdateId).css("white-space","nowrap");$("#"+ItemToUpdateId).css("overflow","scroll");$("#"+ItemToUpdateId).css("overflow-y","scroll");$("#"+ItemToUpdateId).css("overflow-x","scroll");$("#"+ItemToUpdateId).scrollTop($("#"+ItemToUpdateId).val().length);}
|
||||||
|
async function downloadFile(filename)
|
||||||
|
{let response=await fetch(filename);if(response.status!=200){throw new Error("File Read Server Error: '"+response.status+"'");}
|
||||||
|
let text_data=await response.text();return text_data;}
|
||||||
|
function StartFragmentAssemblyTimer(Id)
|
||||||
|
{StopFragmentAssemblyTimer(Id);FragmentAssemblyTimer[Id]=setInterval(function(_Id)
|
||||||
|
{if("undefined"!==typeof controlAssemblyArray[_Id])
|
||||||
|
{if(null!==controlAssemblyArray[_Id])
|
||||||
|
{let TotalRequest=JSON.stringify({'id':controlAssemblyArray[_Id].control.id,'offset':controlAssemblyArray[_Id].offset});websock.send("uifragmentok:"+0+": "+TotalRequest+":");}}},1000,Id);}
|
||||||
|
function StopFragmentAssemblyTimer(Id)
|
||||||
|
{if("undefined"!==typeof FragmentAssemblyTimer[Id])
|
||||||
|
{if(FragmentAssemblyTimer[Id])
|
||||||
|
{clearInterval(FragmentAssemblyTimer[Id]);}}}
|
||||||
function sliderchange(number){var val=$("#sl"+number).val();websock.send("slvalue:"+val+":"+number);$(".range-slider__range").each(function(){$(this).attr("value",$(this)[0].value);});}
|
function sliderchange(number){var val=$("#sl"+number).val();websock.send("slvalue:"+val+":"+number);$(".range-slider__range").each(function(){$(this).attr("value",$(this)[0].value);});}
|
||||||
function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);}
|
function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);}
|
||||||
function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);}
|
function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);}
|
||||||
@ -59,7 +90,7 @@ function padclick(type,number,isdown){if($("#id"+number+" nav").hasClass("disabl
|
|||||||
switch(type){case CENTER:if(isdown)websock.send("pcdown:"+number);else websock.send("pcup:"+number);break;case UP:if(isdown)websock.send("pfdown:"+number);else websock.send("pfup:"+number);break;case DOWN:if(isdown)websock.send("pbdown:"+number);else websock.send("pbup:"+number);break;case LEFT:if(isdown)websock.send("pldown:"+number);else websock.send("plup:"+number);break;case RIGHT:if(isdown)websock.send("prdown:"+number);else websock.send("prup:"+number);break;}}
|
switch(type){case CENTER:if(isdown)websock.send("pcdown:"+number);else websock.send("pcup:"+number);break;case UP:if(isdown)websock.send("pfdown:"+number);else websock.send("pfup:"+number);break;case DOWN:if(isdown)websock.send("pbdown:"+number);else websock.send("pbup:"+number);break;case LEFT:if(isdown)websock.send("pldown:"+number);else websock.send("plup:"+number);break;case RIGHT:if(isdown)websock.send("prdown:"+number);else websock.send("prup:"+number);break;}}
|
||||||
function switcher(number,state){if(state==null){if(!$("#sl"+number).hasClass("checked")){websock.send("sactive:"+number);$("#sl"+number).addClass("checked");}else{websock.send("sinactive:"+number);$("#sl"+number).removeClass("checked");}}else if(state==1){$("#sl"+number).addClass("checked");$("#sl"+number).prop("checked",true);}else if(state==0){$("#sl"+number).removeClass("checked");$("#sl"+number).prop("checked",false);}}
|
function switcher(number,state){if(state==null){if(!$("#sl"+number).hasClass("checked")){websock.send("sactive:"+number);$("#sl"+number).addClass("checked");}else{websock.send("sinactive:"+number);$("#sl"+number).removeClass("checked");}}else if(state==1){$("#sl"+number).addClass("checked");$("#sl"+number).prop("checked",true);}else if(state==0){$("#sl"+number).removeClass("checked");$("#sl"+number).prop("checked",false);}}
|
||||||
var rangeSlider=function(isDiscrete){var range=$(".range-slider__range");var slidercb=function(){sliderchange($(this).attr("id").replace(/^\D+/g,""));};range.on({input:function(){$(this).next().html(this.value)}});range.each(function(){$(this).next().html(this.value);if($(this).attr("callbackSet")!="true"){if(!isDiscrete){$(this).on({input:slidercb});}else{$(this).on({change:slidercb});}
|
var rangeSlider=function(isDiscrete){var range=$(".range-slider__range");var slidercb=function(){sliderchange($(this).attr("id").replace(/^\D+/g,""));};range.on({input:function(){$(this).next().html(this.value)}});range.each(function(){$(this).next().html(this.value);if($(this).attr("callbackSet")!="true"){if(!isDiscrete){$(this).on({input:slidercb});}else{$(this).on({change:slidercb});}
|
||||||
$(this).attr("callbackSet","true");}});};var addToHTML=function(data){panelStyle=data.hasOwnProperty('panelStyle')?" style='"+data.panelStyle+"' ":"";panelwide=data.hasOwnProperty('wide')?"wide":"";if(!data.hasOwnProperty('parentControl')||$("#tab"+data.parentControl).length>0){var parent=data.hasOwnProperty('parentControl')?$("#tab"+data.parentControl):$("#row");var html="";switch(data.type){case UI_LABEL:case UI_BUTTON:case UI_SWITCHER:case UI_CPAD:case UI_PAD:case UI_SLIDER:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GRAPH:case UI_GAUGE:case UI_ACCEL:html="<div id='id"+data.id+"' "+panelStyle+" class='two columns "+panelwide+" card tcenter "+
|
$(this).attr("callbackSet","true");}});};var addToHTML=function(data){panelStyle=data.hasOwnProperty('panelStyle')?" style='"+data.panelStyle+"' ":"";panelwide=data.hasOwnProperty('wide')?"wide":"";if(!data.hasOwnProperty('parentControl')||$("#tab"+data.parentControl).length>0){var parent=data.hasOwnProperty('parentControl')?$("#tab"+data.parentControl):$("#row");var html="";switch(data.type){case UI_LABEL:case UI_BUTTON:case UI_SWITCHER:case UI_CPAD:case UI_PAD:case UI_SLIDER:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GRAPH:case UI_GAUGE:case UI_ACCEL:case UI_FILEDISPLAY:html="<div id='id"+data.id+"' "+panelStyle+" class='two columns "+panelwide+" card tcenter "+
|
||||||
colorClass(data.color)+"'><h5>"+data.label+"</h5><hr/>"+
|
colorClass(data.color)+"'><h5>"+data.label+"</h5><hr/>"+
|
||||||
elementHTML(data)+
|
elementHTML(data)+
|
||||||
"</div>";break;case UI_SEPARATOR:html="<div id='id"+data.id+"' "+panelStyle+" class='sectionbreak columns'>"+
|
"</div>";break;case UI_SEPARATOR:html="<div id='id"+data.id+"' "+panelStyle+" class='sectionbreak columns'>"+
|
||||||
@ -67,7 +98,8 @@ elementHTML(data)+
|
|||||||
parent.append(html);}else{var parent=$("#id"+data.parentControl);parent.append(elementHTML(data));}}
|
parent.append(html);}else{var parent=$("#id"+data.parentControl);parent.append(elementHTML(data));}}
|
||||||
var elementHTML=function(data){var id=data.id
|
var elementHTML=function(data){var id=data.id
|
||||||
var elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"' ":"";var inputType=data.hasOwnProperty('inputType')?" type='"+data.inputType+"' ":"";switch(data.type){case UI_LABEL:return"<span id='l"+id+"' "+elementStyle+
|
var elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"' ":"";var inputType=data.hasOwnProperty('inputType')?" type='"+data.inputType+"' ":"";switch(data.type){case UI_LABEL:return"<span id='l"+id+"' "+elementStyle+
|
||||||
" class='label label-wrap'>"+data.value+"</span>";case UI_BUTTON:return"<button id='btn"+id+"' "+elementStyle+
|
" class='label label-wrap'>"+data.value+"</span>";case UI_FILEDISPLAY:return"<textarea id='fd"+id+"' rows='4' "+elementStyle+
|
||||||
|
" class='label label-wrap'>"+"</textarea>";case UI_BUTTON:return"<button id='btn"+id+"' "+elementStyle+
|
||||||
" onmousedown='buttonclick("+id+", true)'"+
|
" onmousedown='buttonclick("+id+", true)'"+
|
||||||
" onmouseup='buttonclick("+id+", false)'>"+
|
" onmouseup='buttonclick("+id+", false)'>"+
|
||||||
data.value+"</button>";case UI_SWITCHER:return"<label id='sl"+id+"' "+elementStyle+
|
data.value+"</button>";case UI_SWITCHER:return"<label id='sl"+id+"' "+elementStyle+
|
||||||
@ -93,12 +125,12 @@ data.value+"</button>";case UI_SWITCHER:return"<label id='sl"+id+"' "+elementSty
|
|||||||
"'>"+
|
"'>"+
|
||||||
"<input id='sl"+id+"' type='range' min='0' max='100' value='"+data.value+"' "+
|
"<input id='sl"+id+"' type='range' min='0' max='100' value='"+data.value+"' "+
|
||||||
elementStyle+" class='range-slider__range'><span class='range-slider__value'>"+
|
elementStyle+" class='range-slider__range'><span class='range-slider__value'>"+
|
||||||
data.value+"</span></div>";case UI_NUMBER:return"<input style='color:black;' "+elementStyle+" id='num"+id+
|
data.value+"</span></div>";case UI_NUMBER:return"<input style='color:black; "+data.elementStyle+"' id='num"+id+
|
||||||
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_TEXT_INPUT:return"<input "+inputType+"style='color:black;' "+elementStyle+" id='text"+id+
|
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_TEXT_INPUT:return"<input "+inputType+"style='color:black; "+data.elementStyle+"' id='text"+id+
|
||||||
"' value='"+data.value+"' onchange='textchange("+id+")' />";case UI_SELECT:return"<select style='color:black;' "+elementStyle+" id='select"+id+
|
"' value='"+data.value+"' onchange='textchange("+id+")' />";case UI_SELECT:return"<select style='color:black; "+data.elementStyle+"' id='select"+id+
|
||||||
"' onchange='selectchange("+id+")' />";case UI_GRAPH:return"<figure id='graph"+id+"'><figcaption>"+data.label+"</figcaption></figure>";case UI_GAUGE:return"WILL BE A GAUGE <input style='color:black;' id='gauge"+id+
|
"' onchange='selectchange("+id+")' />";case UI_GRAPH:return"<figure id='graph"+id+"'><figcaption>"+data.label+"</figcaption></figure>";case UI_GAUGE:return"WILL BE A GAUGE <input style='color:black;' id='gauge"+id+
|
||||||
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_ACCEL:return"ACCEL // Not implemented fully!<div class='accelerometer' id='accel"+id+
|
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_ACCEL:return"ACCEL // Not implemented fully!<div class='accelerometer' id='accel"+id+
|
||||||
"' ><div class='ball"+id+"'></div><pre class='accelerometeroutput"+id+"'></pre>";default:return"";}}
|
"' ><div class='ball"+id+"'></div><pre class='accelerometeroutput"+id+"'></pre>";default:return"";}}
|
||||||
var processEnabled=function(data){switch(data.type){case UI_SWITCHER:case UPDATE_SWITCHER:if(data.enabled){$("#sl"+data.id).removeClass('disabled');$("#s"+data.id).prop("disabled",false);}else{$("#sl"+data.id).addClass('disabled');$("#s"+data.id).prop("disabled",true);}
|
var processEnabled=function(data){switch(data.type){case UI_SWITCHER:case UPDATE_SWITCHER:if(data.enabled){$("#sl"+data.id).removeClass('disabled');$("#s"+data.id).prop("disabled",false);}else{$("#sl"+data.id).addClass('disabled');$("#s"+data.id).prop("disabled",true);}
|
||||||
break;case UI_SLIDER:case UPDATE_SLIDER:$("#sl"+data.id).prop("disabled",!data.enabled);break;case UI_NUMBER:case UPDATE_NUMBER:$("#num"+data.id).prop("disabled",!data.enabled);break;case UI_TEXT_INPUT:case UPDATE_TEXT_INPUT:$("#text"+data.id).prop("disabled",!data.enabled);break;case UI_SELECT:case UPDATE_SELECT:$("#select"+data.id).prop("disabled",!data.enabled);break;case UI_BUTTON:case UPDATE_BUTTON:$("#btn"+data.id).prop("disabled",!data.enabled);break;case UI_PAD:case UI_CPAD:case UPDATE_PAD:case UPDATE_CPAD:if(data.enabled){$("#id"+data.id+" nav").removeClass('disabled');}else{$("#id"+data.id+" nav").addClass('disabled');}
|
break;case UI_SLIDER:case UPDATE_SLIDER:$("#sl"+data.id).prop("disabled",!data.enabled);break;case UI_NUMBER:case UPDATE_NUMBER:$("#num"+data.id).prop("disabled",!data.enabled);break;case UI_TEXT_INPUT:case UPDATE_TEXT_INPUT:$("#text"+data.id).prop("disabled",!data.enabled);break;case UI_SELECT:case UPDATE_SELECT:$("#select"+data.id).prop("disabled",!data.enabled);break;case UI_BUTTON:case UPDATE_BUTTON:$("#btn"+data.id).prop("disabled",!data.enabled);break;case UI_PAD:case UI_CPAD:case UPDATE_PAD:case UPDATE_CPAD:case UI_FILEDISPLAY:case UPDATE_FILEDISPLAY:if(data.enabled){$("#id"+data.id+" nav").removeClass('disabled');}else{$("#id"+data.id+" nav").addClass('disabled');}
|
||||||
break;}}
|
break;}}
|
@ -55,13 +55,13 @@ function lineGraph(parent, xAccessor, yAccessor) {
|
|||||||
|
|
||||||
// generate labels
|
// generate labels
|
||||||
for (var i = xMin; i <= xMax; i++) {
|
for (var i = xMin; i <= xMax; i++) {
|
||||||
if ((i - xMin) % pixelsPerTick === 0 && i !== xMin) {
|
if ((i - xMin) % (pixelsPerTick*3) === 0 && i !== xMin) {
|
||||||
var text = document.createElementNS(
|
var text = document.createElementNS(
|
||||||
"http://www.w3.org/2000/svg",
|
"http://www.w3.org/2000/svg",
|
||||||
"text"
|
"text"
|
||||||
);
|
);
|
||||||
// primitive formatting
|
// primitive formatting
|
||||||
text.innerHTML = Math.floor(transform(i));
|
text.innerHTML = new Date(Math.floor(transform(i))).toLocaleTimeString();
|
||||||
text.setAttribute("x", i);
|
text.setAttribute("x", i);
|
||||||
text.setAttribute("y", yMin);
|
text.setAttribute("y", yMin);
|
||||||
// offset the text by 1 em
|
// offset the text by 1 em
|
||||||
|
2
data/js/graph.min.js
vendored
2
data/js/graph.min.js
vendored
@ -1,5 +1,5 @@
|
|||||||
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(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 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===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));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);}}}
|
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);}
|
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;}
|
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 "+
|
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+
|
||||||
|
BIN
docs/ui_fileDisplay.png
Normal file
BIN
docs/ui_fileDisplay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
@ -26,8 +26,20 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#else
|
#else
|
||||||
|
// esp8266
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
#ifndef MMU_IRAM_HEAP
|
||||||
|
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
|
||||||
|
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
|
||||||
|
#warning then check http://<ip>/heap
|
||||||
|
#endif // MMU_IRAM_HEAP
|
||||||
|
#ifndef DEBUG_ESP_OOM
|
||||||
|
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//Settings
|
//Settings
|
||||||
@ -49,6 +61,7 @@ void getTimeCallback(Control *sender, int type);
|
|||||||
void graphAddCallback(Control *sender, int type);
|
void graphAddCallback(Control *sender, int type);
|
||||||
void graphClearCallback(Control *sender, int type);
|
void graphClearCallback(Control *sender, int type);
|
||||||
void randomString(char *buf, int len);
|
void randomString(char *buf, int len);
|
||||||
|
void extendedCallback(Control* sender, int type, void* param);
|
||||||
|
|
||||||
//UI handles
|
//UI handles
|
||||||
uint16_t wifi_ssid_text, wifi_pass_text;
|
uint16_t wifi_ssid_text, wifi_pass_text;
|
||||||
@ -61,6 +74,11 @@ volatile bool updates = false;
|
|||||||
|
|
||||||
// This is the main function which builds our GUI
|
// This is the main function which builds our GUI
|
||||||
void setUpUI() {
|
void setUpUI() {
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
{ HeapSelectIram doAllocationsInIRAM;
|
||||||
|
#endif
|
||||||
|
|
||||||
//Turn off verbose debugging
|
//Turn off verbose debugging
|
||||||
ESPUI.setVerbosity(Verbosity::Quiet);
|
ESPUI.setVerbosity(Verbosity::Quiet);
|
||||||
|
|
||||||
@ -81,7 +99,7 @@ void setUpUI() {
|
|||||||
auto maintab = ESPUI.addControl(Tab, "", "Basic controls");
|
auto maintab = ESPUI.addControl(Tab, "", "Basic controls");
|
||||||
|
|
||||||
ESPUI.addControl(Separator, "General controls", "", None, maintab);
|
ESPUI.addControl(Separator, "General controls", "", None, maintab);
|
||||||
ESPUI.addControl(Button, "Button", "Button 1", Alizarin, maintab, generalCallback);
|
ESPUI.addControl(Button, "Button", "Button 1", Alizarin, maintab, extendedCallback, (void*)19);
|
||||||
mainLabel = ESPUI.addControl(Label, "Label", "Label text", Emerald, maintab, generalCallback);
|
mainLabel = ESPUI.addControl(Label, "Label", "Label text", Emerald, maintab, generalCallback);
|
||||||
mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback);
|
mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback);
|
||||||
|
|
||||||
@ -273,6 +291,11 @@ void setUpUI() {
|
|||||||
//Finally, start up the UI.
|
//Finally, start up the UI.
|
||||||
//This should only be called once we are connected to WiFi.
|
//This should only be called once we are connected to WiFi.
|
||||||
ESPUI.begin(HOSTNAME);
|
ESPUI.begin(HOSTNAME);
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
} // HeapSelectIram
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//This callback generates and applies inline styles to a bunch of controls to change their colour.
|
//This callback generates and applies inline styles to a bunch of controls to change their colour.
|
||||||
@ -360,7 +383,24 @@ void generalCallback(Control *sender, int type) {
|
|||||||
Serial.println(sender->value);
|
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() {
|
void setup() {
|
||||||
randomSeed(0);
|
randomSeed(0);
|
||||||
@ -406,6 +446,7 @@ void loop() {
|
|||||||
#if !defined(ESP32)
|
#if !defined(ESP32)
|
||||||
((void (*)())0xf00fdead)();
|
((void (*)())0xf00fdead)();
|
||||||
#endif
|
#endif
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Serial.print('#');
|
Serial.print('#');
|
||||||
break;
|
break;
|
||||||
|
4
examples/completeExample/completeExample.ino
Normal file
4
examples/completeExample/completeExample.ino
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// placeholder
|
||||||
|
#if CORE_MOCK
|
||||||
|
#include "completeExample.cpp"
|
||||||
|
#endif
|
544
examples/completeLambda/completeLambda.ino
Normal file
544
examples/completeLambda/completeLambda.ino
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.
|
||||||
|
*
|
||||||
|
* Version with lambdas. Comparing to version with only callbacks:
|
||||||
|
* diff -u ../completeExample/completeExample.cpp completeLambda.ino|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>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
#ifndef MMU_IRAM_HEAP
|
||||||
|
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
|
||||||
|
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
|
||||||
|
#warning then check http://<ip>/heap
|
||||||
|
#endif // MMU_IRAM_HEAP
|
||||||
|
#ifndef DEBUG_ESP_OOM
|
||||||
|
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#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() {
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
{ HeapSelectIram doAllocationsInIRAM;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//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);
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
} // HeapSelectIram
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//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';
|
||||||
|
}
|
@ -8,7 +8,19 @@ DNSServer dnsServer;
|
|||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#else
|
#else
|
||||||
|
// esp8266
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
#ifndef MMU_IRAM_HEAP
|
||||||
|
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
|
||||||
|
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
|
||||||
|
#warning then check http://<ip>/heap
|
||||||
|
#endif // MMU_IRAM_HEAP
|
||||||
|
#if !defined(DEBUG_ESP_OOM)
|
||||||
|
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char* ssid = "ESPUI";
|
const char* ssid = "ESPUI";
|
||||||
@ -55,8 +67,10 @@ void buttonCallback(Control* sender, int type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void buttonExample(Control* sender, int type)
|
void buttonExample(Control* sender, int type, void* param)
|
||||||
{
|
{
|
||||||
|
Serial.print("param: ");
|
||||||
|
Serial.println((long)param);
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case B_DOWN:
|
case B_DOWN:
|
||||||
@ -210,7 +224,8 @@ void setup(void)
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
uint32_t chipid = ESP.getChipId();
|
uint32_t chipid = ESP.getChipId();
|
||||||
#endif char ap_ssid[25];
|
#endif
|
||||||
|
char ap_ssid[25];
|
||||||
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
||||||
WiFi.softAP(ap_ssid);
|
WiFi.softAP(ap_ssid);
|
||||||
|
|
||||||
@ -233,6 +248,10 @@ void setup(void)
|
|||||||
Serial.print("IP address: ");
|
Serial.print("IP address: ");
|
||||||
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
{ HeapSelectIram doAllocationsInIRAM;
|
||||||
|
#endif
|
||||||
|
|
||||||
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
|
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
|
||||||
|
|
||||||
uint16_t select1 = ESPUI.addControl(
|
uint16_t select1 = ESPUI.addControl(
|
||||||
@ -249,7 +268,7 @@ void setup(void)
|
|||||||
button1 = ESPUI.addControl(
|
button1 = ESPUI.addControl(
|
||||||
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
|
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
|
||||||
ESPUI.addControl(
|
ESPUI.addControl(
|
||||||
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample);
|
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample, (void*)19);
|
||||||
ESPUI.addControl(
|
ESPUI.addControl(
|
||||||
ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
|
ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
|
||||||
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
|
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
|
||||||
@ -279,6 +298,10 @@ void setup(void)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
ESPUI.begin("ESPUI Control");
|
ESPUI.begin("ESPUI Control");
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
} // HeapSelectIram
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(void)
|
void loop(void)
|
||||||
|
@ -8,7 +8,19 @@ DNSServer dnsServer;
|
|||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#else
|
#else
|
||||||
|
// esp8266
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
#ifndef MMU_IRAM_HEAP
|
||||||
|
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
|
||||||
|
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
|
||||||
|
#warning then check http://<ip>/heap
|
||||||
|
#endif // MMU_IRAM_HEAP
|
||||||
|
#ifndef DEBUG_ESP_OOM
|
||||||
|
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char* ssid = "ESPUI";
|
const char* ssid = "ESPUI";
|
||||||
@ -60,8 +72,10 @@ void buttonCallback(Control* sender, int type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void buttonExample(Control* sender, int type)
|
void buttonExample(Control* sender, int type, void* param)
|
||||||
{
|
{
|
||||||
|
Serial.print("param: ");
|
||||||
|
Serial.println((long)param);
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case B_DOWN:
|
case B_DOWN:
|
||||||
@ -224,10 +238,14 @@ void setup(void)
|
|||||||
Serial.print("IP address: ");
|
Serial.print("IP address: ");
|
||||||
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
{ HeapSelectIram doAllocationsInIRAM;
|
||||||
|
#endif
|
||||||
|
|
||||||
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
||||||
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
||||||
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
|
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
|
||||||
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press");
|
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press", (void*)19);
|
||||||
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
|
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
|
||||||
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
|
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
|
||||||
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
||||||
@ -255,8 +273,11 @@ void setup(void)
|
|||||||
* since it is transmitted in cleartext. Just add a string as username and
|
* since it is transmitted in cleartext. Just add a string as username and
|
||||||
* password, for example begin("ESPUI Control", "username", "password")
|
* password, for example begin("ESPUI Control", "username", "password")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ESPUI.begin("ESPUI Control");
|
ESPUI.begin("ESPUI Control");
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
} // HeapSelectIram
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(void)
|
void loop(void)
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
void setup(void)
|
void setup(void)
|
||||||
{
|
{
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
ESPUI.prepareFileSystem();
|
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()
|
void loop()
|
||||||
|
@ -8,7 +8,19 @@ DNSServer dnsServer;
|
|||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#else
|
#else
|
||||||
|
// esp8266
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
#ifndef MMU_IRAM_HEAP
|
||||||
|
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
|
||||||
|
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
|
||||||
|
#warning then check http://<ip>/heap
|
||||||
|
#endif // MMU_IRAM_HEAP
|
||||||
|
#ifndef DEBUG_ESP_OOM
|
||||||
|
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char* ssid = "ESPUI";
|
const char* ssid = "ESPUI";
|
||||||
@ -54,8 +66,10 @@ void buttonCallback(Control* sender, int type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void buttonExample(Control* sender, int type)
|
void buttonExample(Control* sender, int type, void* param)
|
||||||
{
|
{
|
||||||
|
Serial.print("param: ");
|
||||||
|
Serial.println((long)param);
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case B_DOWN:
|
case B_DOWN:
|
||||||
@ -232,6 +246,10 @@ void setup(void)
|
|||||||
Serial.print("IP address: ");
|
Serial.print("IP address: ");
|
||||||
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
{ HeapSelectIram doAllocationsInIRAM;
|
||||||
|
#endif
|
||||||
|
|
||||||
uint16_t tab1 = ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1");
|
uint16_t tab1 = ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1");
|
||||||
uint16_t tab2 = ESPUI.addControl(ControlType::Tab, "Settings 2", "Settings 2");
|
uint16_t tab2 = ESPUI.addControl(ControlType::Tab, "Settings 2", "Settings 2");
|
||||||
uint16_t tab3 = ESPUI.addControl(ControlType::Tab, "Settings 3", "Settings 3");
|
uint16_t tab3 = ESPUI.addControl(ControlType::Tab, "Settings 3", "Settings 3");
|
||||||
@ -251,7 +269,7 @@ void setup(void)
|
|||||||
ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1);
|
ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1);
|
||||||
button1 = ESPUI.addControl(
|
button1 = ESPUI.addControl(
|
||||||
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback);
|
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback);
|
||||||
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample);
|
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::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, tab2, &padExample);
|
||||||
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample);
|
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample);
|
||||||
switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample);
|
switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample);
|
||||||
@ -278,6 +296,10 @@ void setup(void)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
ESPUI.begin("ESPUI Control");
|
ESPUI.begin("ESPUI Control");
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
} // HeapSelectIram
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(void)
|
void loop(void)
|
||||||
|
@ -25,6 +25,7 @@ beginLITTLEFS KEYWORD2
|
|||||||
print KEYWORD2
|
print KEYWORD2
|
||||||
updateSwitcher KEYWORD2
|
updateSwitcher KEYWORD2
|
||||||
updateSlider KEYWORD2
|
updateSlider KEYWORD2
|
||||||
|
captivePortal LITERAL1
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Instances (KEYWORD2)
|
# Instances (KEYWORD2)
|
||||||
|
@ -38,6 +38,25 @@
|
|||||||
"LANG_ESPUI_SWITCH_SWITCH": "UI 开关",
|
"LANG_ESPUI_SWITCH_SWITCH": "UI 开关",
|
||||||
"LANG_ESPUI_PAD_PAD": "UI 面板",
|
"LANG_ESPUI_PAD_PAD": "UI 面板",
|
||||||
"LANG_ESPUI_PAD_CENTER": "中间按钮?"
|
"LANG_ESPUI_PAD_CENTER": "中间按钮?"
|
||||||
|
},
|
||||||
|
"tr-TR": {
|
||||||
|
"keys": {
|
||||||
|
"LANG_SUBCATERGORY_ESPUI": "Kullanıcı Arayüzü",
|
||||||
|
"LANG_ESPUI_ESPUI_TITLE": "Başlık",
|
||||||
|
"LANG_ESPUI_ESPUI_HOTSPOT": "Wifi Erişim Noktası Kodunu Etkinleştir",
|
||||||
|
"LANG_ESPUI_ESPUI_TOOLTIP": "ESP32/ESP8266'da bir web arayüzü oluşturur",
|
||||||
|
"LANG_ESPUI_BUTTON_BUTTON": "Kullanıcı Arayüzü Düğmesi",
|
||||||
|
"LANG_ESPUI_NAME": "ad",
|
||||||
|
"LANG_ESPUI_COLOR": "Renk",
|
||||||
|
"LANG_ESPUI_TEXT": "Metin",
|
||||||
|
"LANG_ESPUI_BUTTON_TOOLTIP": "Bir web arayüzü düğmesi",
|
||||||
|
"LANG_ESPUI_LABEL_LABEL": "Kullanıcı Arayüzü Etiketi",
|
||||||
|
"LANG_ESPUI_LABEL": "Etiket",
|
||||||
|
"LANG_ESPUI_LABEL_TOOLTIP": "Kodunuzdan güncelleyebileceğiniz bir web arayüzü etiketi",
|
||||||
|
"LANG_ESPUI_STATE": "Durum",
|
||||||
|
"LANG_ESPUI_SWITCH_SWITCH": "Kullanıcı Arayüzü Anahtarı",
|
||||||
|
"LANG_ESPUI_PAD_PAD": "UI Pad",
|
||||||
|
"LANG_ESPUI_PAD_CENTER": "Orta düğme?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
library.json
13
library.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ESPUI",
|
"name": "ESPUI",
|
||||||
"keywords": "espressif web interface iot control simple easy ui userinterface",
|
"keywords": "espressif web interface iot easy ui",
|
||||||
"description": "ESP32 and ESP8266 Web Interface Library",
|
"description": "ESP32 and ESP8266 Web Interface Library",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -16,22 +16,17 @@
|
|||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "ESP Async WebServer",
|
"name": "ESPAsyncWebServer",
|
||||||
"authors": "Hristo Gochkov",
|
"authors": "ESP32Async",
|
||||||
"frameworks": "arduino"
|
"frameworks": "arduino"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ArduinoJson",
|
"name": "ArduinoJson",
|
||||||
"authors": "Benoit Blanchon",
|
"authors": "Benoit Blanchon",
|
||||||
"frameworks": "arduino"
|
"frameworks": "arduino"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LittleFS_esp32",
|
|
||||||
"authors": "lorol",
|
|
||||||
"frameworks": "arduino"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": "2.1.1",
|
"version": "2.2.4",
|
||||||
"frameworks": "arduino",
|
"frameworks": "arduino",
|
||||||
"platforms": "*"
|
"platforms": "*"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name=ESPUI
|
name=ESPUI
|
||||||
version=2.1.1
|
version=2.2.4
|
||||||
author=Lukas Bachschwell
|
author=Lukas Bachschwell
|
||||||
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
|
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
|
||||||
sentence=ESP32 and ESP8266 Web Interface Library
|
sentence=ESP32 and ESP8266 Web Interface Library
|
||||||
|
@ -13,25 +13,41 @@ src_dir = ./src
|
|||||||
data_dir = ../../data
|
data_dir = ../../data
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
lib_extra_dirs = ../../
|
framework = arduino
|
||||||
board_build.filesystem = littlefs
|
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
|
||||||
|
|
||||||
; Additional scripts: Usage: see https://github.com/s00500/ESPUI/issues/144#issuecomment-1005135077
|
; Additional scripts: Usage: see https://github.com/s00500/ESPUI/issues/144#issuecomment-1005135077
|
||||||
;extra_scripts =
|
;extra_scripts =
|
||||||
; LittleFSBuilder.py
|
; LittleFSBuilder.py
|
||||||
|
|
||||||
[env:esp8266]
|
[env:esp8266]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
framework = arduino
|
|
||||||
board = nodemcuv2
|
board = nodemcuv2
|
||||||
lib_deps =
|
upload_port = COM8
|
||||||
bblanchon/ArduinoJson @ ^6.18.5
|
monitor_port = COM8
|
||||||
me-no-dev/ESP Async WebServer @ ^1.2.3
|
monitor_speed = 115200
|
||||||
|
|
||||||
[env:esp32]
|
[env:esp32]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
framework = arduino
|
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
board_build.flash_mode = dout
|
||||||
|
build_flags =
|
||||||
|
; -D DEBUG_ESPUI
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
lorol/LittleFS_esp32@^1.0.6
|
${env.lib_deps}
|
||||||
bblanchon/ArduinoJson @ ^6.18.5
|
me-no-dev/AsyncTCP
|
||||||
me-no-dev/ESP Async WebServer @ ^1.2.3
|
upload_port = COM6
|
||||||
|
monitor_port = COM6
|
||||||
|
monitor_speed = 115200
|
||||||
|
@ -11,15 +11,23 @@ DNSServer dnsServer;
|
|||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char* ssid = "ESPUI";
|
const char* ssid = "YourNetworkName";
|
||||||
const char* password = "espui";
|
const char* password = "YourNetworkPassphrase";
|
||||||
|
|
||||||
const char* hostname = "espui";
|
const char* hostname = "espui";
|
||||||
|
|
||||||
int statusLabelId;
|
String DisplayTestFileName = "/FileName.txt";
|
||||||
int graphId;
|
int fileDisplayId = Control::noParent;
|
||||||
int millisLabelId;
|
|
||||||
int testSwitchId;
|
int statusLabelId = Control::noParent;
|
||||||
|
|
||||||
|
#ifdef TEST_GRAPH
|
||||||
|
int graphId = Control::noParent;
|
||||||
|
#endif // def TEST_GRAPH
|
||||||
|
int millisLabelId = Control::noParent;
|
||||||
|
int testSwitchId = Control::noParent;
|
||||||
|
|
||||||
|
char HugeText[1025];
|
||||||
|
|
||||||
void numberCall(Control* sender, int type)
|
void numberCall(Control* sender, int type)
|
||||||
{
|
{
|
||||||
@ -60,8 +68,9 @@ void buttonCallback(Control* sender, int type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void buttonExample(Control* sender, int type)
|
void buttonExample(Control* sender, int type, void* param)
|
||||||
{
|
{
|
||||||
|
Serial.println(String("param: ") + String(long(param)));
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case B_DOWN:
|
case B_DOWN:
|
||||||
@ -163,6 +172,9 @@ void setup(void)
|
|||||||
ESPUI.setVerbosity(Verbosity::VerboseJSON);
|
ESPUI.setVerbosity(Verbosity::VerboseJSON);
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
memset(HugeText, 0x0, sizeof(HugeText));
|
||||||
|
memset(HugeText, 'a', sizeof(HugeText)-1);
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
WiFi.setHostname(hostname);
|
WiFi.setHostname(hostname);
|
||||||
#else
|
#else
|
||||||
@ -228,7 +240,7 @@ void setup(void)
|
|||||||
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
||||||
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
||||||
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
|
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
|
||||||
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press");
|
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press", (void*)19);
|
||||||
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
|
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
|
||||||
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
|
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
|
||||||
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
||||||
@ -236,9 +248,16 @@ void setup(void)
|
|||||||
ESPUI.slider("Slider one", &slider, ControlColor::Alizarin, 30, 0, 30);
|
ESPUI.slider("Slider one", &slider, ControlColor::Alizarin, 30, 0, 30);
|
||||||
ESPUI.slider("Slider two", &slider, ControlColor::None, 100);
|
ESPUI.slider("Slider two", &slider, ControlColor::None, 100);
|
||||||
ESPUI.text("Text Test:", &textCall, ControlColor::Alizarin, "a Text Field");
|
ESPUI.text("Text Test:", &textCall, ControlColor::Alizarin, "a Text Field");
|
||||||
|
|
||||||
|
ESPUI.text("Huge Text Test:", &textCall, ControlColor::Alizarin, HugeText);
|
||||||
|
|
||||||
ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);
|
ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);
|
||||||
|
|
||||||
|
fileDisplayId = ESPUI.fileDisplay("Filetest", ControlColor::Turquoise, DisplayTestFileName);
|
||||||
|
|
||||||
|
#ifdef TEST_GRAPH
|
||||||
graphId = ESPUI.graph("Graph Test", ControlColor::Wetasphalt);
|
graphId = ESPUI.graph("Graph Test", ControlColor::Wetasphalt);
|
||||||
|
#endif // def TEST_GRAPH
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* .begin loads and serves all files from PROGMEM directly.
|
* .begin loads and serves all files from PROGMEM directly.
|
||||||
@ -257,7 +276,17 @@ void setup(void)
|
|||||||
* password, for example begin("ESPUI Control", "username", "password")
|
* password, for example begin("ESPUI Control", "username", "password")
|
||||||
*/
|
*/
|
||||||
ESPUI.sliderContinuous = true;
|
ESPUI.sliderContinuous = true;
|
||||||
ESPUI.begin("ESPUI Control");
|
|
||||||
|
ESPUI.prepareFileSystem();
|
||||||
|
|
||||||
|
ESPUI.beginLITTLEFS("ESPUI Control");
|
||||||
|
|
||||||
|
// these files are used by browsers to auto config a connection.
|
||||||
|
ESPUI.writeFile("/wpad.dat", " ");
|
||||||
|
ESPUI.writeFile("/connecttest.txt", " ");
|
||||||
|
|
||||||
|
// create a text file
|
||||||
|
ESPUI.writeFile("/DisplayFile.txt", "Test Line\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(void)
|
void loop(void)
|
||||||
@ -267,17 +296,33 @@ void loop(void)
|
|||||||
static long oldTime = 0;
|
static long oldTime = 0;
|
||||||
static bool testSwitchState = false;
|
static bool testSwitchState = false;
|
||||||
delay(10);
|
delay(10);
|
||||||
return;
|
|
||||||
|
|
||||||
if (millis() - oldTime > 5000)
|
if (millis() - oldTime > 5000)
|
||||||
{
|
{
|
||||||
ESPUI.print(millisLabelId, String(millis()));
|
ESPUI.print(millisLabelId, String(millis()));
|
||||||
|
|
||||||
|
#ifdef TEST_GRAPH
|
||||||
ESPUI.addGraphPoint(graphId, random(1, 50));
|
ESPUI.addGraphPoint(graphId, random(1, 50));
|
||||||
|
#endif // def TEST_GRAPH
|
||||||
|
|
||||||
testSwitchState = !testSwitchState;
|
testSwitchState = !testSwitchState;
|
||||||
ESPUI.updateSwitcher(testSwitchId, testSwitchState);
|
ESPUI.updateSwitcher(testSwitchId, testSwitchState);
|
||||||
|
|
||||||
|
// update the file Display file.
|
||||||
|
File testFile = ESPUI.EspuiLittleFS.open(String("/") + DisplayTestFileName, "a");
|
||||||
|
uint32_t filesize = testFile.size();
|
||||||
|
|
||||||
|
String TestLine = String("Current Time = ") + String(millis()) + "\n";
|
||||||
|
if(filesize < 1000)
|
||||||
|
{
|
||||||
|
testFile.write((const uint8_t*)TestLine.c_str(), TestLine.length());
|
||||||
|
ESPUI.updateControl(fileDisplayId);
|
||||||
|
|
||||||
|
TestLine += String("filesize: ") + String(filesize);
|
||||||
|
// Serial.println(TestLine);
|
||||||
|
}
|
||||||
|
testFile.close();
|
||||||
|
|
||||||
oldTime = millis();
|
oldTime = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1092
src/ESPUI.cpp
1092
src/ESPUI.cpp
File diff suppressed because it is too large
Load Diff
354
src/ESPUI.h
354
src/ESPUI.h
@ -1,18 +1,42 @@
|
|||||||
#ifndef ESPUI_h
|
#pragma once
|
||||||
#define ESPUI_h
|
|
||||||
|
|
||||||
#define DEBUG_ESPUI true
|
// comment out to turn off debug output
|
||||||
|
// #define DEBUG_ESPUI true
|
||||||
#define WS_AUTHENTICATION false
|
#define WS_AUTHENTICATION false
|
||||||
|
|
||||||
#include "Arduino.h"
|
#include <Arduino.h>
|
||||||
#include "ArduinoJson.h"
|
|
||||||
#include "stdlib_noniso.h"
|
#include <ArduinoJson.h>
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR > 6
|
||||||
|
#define AllocateJsonDocument(name, size) JsonDocument name
|
||||||
|
#define AllocateJsonArray(doc, name) doc[name].to<JsonArray>()
|
||||||
|
#define AllocateJsonObject(doc) doc.add<JsonObject>()
|
||||||
|
#define AllocateNamedJsonObject(t, s, n) t[n] = s
|
||||||
|
#else
|
||||||
|
#define AllocateJsonDocument(name, size) DynamicJsonDocument name(size)
|
||||||
|
#define AllocateJsonArray(doc, name) doc.createNestedArray(name)
|
||||||
|
#define AllocateJsonObject(doc) doc.createNestedObject()
|
||||||
|
#define AllocateNamedJsonObject(t, s, n) t = s.createNestedObject(n)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdlib_noniso.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR >= 4) || ESP_IDF_VERSION_MAJOR > 4
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#else
|
||||||
|
#include <LITTLEFS.h>
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#endif
|
||||||
|
#include <map>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#include "ESPUIcontrol.h"
|
||||||
|
#include "ESPUIclient.h"
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <AsyncTCP.h>
|
#include <AsyncTCP.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
#include <LITTLEFS.h>
|
|
||||||
|
|
||||||
#include "WiFi.h"
|
#include "WiFi.h"
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@ -21,168 +45,26 @@
|
|||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include <ESPAsyncTCP.h>
|
#include <ESPAsyncTCP.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
#include <Hash.h>
|
#include <Hash.h>
|
||||||
#include <LittleFS.h>
|
|
||||||
|
|
||||||
#define FILE_WRITE "w"
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define FILE_WRITING "w"
|
||||||
|
|
||||||
// Message Types (and control types)
|
// Message Types (and control types)
|
||||||
|
|
||||||
enum ControlType : uint8_t
|
enum MessageTypes : uint8_t
|
||||||
{
|
{
|
||||||
// fixed controls
|
|
||||||
Title = 0,
|
|
||||||
|
|
||||||
// updatable controls
|
|
||||||
Pad,
|
|
||||||
PadWithCenter,
|
|
||||||
Button,
|
|
||||||
Label,
|
|
||||||
Switcher,
|
|
||||||
Slider,
|
|
||||||
Number,
|
|
||||||
Text,
|
|
||||||
Graph,
|
|
||||||
GraphPoint,
|
|
||||||
Tab,
|
|
||||||
Select,
|
|
||||||
Option,
|
|
||||||
Min,
|
|
||||||
Max,
|
|
||||||
Step,
|
|
||||||
Gauge,
|
|
||||||
Accel,
|
|
||||||
Separator,
|
|
||||||
Time,
|
|
||||||
|
|
||||||
UpdateOffset = 100,
|
|
||||||
UpdatePad = 101,
|
|
||||||
UpdatePadWithCenter,
|
|
||||||
ButtonButton,
|
|
||||||
UpdateLabel,
|
|
||||||
UpdateSwitcher,
|
|
||||||
UpdateSlider,
|
|
||||||
UpdateNumber,
|
|
||||||
UpdateText,
|
|
||||||
ClearGraph,
|
|
||||||
UpdateTab,
|
|
||||||
UpdateSelection,
|
|
||||||
UpdateOption,
|
|
||||||
UpdateMin,
|
|
||||||
UpdateMax,
|
|
||||||
UpdateStep,
|
|
||||||
UpdateGauge,
|
|
||||||
UpdateAccel,
|
|
||||||
UpdateSeparator,
|
|
||||||
UpdateTime,
|
|
||||||
|
|
||||||
InitialGui = 200,
|
InitialGui = 200,
|
||||||
Reload = 201,
|
Reload = 201,
|
||||||
ExtendGUI = 210
|
ExtendGUI = 210,
|
||||||
|
UpdateGui = 220,
|
||||||
|
ExtendedUpdateGui = 230,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define UI_INITIAL_GUI ControlType::InitialGui
|
#define UI_INITIAL_GUI MessageTypes::InitialGui
|
||||||
#define UI_RELOAD ControlType::Reload
|
#define UI_EXTEND_GUI MessageTypes::ExtendGUI
|
||||||
#define UI_EXTEND_GUI ControlType::ExtendGUI
|
#define UI_RELOAD MessageTypes::Reload
|
||||||
|
|
||||||
#define UI_TITLE ControlType::Title
|
|
||||||
#define UI_LABEL ControlType::Label
|
|
||||||
#define UI_BUTTON ControlType::Button
|
|
||||||
#define UI_SWITCHER ControlType::Switcher
|
|
||||||
#define UI_PAD ControlType::Pad
|
|
||||||
#define UI_CPAD ControlType::Cpad
|
|
||||||
#define UI_SLIDER ControlType::Slider
|
|
||||||
#define UI_NUMBER ControlType::Number
|
|
||||||
#define UI_TEXT_INPUT ControlType::Text
|
|
||||||
#define UI_GRAPH ControlType::Graph
|
|
||||||
#define UI_ADD_GRAPH_POINT ControlType::GraphPoint
|
|
||||||
|
|
||||||
#define UPDATE_LABEL ControlType::UpdateLabel
|
|
||||||
#define UPDATE_SWITCHER ControlType::UpdateSwitcher
|
|
||||||
#define UPDATE_SLIDER ControlType::UpdateSlider
|
|
||||||
#define UPDATE_NUMBER ControlType::UpdateNumber
|
|
||||||
#define UPDATE_TEXT_INPUT ControlType::UpdateText
|
|
||||||
#define CLEAR_GRAPH ControlType::ClearGraph
|
|
||||||
|
|
||||||
// Colors
|
|
||||||
enum ControlColor : uint8_t
|
|
||||||
{
|
|
||||||
Turquoise,
|
|
||||||
Emerald,
|
|
||||||
Peterriver,
|
|
||||||
Wetasphalt,
|
|
||||||
Sunflower,
|
|
||||||
Carrot,
|
|
||||||
Alizarin,
|
|
||||||
Dark,
|
|
||||||
None = 0xFF
|
|
||||||
};
|
|
||||||
#define COLOR_TURQUOISE ControlColor::Turquoise
|
|
||||||
#define COLOR_EMERALD ControlColor::Emerald
|
|
||||||
#define COLOR_PETERRIVER ControlColor::Peterriver
|
|
||||||
#define COLOR_WETASPHALT ControlColor::Wetasphalt
|
|
||||||
#define COLOR_SUNFLOWER ControlColor::Sunflower
|
|
||||||
#define COLOR_CARROT ControlColor::Carrot
|
|
||||||
#define COLOR_ALIZARIN ControlColor::Alizarin
|
|
||||||
#define COLOR_DARK ControlColor::Dark
|
|
||||||
#define COLOR_NONE ControlColor::None
|
|
||||||
|
|
||||||
class Control
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ControlType type;
|
|
||||||
uint16_t id; // just mirroring the id here for practical reasons
|
|
||||||
const char* label;
|
|
||||||
void (*callback)(Control*, int);
|
|
||||||
String value;
|
|
||||||
ControlColor color;
|
|
||||||
bool visible;
|
|
||||||
bool wide;
|
|
||||||
bool vertical;
|
|
||||||
bool enabled;
|
|
||||||
uint16_t parentControl;
|
|
||||||
String panelStyle;
|
|
||||||
String elementStyle;
|
|
||||||
String inputType;
|
|
||||||
Control* next;
|
|
||||||
|
|
||||||
static constexpr uint16_t noParent = 0xffff;
|
|
||||||
|
|
||||||
Control(ControlType type, const char* label, void (*callback)(Control*, int), const String& value,
|
|
||||||
ControlColor color, bool visible = true, uint16_t parentControl = Control::noParent)
|
|
||||||
: type(type),
|
|
||||||
label(label),
|
|
||||||
callback(callback),
|
|
||||||
value(value),
|
|
||||||
color(color),
|
|
||||||
visible(visible),
|
|
||||||
wide(false),
|
|
||||||
vertical(false),
|
|
||||||
enabled(true),
|
|
||||||
parentControl(parentControl),
|
|
||||||
next(nullptr)
|
|
||||||
{
|
|
||||||
id = idCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Control(const Control& control)
|
|
||||||
: type(control.type),
|
|
||||||
id(control.id),
|
|
||||||
label(control.label),
|
|
||||||
callback(control.callback),
|
|
||||||
value(control.value),
|
|
||||||
color(control.color),
|
|
||||||
visible(control.visible),
|
|
||||||
parentControl(control.parentControl),
|
|
||||||
next(control.next)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
private:
|
|
||||||
static uint16_t idCounter;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
#define B_DOWN -1
|
#define B_DOWN -1
|
||||||
@ -220,14 +102,22 @@ class ESPUIClass
|
|||||||
public:
|
public:
|
||||||
ESPUIClass()
|
ESPUIClass()
|
||||||
{
|
{
|
||||||
verbosity = Verbosity::Quiet;
|
#ifdef ESP32
|
||||||
jsonUpdateDocumentSize = 2000;
|
ControlsSemaphore = xSemaphoreCreateMutex();
|
||||||
jsonInitialDocumentSize = 8000;
|
xSemaphoreGive(ControlsSemaphore);
|
||||||
sliderContinuous = false;
|
#endif // def ESP32
|
||||||
}
|
}
|
||||||
unsigned int jsonUpdateDocumentSize;
|
unsigned int jsonUpdateDocumentSize = 2000;
|
||||||
unsigned int jsonInitialDocumentSize;
|
#ifdef ESP8266
|
||||||
bool sliderContinuous;
|
unsigned int jsonInitialDocumentSize = 2000;
|
||||||
|
unsigned int jsonChunkNumberMax = 5;
|
||||||
|
#else
|
||||||
|
unsigned int jsonInitialDocumentSize = 8000;
|
||||||
|
unsigned int jsonChunkNumberMax = 0;
|
||||||
|
#endif
|
||||||
|
bool sliderContinuous = false;
|
||||||
|
void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||||
|
bool captivePortal = true;
|
||||||
|
|
||||||
void setVerbosity(Verbosity verbosity);
|
void setVerbosity(Verbosity verbosity);
|
||||||
void begin(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
void begin(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||||
@ -237,31 +127,28 @@ public:
|
|||||||
void beginLITTLEFS(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
void beginLITTLEFS(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||||
uint16_t port = 80); // Setup server and page in LITTLEFS mode
|
uint16_t port = 80); // Setup server and page in LITTLEFS mode
|
||||||
|
|
||||||
void prepareFileSystem(); // Initially preps the filesystem and loads a lot of
|
void prepareFileSystem(bool format = true); // Initially preps the filesystem and loads a lot of
|
||||||
// stuff into LITTLEFS
|
// stuff into LITTLEFS
|
||||||
void list(); // Lists LITTLEFS directory
|
void list(); // Lists LITTLEFS directory
|
||||||
|
void writeFile(const char* path, const char* data);
|
||||||
|
|
||||||
uint16_t addControl(ControlType type, const char* label, const String& value = String(""),
|
uint16_t addControl(ControlType type, const char* label);
|
||||||
ControlColor color = ControlColor::Turquoise, uint16_t parentControl = Control::noParent,
|
uint16_t addControl(ControlType type, const char* label, const String& value);
|
||||||
void (*callback)(Control*, int) = nullptr);
|
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color);
|
||||||
bool removeControl(uint16_t id, bool force_reload_ui = false);
|
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl);
|
||||||
|
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, std::function<void(Control*, int)> callback);
|
||||||
|
|
||||||
|
bool removeControl(uint16_t id, bool force_rebuild_ui = false);
|
||||||
|
|
||||||
// create Elements
|
// create Elements
|
||||||
uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color,
|
// Create Event Button
|
||||||
const String& value = ""); // Create Event Button
|
uint16_t button(const char* label, std::function<void(Control*, int)> callback, ControlColor color, const String& value = "");
|
||||||
uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color,
|
uint16_t switcher(const char* label, std::function<void(Control*, int)> callback, ControlColor color, bool startState = false); // Create Toggle Button
|
||||||
bool startState = false); // Create Toggle Button
|
uint16_t pad(const char* label, std::function<void(Control*, int)> callback, ControlColor color); // Create Pad Control
|
||||||
uint16_t pad(const char* label, void (*callback)(Control*, int),
|
uint16_t padWithCenter(const char* label, std::function<void(Control*, int)> callback, ControlColor color); // Create Pad Control with Centerbutton
|
||||||
ControlColor color); // Create Pad Control
|
uint16_t slider(const char* label, std::function<void(Control*, int)> callback, ControlColor color, int value, int min = 0, int max = 100); // Create Slider Control
|
||||||
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int),
|
uint16_t number(const char* label, std::function<void(Control*, int)> callback, ControlColor color, int value, int min = 0, int max = 100); // Create a Number Input Control
|
||||||
ControlColor color); // Create Pad Control with Centerbutton
|
uint16_t text(const char* label, std::function<void(Control*, int)> callback, ControlColor color, const String& value = ""); // Create a Text Input Control
|
||||||
|
|
||||||
uint16_t slider(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0,
|
|
||||||
int max = 100); // Create Slider Control
|
|
||||||
uint16_t number(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0,
|
|
||||||
int max = 100); // Create a Number Input Control
|
|
||||||
uint16_t text(const char* label, void (*callback)(Control*, int), ControlColor color,
|
|
||||||
const String& value = ""); // Create a Text Input Control
|
|
||||||
|
|
||||||
// Output only
|
// Output only
|
||||||
uint16_t label(const char* label, ControlColor color,
|
uint16_t label(const char* label, ControlColor color,
|
||||||
@ -270,18 +157,23 @@ public:
|
|||||||
uint16_t gauge(const char* label, ControlColor color, int value, int min = 0,
|
uint16_t gauge(const char* label, ControlColor color, int value, int min = 0,
|
||||||
int max = 100); // Create Gauge display
|
int max = 100); // Create Gauge display
|
||||||
uint16_t separator(const char* label); //Create separator
|
uint16_t separator(const char* label); //Create separator
|
||||||
|
uint16_t fileDisplay(const char* label, ControlColor color, String filename);
|
||||||
|
|
||||||
// Input only
|
// Input only
|
||||||
uint16_t accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color);
|
uint16_t accelerometer(const char* label, std::function<void(Control*, int)> callback, ControlColor color);
|
||||||
|
|
||||||
// Update Elements
|
// Update Elements
|
||||||
|
|
||||||
Control* getControl(uint16_t id);
|
Control* getControl(uint16_t id);
|
||||||
|
Control* getControlNoLock(uint16_t id);
|
||||||
|
|
||||||
// Update Elements
|
// Update Elements
|
||||||
void updateControlValue(uint16_t id, const String& value, int clientId = -1);
|
void updateControlValue(uint16_t id, const String& value, int clientId = -1);
|
||||||
void updateControlValue(Control* control, const String& value, int clientId = -1);
|
void updateControlValue(Control* control, const String& value, int clientId = -1);
|
||||||
|
|
||||||
|
void updateControlLabel(uint16_t control, const char * value, int clientId = -1);
|
||||||
|
void updateControlLabel(Control* control, const char * value, int clientId = -1);
|
||||||
|
|
||||||
void updateControl(uint16_t id, int clientId = -1);
|
void updateControl(uint16_t id, int clientId = -1);
|
||||||
void updateControl(Control* control, int clientId = -1);
|
void updateControl(Control* control, int clientId = -1);
|
||||||
|
|
||||||
@ -299,9 +191,9 @@ public:
|
|||||||
void clearGraph(uint16_t id, int clientId = -1);
|
void clearGraph(uint16_t id, int clientId = -1);
|
||||||
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, String style, int clientId = -1);
|
void setPanelStyle(uint16_t id, const String& style, int clientId = -1);
|
||||||
void setElementStyle(uint16_t id, String style, int clientId = -1);
|
void setElementStyle(uint16_t id, const String& style, int clientId = -1);
|
||||||
void setInputType(uint16_t id, String type, int clientId = -1);
|
void setInputType(uint16_t id, const String& type, int clientId = -1);
|
||||||
|
|
||||||
void setPanelWide(uint16_t id, bool wide);
|
void setPanelWide(uint16_t id, bool wide);
|
||||||
void setVertical(uint16_t id, bool vert = true);
|
void setVertical(uint16_t id, bool vert = true);
|
||||||
@ -313,22 +205,90 @@ public:
|
|||||||
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;
|
||||||
void jsonReload();
|
void jsonReload();
|
||||||
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr);
|
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false);
|
||||||
|
|
||||||
Verbosity verbosity;
|
Verbosity verbosity = Verbosity::Quiet;
|
||||||
|
uint32_t GetNextControlChangeId();
|
||||||
|
// emulate former extended callback API by using an intermediate lambda (no deprecation)
|
||||||
|
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, std::function<void(Control*, int, void*)> callback, void* userData)
|
||||||
|
{
|
||||||
|
return addControl(type, label, value, color, parentControl, [callback, userData](Control* sender, int type){ callback(sender, type, userData); });
|
||||||
|
}
|
||||||
|
uint16_t button(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, const String& value, void* userData)
|
||||||
|
{
|
||||||
|
return button(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, value);
|
||||||
|
}
|
||||||
|
uint16_t switcher(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, bool startState, void* userData)
|
||||||
|
{
|
||||||
|
return switcher(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, startState);
|
||||||
|
}
|
||||||
|
uint16_t pad(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, void* userData)
|
||||||
|
{
|
||||||
|
return pad(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color);
|
||||||
|
}
|
||||||
|
uint16_t padWithCenter(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, void* userData)
|
||||||
|
{
|
||||||
|
return padWithCenter(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color);
|
||||||
|
}
|
||||||
|
uint16_t slider(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, int value, int min, int max, void* userData)
|
||||||
|
{
|
||||||
|
return slider(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, value, min, max);
|
||||||
|
}
|
||||||
|
uint16_t number(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, int value, int min, int max, void* userData)
|
||||||
|
{
|
||||||
|
return number(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, value, min, max);
|
||||||
|
}
|
||||||
|
uint16_t text(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, const String& value, void* userData)
|
||||||
|
{
|
||||||
|
return text(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); } , color, value);
|
||||||
|
}
|
||||||
|
uint16_t accelerometer(const char* label, std::function<void(Control*, int, void*)> callback, ControlColor color, void* userData)
|
||||||
|
{
|
||||||
|
return accelerometer(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServer* WebServer() {return server;}
|
||||||
|
AsyncWebSocket* WebSocket() {return ws;}
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
# if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR >= 4) || ESP_IDF_VERSION_MAJOR > 4
|
||||||
|
fs::LittleFSFS & EspuiLittleFS = LittleFS;
|
||||||
|
#else
|
||||||
|
fs::LITTLEFSFS & EspuiLittleFS = LITTLEFS;
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
fs::FS & EspuiLittleFS = LittleFS;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class ESPUIclient;
|
||||||
|
friend class ESPUIcontrol;
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
SemaphoreHandle_t ControlsSemaphore = NULL;
|
||||||
|
#endif // def ESP32
|
||||||
|
|
||||||
|
void RemoveToBeDeletedControls();
|
||||||
|
|
||||||
AsyncWebServer* server;
|
AsyncWebServer* server;
|
||||||
AsyncWebSocket* ws;
|
AsyncWebSocket* ws;
|
||||||
|
|
||||||
private:
|
|
||||||
const char* basicAuthUsername = nullptr;
|
const char* basicAuthUsername = nullptr;
|
||||||
const char* basicAuthPassword = nullptr;
|
const char* basicAuthPassword = nullptr;
|
||||||
bool basicAuth = true;
|
bool basicAuth = true;
|
||||||
|
|
||||||
uint16_t controlCount = 0;
|
uint16_t controlCount = 0;
|
||||||
|
|
||||||
void prepareJSONChunk(AsyncWebSocketClient* client, uint16_t startindex, JsonArray* items);
|
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, Control* control);
|
||||||
|
|
||||||
|
#define ClientUpdateType_t ESPUIclient::ClientUpdateType_t
|
||||||
|
void NotifyClients(ClientUpdateType_t newState);
|
||||||
|
void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState);
|
||||||
|
|
||||||
|
bool SendJsonDocToWebSocket(ArduinoJson::JsonDocument& document, uint16_t clientId);
|
||||||
|
|
||||||
|
std::map<uint32_t, ESPUIclient*> MapOfClients;
|
||||||
|
|
||||||
|
uint32_t ControlChangeID = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ESPUIClass ESPUI;
|
extern ESPUIClass ESPUI;
|
||||||
#endif
|
|
||||||
|
600
src/ESPUIclient.cpp
Normal file
600
src/ESPUIclient.cpp
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
#include "ESPUI.h"
|
||||||
|
#include "ESPUIclient.h"
|
||||||
|
#include "ESPUIcontrol.h"
|
||||||
|
|
||||||
|
// JSONSlave:
|
||||||
|
// helper to process exact JSON serialization size
|
||||||
|
// it takes ~2ms on esp8266 and avoid large String reallocation which is really worth the cost
|
||||||
|
class JSONSlave: public Print
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
size_t write (uint8_t c) override { counter++; return 1; }
|
||||||
|
size_t write (const uint8_t* buf, size_t count) override { counter += count; return count; }
|
||||||
|
size_t get_counter () { return counter; }
|
||||||
|
|
||||||
|
static size_t serializedSize (JsonDocument& doc)
|
||||||
|
{
|
||||||
|
JSONSlave counter;
|
||||||
|
serializeJson(doc, counter);
|
||||||
|
return counter.get_counter();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t serialize (JsonDocument& doc, String& str)
|
||||||
|
{
|
||||||
|
size_t s = serializedSize(doc) + 10; // 10 is paranoid
|
||||||
|
str.reserve(s);
|
||||||
|
serializeJson(doc, str);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toString (JsonDocument& doc)
|
||||||
|
{
|
||||||
|
String str;
|
||||||
|
serialize(doc, str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t counter = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ESPUIclient::ESPUIclient(AsyncWebSocketClient * _client):
|
||||||
|
client(_client)
|
||||||
|
{
|
||||||
|
fsm_EspuiClient_state_Idle_imp.SetParent(this);
|
||||||
|
fsm_EspuiClient_state_SendingUpdate_imp.SetParent(this);
|
||||||
|
fsm_EspuiClient_state_Rebuilding_imp.SetParent(this);
|
||||||
|
fsm_EspuiClient_state_Reloading_imp.SetParent(this);
|
||||||
|
|
||||||
|
fsm_EspuiClient_state_Idle_imp.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPUIclient::ESPUIclient(const ESPUIclient& source):
|
||||||
|
client(source.client)
|
||||||
|
{
|
||||||
|
fsm_EspuiClient_state_Idle_imp.SetParent(this);
|
||||||
|
fsm_EspuiClient_state_SendingUpdate_imp.SetParent(this);
|
||||||
|
fsm_EspuiClient_state_Rebuilding_imp.SetParent(this);
|
||||||
|
fsm_EspuiClient_state_Reloading_imp.SetParent(this);
|
||||||
|
|
||||||
|
fsm_EspuiClient_state_Idle_imp.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPUIclient::~ESPUIclient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUIclient::CanSend()
|
||||||
|
{
|
||||||
|
bool Response = false;
|
||||||
|
if (nullptr != client)
|
||||||
|
{
|
||||||
|
Response = client->canSend();
|
||||||
|
}
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPUIclient::FillInHeader(JsonDocument& document)
|
||||||
|
{
|
||||||
|
document[F("type")] = UI_EXTEND_GUI;
|
||||||
|
document[F("sliderContinuous")] = ESPUI.sliderContinuous;
|
||||||
|
document[F("startindex")] = 0;
|
||||||
|
document[F("totalcontrols")] = ESPUI.controlCount;
|
||||||
|
JsonArray items = AllocateJsonArray(document, F("controls"));
|
||||||
|
JsonObject titleItem = AllocateJsonObject(items);
|
||||||
|
titleItem[F("type")] = (int)UI_TITLE;
|
||||||
|
titleItem[F("label")] = ESPUI.ui_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUIclient::IsSyncronized()
|
||||||
|
{
|
||||||
|
return ((ClientUpdateType_t::Synchronized == ClientUpdateType) &&
|
||||||
|
(&fsm_EspuiClient_state_Idle_imp == pCurrentFsmState));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUIclient::SendClientNotification(ClientUpdateType_t value)
|
||||||
|
{
|
||||||
|
bool Response = false;
|
||||||
|
|
||||||
|
do // once
|
||||||
|
{
|
||||||
|
if(!CanSend())
|
||||||
|
{
|
||||||
|
// Serial.println(F("ESPUIclient::SendClientNotification:CannotSend"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocateJsonDocument(document, ESPUI.jsonUpdateDocumentSize);
|
||||||
|
FillInHeader(document);
|
||||||
|
if(ClientUpdateType_t::ReloadNeeded == value)
|
||||||
|
{
|
||||||
|
// Serial.println(F("ESPUIclient::SendClientNotification:set type to reload"));
|
||||||
|
document["type"] = int(UI_RELOAD);
|
||||||
|
}
|
||||||
|
// dont send any controls
|
||||||
|
|
||||||
|
Response = SendJsonDocToWebSocket(document);
|
||||||
|
// Serial.println(String("ESPUIclient::SendClientNotification:NotificationSent:Response: ") + String(Response));
|
||||||
|
|
||||||
|
} while (false);
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPUIclient::NotifyClient(ClientUpdateType_t newState)
|
||||||
|
{
|
||||||
|
SetState(newState);
|
||||||
|
pCurrentFsmState->NotifyClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Websockets Communication
|
||||||
|
bool ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||||
|
{
|
||||||
|
bool Response = false;
|
||||||
|
// Serial.println(String("ESPUIclient::OnWsEvent: type: ") + String(type));
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case WS_EVT_PONG:
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity)
|
||||||
|
{
|
||||||
|
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_PONG"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_EVT_ERROR:
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity)
|
||||||
|
{
|
||||||
|
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_ERROR"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_EVT_CONNECT:
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity)
|
||||||
|
{
|
||||||
|
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_CONNECT"));
|
||||||
|
Serial.println(client->id());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Serial.println("ESPUIclient:onWsEvent:WS_EVT_CONNECT: Call NotifyClient: RebuildNeeded");
|
||||||
|
NotifyClient(ClientUpdateType_t::RebuildNeeded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_EVT_DATA:
|
||||||
|
{
|
||||||
|
// Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_DATA"));
|
||||||
|
String msg = "";
|
||||||
|
msg.reserve(len + 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
msg += (char)data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
String cmd = msg.substring(0, msg.indexOf(":"));
|
||||||
|
String value = msg.substring(cmd.length() + 1, msg.lastIndexOf(':'));
|
||||||
|
uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
|
||||||
|
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||||
|
{
|
||||||
|
Serial.println(String(F(" WS msg: ")) + msg);
|
||||||
|
Serial.println(String(F(" WS cmd: ")) + cmd);
|
||||||
|
Serial.println(String(F(" WS id: ")) + String(id));
|
||||||
|
Serial.println(String(F("WS value: ")) + String(value));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (cmd.equals(F("uiok")))
|
||||||
|
{
|
||||||
|
|
||||||
|
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck:")) + pCurrentFsmState->GetStateName());
|
||||||
|
pCurrentFsmState->ProcessAck(id, emptyString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.equals(F("uifragmentok")))
|
||||||
|
{
|
||||||
|
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck");
|
||||||
|
if(!emptyString.equals(value))
|
||||||
|
{
|
||||||
|
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck:value:'" + value + "'");
|
||||||
|
pCurrentFsmState->ProcessAck(uint16_t(-1), value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.println(F("ERROR:ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:Fragment Header is missing"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.equals(F("uiuok")))
|
||||||
|
{
|
||||||
|
// Serial.println(F("WS_EVT_DATA: uiuok. Unlock new async notifications"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial.println(F("WS_EVT_DATA:Process Control"));
|
||||||
|
Control* control = ESPUI.getControl(id);
|
||||||
|
if (nullptr == control)
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity)
|
||||||
|
{
|
||||||
|
Serial.println(String(F("No control found for ID ")) + String(id));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
control->onWsEvent(cmd, value);
|
||||||
|
// notify other clients of change
|
||||||
|
Response = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// Serial.println(F("ESPUIclient::OnWsEvent:default"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // end switch
|
||||||
|
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prepare a chunk of elements as a single JSON string. If the allowed number of elements is greater than the total
|
||||||
|
number this will represent the entire UI. More likely, it will represent a small section of the UI to be sent. The
|
||||||
|
client will acknowledge receipt by requesting the next chunk.
|
||||||
|
*/
|
||||||
|
uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||||
|
JsonDocument & rootDoc,
|
||||||
|
bool InUpdateMode,
|
||||||
|
String FragmentRequestString)
|
||||||
|
{
|
||||||
|
#ifdef ESP32
|
||||||
|
xSemaphoreTake(ESPUI.ControlsSemaphore, portMAX_DELAY);
|
||||||
|
#endif // def ESP32
|
||||||
|
|
||||||
|
// Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode));
|
||||||
|
// Serial.println(String("prepareJSONChunk: Start. startindex: ") + String(startindex));
|
||||||
|
// Serial.println(String("prepareJSONChunk: Start. FragmentRequestString: '") + FragmentRequestString + "'");
|
||||||
|
int elementcount = 0;
|
||||||
|
uint32_t MaxMarshaledJsonSize = (!InUpdateMode) ? ESPUI.jsonInitialDocumentSize: ESPUI.jsonUpdateDocumentSize;
|
||||||
|
uint32_t EstimatedUsedMarshaledJsonSize = 0;
|
||||||
|
|
||||||
|
do // once
|
||||||
|
{
|
||||||
|
// Follow the list until control points to the startindex'th node
|
||||||
|
Control* control = ESPUI.controls;
|
||||||
|
uint32_t currentIndex = 0;
|
||||||
|
uint32_t DataOffset = 0;
|
||||||
|
JsonArray items = rootDoc[F("controls")];
|
||||||
|
bool SingleControl = false;
|
||||||
|
|
||||||
|
if(!emptyString.equals(FragmentRequestString))
|
||||||
|
{
|
||||||
|
// Serial.println(F("prepareJSONChunk:Fragmentation:Got Header (1)"));
|
||||||
|
// Serial.println(String("prepareJSONChunk:startindex: ") + String(startindex));
|
||||||
|
// Serial.println(String("prepareJSONChunk:currentIndex: ") + String(currentIndex));
|
||||||
|
// Serial.println(String("prepareJSONChunk:FragmentRequestString: '") + FragmentRequestString + "'");
|
||||||
|
|
||||||
|
// this is actually a fragment or directed update request
|
||||||
|
// parse the string we got from the UI and try to update that specific
|
||||||
|
// control.
|
||||||
|
AllocateJsonDocument(FragmentRequest, FragmentRequestString.length() * 3);
|
||||||
|
/*
|
||||||
|
ArduinoJson::detail::sizeofObject(N);
|
||||||
|
if(0 >= FragmentRequest.capacity())
|
||||||
|
{
|
||||||
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Could not allocate memory for a fragmentation request. Skipping Response"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
size_t FragmentRequestStartOffset = FragmentRequestString.indexOf("{");
|
||||||
|
DeserializationError error = deserializeJson(FragmentRequest, FragmentRequestString.substring(FragmentRequestStartOffset));
|
||||||
|
if(DeserializationError::Ok != error)
|
||||||
|
{
|
||||||
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Could not extract json from the fragment request"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!FragmentRequest["id"].is<JsonVariant>())
|
||||||
|
{
|
||||||
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a control ID"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint16_t ControlId = uint16_t(FragmentRequest[F("id")]);
|
||||||
|
|
||||||
|
if(!FragmentRequest["offset"].is<JsonVariant>())
|
||||||
|
{
|
||||||
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a starting offset"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DataOffset = uint16_t(FragmentRequest[F("offset")]);
|
||||||
|
control = ESPUI.getControlNoLock(ControlId);
|
||||||
|
if(nullptr == control)
|
||||||
|
{
|
||||||
|
Serial.println(String(F("ERROR:prepareJSONChunk:Fragmentation:Requested control: ")) + String(ControlId) + F(" does not exist"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial.println(F("prepareJSONChunk:Fragmentation:disable the control search operation"));
|
||||||
|
currentIndex = 1;
|
||||||
|
startindex = 0;
|
||||||
|
SingleControl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a control to send
|
||||||
|
while ((startindex > currentIndex) && (nullptr != control))
|
||||||
|
{
|
||||||
|
// only count active controls
|
||||||
|
if (!control->ToBeDeleted())
|
||||||
|
{
|
||||||
|
if(InUpdateMode)
|
||||||
|
{
|
||||||
|
// In update mode we only count the controls that have been updated.
|
||||||
|
if(control->NeedsSync(CurrentSyncID))
|
||||||
|
{
|
||||||
|
++currentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not in update mode. Count all active controls
|
||||||
|
++currentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
control = control->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// any controls left to be processed?
|
||||||
|
if(nullptr == control)
|
||||||
|
{
|
||||||
|
// Serial.println("prepareJSONChunk: No controls to process");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep track of the number of elements we have serialised into this
|
||||||
|
// message. Overflow is detected and handled later in this loop
|
||||||
|
// and needs an index to the last item added.
|
||||||
|
while (nullptr != control)
|
||||||
|
{
|
||||||
|
// skip deleted controls or controls that have not been updated
|
||||||
|
if (control->ToBeDeleted() && !SingleControl)
|
||||||
|
{
|
||||||
|
// Serial.println(String("prepareJSONChunk: Ignoring Deleted control: ") + String(control->id));
|
||||||
|
control = control->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(InUpdateMode && !SingleControl)
|
||||||
|
{
|
||||||
|
if(control->NeedsSync(CurrentSyncID))
|
||||||
|
{
|
||||||
|
// dont skip this control
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// control has not been updated. Skip it
|
||||||
|
control = control->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: MaxMarshaledJsonSize: ")) + String(MaxMarshaledJsonSize));
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: Cur EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
|
||||||
|
|
||||||
|
JsonObject item = AllocateJsonObject(items);
|
||||||
|
elementcount++;
|
||||||
|
uint32_t RemainingSpace = (MaxMarshaledJsonSize - EstimatedUsedMarshaledJsonSize) - 100;
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: RemainingSpace: ")) + String(RemainingSpace));
|
||||||
|
uint32_t SpaceUsedByMarshaledControl = 0;
|
||||||
|
bool ControlIsFragmented = control->MarshalControl(item,
|
||||||
|
InUpdateMode,
|
||||||
|
DataOffset,
|
||||||
|
RemainingSpace,
|
||||||
|
SpaceUsedByMarshaledControl);
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: SpaceUsedByMarshaledControl: ")) + String(SpaceUsedByMarshaledControl));
|
||||||
|
EstimatedUsedMarshaledJsonSize += SpaceUsedByMarshaledControl;
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: New EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: ControlIsFragmented: ")) + String(ControlIsFragmented));
|
||||||
|
|
||||||
|
// did the control get added to the doc?
|
||||||
|
if (0 == SpaceUsedByMarshaledControl ||
|
||||||
|
(ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0))
|
||||||
|
{
|
||||||
|
// Serial.println( String("prepareJSONChunk: too much data in the message. Remove the last entry"));
|
||||||
|
if (1 == elementcount)
|
||||||
|
{
|
||||||
|
// Serial.println(String(F("prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser."));
|
||||||
|
// Serial.println(String(F("ERROR: prepareJSONChunk: value: ")) + control->value);
|
||||||
|
rootDoc.clear();
|
||||||
|
item = AllocateJsonObject(items);
|
||||||
|
control->MarshalErrorMessage(item);
|
||||||
|
elementcount = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Serial.println(String("prepareJSONChunk: Defering control: ") + String(control->id));
|
||||||
|
// Serial.println(String("prepareJSONChunk: elementcount: ") + String(elementcount));
|
||||||
|
|
||||||
|
items.remove(elementcount);
|
||||||
|
--elementcount;
|
||||||
|
}
|
||||||
|
// exit the loop
|
||||||
|
control = nullptr;
|
||||||
|
}
|
||||||
|
else if ((SingleControl) ||
|
||||||
|
(ControlIsFragmented) ||
|
||||||
|
(MaxMarshaledJsonSize < (EstimatedUsedMarshaledJsonSize + 100)))
|
||||||
|
{
|
||||||
|
// Serial.println("prepareJSONChunk: Doc is Full, Fragmented Control or Single Control. exit loop");
|
||||||
|
control = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Serial.println("prepareJSONChunk: Next Control");
|
||||||
|
control = control->next;
|
||||||
|
}
|
||||||
|
} // end while (control != nullptr)
|
||||||
|
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
xSemaphoreGive(ESPUI.ControlsSemaphore);
|
||||||
|
#endif // def ESP32
|
||||||
|
|
||||||
|
// Serial.println(String("prepareJSONChunk: END: elementcount: ") + String(elementcount));
|
||||||
|
return elementcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert & Transfer Arduino elements to JSON elements. This function sends a chunk of
|
||||||
|
JSON describing the controls of the UI, starting from the control at index startidx.
|
||||||
|
If startidx is 0 then a UI_INITIAL_GUI message will be sent, else a UI_EXTEND_GUI.
|
||||||
|
Both message types contain a list of serialised UI elements. Only a portion of the UI
|
||||||
|
will be sent in order to avoid websocket buffer overflows. The client will acknowledge
|
||||||
|
receipt of a partial message by requesting the next chunk of UI.
|
||||||
|
|
||||||
|
The protocol is:
|
||||||
|
SERVER: SendControlsToClient(0):
|
||||||
|
"UI_INITIAL_GUI: n serialised UI elements"
|
||||||
|
CLIENT: controls.js:handleEvent()
|
||||||
|
"uiok:n"
|
||||||
|
SERVER: SendControlsToClient(n):
|
||||||
|
"UI_EXTEND_GUI: n serialised UI elements"
|
||||||
|
CLIENT: controls.js:handleEvent()
|
||||||
|
"uiok:2*n"
|
||||||
|
etc.
|
||||||
|
Returns true if all controls have been sent (aka: Done)
|
||||||
|
*/
|
||||||
|
bool ESPUIclient::SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode, String FragmentRequest)
|
||||||
|
{
|
||||||
|
bool Response = false;
|
||||||
|
// Serial.println(String("ESPUIclient:SendControlsToClient:startidx: ") + String(startidx));
|
||||||
|
do // once
|
||||||
|
{
|
||||||
|
if(!CanSend())
|
||||||
|
{
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: Cannot Send to clients.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((startidx >= ESPUI.controlCount) && (emptyString.equals(FragmentRequest)))
|
||||||
|
{
|
||||||
|
// Serial.println(F("ERROR:ESPUIclient:SendControlsToClient: No more controls to send."));
|
||||||
|
Response = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocateJsonDocument(document, ESPUI.jsonInitialDocumentSize);
|
||||||
|
FillInHeader(document);
|
||||||
|
document[F("startindex")] = startidx;
|
||||||
|
document[F("totalcontrols")] = uint16_t(-1); // ESPUI.controlCount;
|
||||||
|
|
||||||
|
if(0 == startidx)
|
||||||
|
{
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: Tell client we are starting a transfer of controls.");
|
||||||
|
document["type"] = (ClientUpdateType_t::RebuildNeeded == TransferMode) ? UI_INITIAL_GUI : UI_EXTEND_GUI;
|
||||||
|
CurrentSyncID = NextSyncID;
|
||||||
|
NextSyncID = ESPUI.GetNextControlChangeId();
|
||||||
|
}
|
||||||
|
// Serial.println(String("ESPUIclient:SendControlsToClient:type: ") + String((uint32_t)document["type"]));
|
||||||
|
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: Build Controls.");
|
||||||
|
if(prepareJSONChunk(startidx, document, ClientUpdateType_t::UpdateNeeded == TransferMode, FragmentRequest))
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||||
|
{
|
||||||
|
Serial.println(F("ESPUIclient:SendControlsToClient: Sending elements --------->"));
|
||||||
|
serializeJson(document, Serial);
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: Send message.");
|
||||||
|
if(true == SendJsonDocToWebSocket(document))
|
||||||
|
{
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: Sent.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: Send failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Serial.println("ESPUIclient:SendControlsToClient: No elements to send.");
|
||||||
|
Response = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
// Serial.println(String("ESPUIclient:SendControlsToClient:Response: ") + String(Response));
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUIclient::SendJsonDocToWebSocket(JsonDocument& document)
|
||||||
|
{
|
||||||
|
bool Response = true;
|
||||||
|
|
||||||
|
do // once
|
||||||
|
{
|
||||||
|
if (!CanSend())
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||||
|
{
|
||||||
|
Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: Cannot Send to client. Not sending websocket message"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Serial.println("ESPUIclient::SendJsonDocToWebSocket: Cannot Send to client. Not sending websocket message");
|
||||||
|
Response = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = JSONSlave::toString(document);
|
||||||
|
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||||
|
{
|
||||||
|
Serial.println(String(F("ESPUIclient::SendJsonDocToWebSocket: json: '")) + json + "'");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||||
|
{
|
||||||
|
Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: client.text"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: client.text"));
|
||||||
|
client->text(json);
|
||||||
|
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPUIclient::SetState(ClientUpdateType_t value)
|
||||||
|
{
|
||||||
|
// only a higher priority state request can replace the current state request
|
||||||
|
if(uint32_t(ClientUpdateType) < uint32_t(value))
|
||||||
|
{
|
||||||
|
ClientUpdateType = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
67
src/ESPUIclient.h
Normal file
67
src/ESPUIclient.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "ESPUIclientFsm.h"
|
||||||
|
#include "ESPUIcontrol.h"
|
||||||
|
|
||||||
|
class ESPUIclient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum ClientUpdateType_t
|
||||||
|
{ // this is an orderd list. highest number is highest priority
|
||||||
|
Synchronized = 0,
|
||||||
|
UpdateNeeded = 1,
|
||||||
|
RebuildNeeded = 2,
|
||||||
|
ReloadNeeded = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// bool HasBeenNotified = false; // Set when a notification has been sent and we are waiting for a reply
|
||||||
|
// bool DelayedNotification = false; // set if a delayed notification is needed
|
||||||
|
|
||||||
|
ClientUpdateType_t ClientUpdateType = ClientUpdateType_t::RebuildNeeded;
|
||||||
|
|
||||||
|
AsyncWebSocketClient * client = nullptr;
|
||||||
|
|
||||||
|
friend class fsm_EspuiClient_state_Idle;
|
||||||
|
friend class fsm_EspuiClient_state_SendingUpdate;
|
||||||
|
friend class fsm_EspuiClient_state_Rebuilding;
|
||||||
|
friend class fsm_EspuiClient_state_WaitForAck;
|
||||||
|
friend class fsm_EspuiClient_state_Reloading;
|
||||||
|
friend class fsm_EspuiClient_state;
|
||||||
|
|
||||||
|
fsm_EspuiClient_state_Idle fsm_EspuiClient_state_Idle_imp;
|
||||||
|
fsm_EspuiClient_state_SendingUpdate fsm_EspuiClient_state_SendingUpdate_imp;
|
||||||
|
fsm_EspuiClient_state_Rebuilding fsm_EspuiClient_state_Rebuilding_imp;
|
||||||
|
fsm_EspuiClient_state_Reloading fsm_EspuiClient_state_Reloading_imp;
|
||||||
|
fsm_EspuiClient_state* pCurrentFsmState = &fsm_EspuiClient_state_Idle_imp;
|
||||||
|
|
||||||
|
time_t EspuiClientEndTime = 0;
|
||||||
|
|
||||||
|
// bool NeedsNotification() { return pCurrentFsmState != &fsm_EspuiClient_state_Idle_imp; }
|
||||||
|
|
||||||
|
bool CanSend();
|
||||||
|
void FillInHeader(ArduinoJson::JsonDocument& document);
|
||||||
|
uint32_t prepareJSONChunk(uint16_t startindex, JsonDocument& rootDoc, bool InUpdateMode, String value);
|
||||||
|
bool SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode, String FragmentRequest);
|
||||||
|
|
||||||
|
bool SendClientNotification(ClientUpdateType_t value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t CurrentSyncID = 0;
|
||||||
|
uint32_t NextSyncID = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ESPUIclient(AsyncWebSocketClient * _client);
|
||||||
|
ESPUIclient(const ESPUIclient & source);
|
||||||
|
virtual ~ESPUIclient();
|
||||||
|
void NotifyClient(ClientUpdateType_t value);
|
||||||
|
bool onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||||
|
bool IsSyncronized();
|
||||||
|
uint32_t id() { return client->id(); }
|
||||||
|
void SetState(ClientUpdateType_t value);
|
||||||
|
bool SendJsonDocToWebSocket(ArduinoJson::JsonDocument& document);
|
||||||
|
|
||||||
|
};
|
149
src/ESPUIclientFsm.cpp
Normal file
149
src/ESPUIclientFsm.cpp
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include "ESPUI.h"
|
||||||
|
#include "ESPUIclient.h"
|
||||||
|
|
||||||
|
//----------------------------------------------
|
||||||
|
// FSM definitions
|
||||||
|
//----------------------------------------------
|
||||||
|
void fsm_EspuiClient_state::Init()
|
||||||
|
{
|
||||||
|
// Serial.println(String("fsm_EspuiClient_state:Init: ") + GetStateName());
|
||||||
|
Parent->pCurrentFsmState = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
bool fsm_EspuiClient_state_Idle::NotifyClient()
|
||||||
|
{
|
||||||
|
bool Response = false;
|
||||||
|
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient"));
|
||||||
|
ClientUpdateType_t TypeToProcess = Parent->ClientUpdateType;
|
||||||
|
// Clear the type so that we capture any changes in type that happen
|
||||||
|
// while we are processing the current request.
|
||||||
|
Parent->ClientUpdateType = ClientUpdateType_t::Synchronized;
|
||||||
|
|
||||||
|
// Start processing the current request.
|
||||||
|
switch (TypeToProcess)
|
||||||
|
{
|
||||||
|
case ClientUpdateType_t::Synchronized:
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:Synchronized"));
|
||||||
|
// Parent->fsm_EspuiClient_state_Idle_imp.Init();
|
||||||
|
Response = true; // Parent->SendClientNotification(ClientUpdateType_t::UpdateNeeded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientUpdateType_t::UpdateNeeded:
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:UpdateNeeded"));
|
||||||
|
Parent->fsm_EspuiClient_state_SendingUpdate_imp.Init();
|
||||||
|
Response = Parent->SendClientNotification(ClientUpdateType_t::UpdateNeeded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientUpdateType_t::RebuildNeeded:
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:RebuildNeeded"));
|
||||||
|
Parent->fsm_EspuiClient_state_Rebuilding_imp.Init();
|
||||||
|
Response = Parent->SendClientNotification(ClientUpdateType_t::RebuildNeeded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientUpdateType_t::ReloadNeeded:
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:ReloadNeeded"));
|
||||||
|
Parent->fsm_EspuiClient_state_Reloading_imp.Init();
|
||||||
|
Response = Parent->SendClientNotification(ClientUpdateType_t::ReloadNeeded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsm_EspuiClient_state_Idle::ProcessAck(uint16_t ControlIndex, String FragmentRequestString)
|
||||||
|
{
|
||||||
|
if(!emptyString.equals(FragmentRequestString))
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle::ProcessAck:Fragmentation:Got fragment Header"));
|
||||||
|
Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequestString);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is an unexpected request for control data from the browser
|
||||||
|
// treat it as if it was a rebuild operation
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Idle: ProcessAck:Error: Rebuild"));
|
||||||
|
Parent->NotifyClient(ClientUpdateType_t::RebuildNeeded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
bool fsm_EspuiClient_state_SendingUpdate::NotifyClient()
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_SendingUpdate:NotifyClient"));
|
||||||
|
return true; /* Ignore request */
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsm_EspuiClient_state_SendingUpdate::ProcessAck(uint16_t ControlIndex, String FragmentRequest)
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_SendingUpdate: ProcessAck"));
|
||||||
|
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequest))
|
||||||
|
{
|
||||||
|
// No more data to send. Go back to idle or start next request
|
||||||
|
Parent->fsm_EspuiClient_state_Idle_imp.Init();
|
||||||
|
Parent->fsm_EspuiClient_state_Idle_imp.NotifyClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
void fsm_EspuiClient_state_Rebuilding::Init()
|
||||||
|
{
|
||||||
|
// Serial.println(String("fsm_EspuiClient_state:Init: ") + GetStateName());
|
||||||
|
Parent->CurrentSyncID = 0;
|
||||||
|
Parent->NextSyncID = 0;
|
||||||
|
Parent->pCurrentFsmState = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fsm_EspuiClient_state_Rebuilding::NotifyClient()
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: NotifyClient"));
|
||||||
|
return true; /* Ignore request */
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsm_EspuiClient_state_Rebuilding::ProcessAck(uint16_t ControlIndex, String FragmentRequest)
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: ProcessAck"));
|
||||||
|
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::RebuildNeeded, FragmentRequest))
|
||||||
|
{
|
||||||
|
// No more data to send. Go back to idle or start next request
|
||||||
|
Parent->fsm_EspuiClient_state_Idle_imp.Init();
|
||||||
|
Parent->fsm_EspuiClient_state_Idle_imp.NotifyClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
//----------------------------------------------
|
||||||
|
void fsm_EspuiClient_state_Reloading::Init()
|
||||||
|
{
|
||||||
|
// Serial.println(String("fsm_EspuiClient_state:Init: ") + GetStateName());
|
||||||
|
Parent->CurrentSyncID = 0;
|
||||||
|
Parent->NextSyncID = 0;
|
||||||
|
Parent->pCurrentFsmState = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsm_EspuiClient_state_Reloading::ProcessAck(uint16_t ControlIndex, String FragmentRequestString)
|
||||||
|
{
|
||||||
|
if(!emptyString.equals(FragmentRequestString))
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Reloading::ProcessAck:Fragmentation:Got fragment Header"));
|
||||||
|
Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequestString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fsm_EspuiClient_state_Reloading::NotifyClient()
|
||||||
|
{
|
||||||
|
// Serial.println(F("fsm_EspuiClient_state_Reloading: NotifyClient"));
|
||||||
|
return true; /* Ignore request */
|
||||||
|
}
|
81
src/ESPUIclientFsm.h
Normal file
81
src/ESPUIclientFsm.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
// forward declaration
|
||||||
|
class ESPUIclient;
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/*
|
||||||
|
* Generic fsm base class.
|
||||||
|
*/
|
||||||
|
/*****************************************************************************/
|
||||||
|
/*****************************************************************************/
|
||||||
|
class fsm_EspuiClient_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fsm_EspuiClient_state() {};
|
||||||
|
virtual ~fsm_EspuiClient_state() {}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
virtual bool NotifyClient() = 0;
|
||||||
|
virtual void ProcessAck(uint16_t id, String FragmentRequest) = 0;
|
||||||
|
virtual String GetStateName () = 0;
|
||||||
|
void SetParent(ESPUIclient * value) { Parent = value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ESPUIclient * Parent = nullptr;
|
||||||
|
|
||||||
|
}; // fsm_EspuiClient_state
|
||||||
|
|
||||||
|
class fsm_EspuiClient_state_Idle : public fsm_EspuiClient_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fsm_EspuiClient_state_Idle() {}
|
||||||
|
virtual ~fsm_EspuiClient_state_Idle() {}
|
||||||
|
|
||||||
|
virtual bool NotifyClient();
|
||||||
|
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||||
|
String GetStateName() { return String(F("Idle")); }
|
||||||
|
|
||||||
|
}; // fsm_EspuiClient_state_Idle
|
||||||
|
|
||||||
|
class fsm_EspuiClient_state_SendingUpdate : public fsm_EspuiClient_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fsm_EspuiClient_state_SendingUpdate() {}
|
||||||
|
virtual ~fsm_EspuiClient_state_SendingUpdate() {}
|
||||||
|
|
||||||
|
virtual bool NotifyClient();
|
||||||
|
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||||
|
String GetStateName() { return String(F("Sending Update")); }
|
||||||
|
|
||||||
|
}; // fsm_EspuiClient_state_SendingUpdate
|
||||||
|
|
||||||
|
class fsm_EspuiClient_state_Rebuilding : public fsm_EspuiClient_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fsm_EspuiClient_state_Rebuilding() {}
|
||||||
|
virtual ~fsm_EspuiClient_state_Rebuilding() {}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
virtual bool NotifyClient();
|
||||||
|
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||||
|
String GetStateName() { return String(F("Sending Rebuild")); }
|
||||||
|
|
||||||
|
}; // fsm_EspuiClient_state_Rebuilding
|
||||||
|
|
||||||
|
class fsm_EspuiClient_state_Reloading : public fsm_EspuiClient_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fsm_EspuiClient_state_Reloading() {}
|
||||||
|
virtual ~fsm_EspuiClient_state_Reloading() {}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
virtual bool NotifyClient();
|
||||||
|
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||||
|
String GetStateName() { return String(F("Reloading")); }
|
||||||
|
|
||||||
|
}; // fsm_EspuiClient_state_Reloading
|
||||||
|
|
336
src/ESPUIcontrol.cpp
Normal file
336
src/ESPUIcontrol.cpp
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
#include "ESPUI.h"
|
||||||
|
|
||||||
|
static uint16_t idCounter = 0;
|
||||||
|
static const String ControlError = "*** ESPUI ERROR: Could not transfer control ***";
|
||||||
|
|
||||||
|
Control::Control(ControlType type, const char* label, std::function<void(Control*, int)> callback,
|
||||||
|
const String& value, ControlColor color, bool visible, uint16_t parentControl)
|
||||||
|
: type(type),
|
||||||
|
label(label),
|
||||||
|
callback(callback),
|
||||||
|
value(value),
|
||||||
|
color(color),
|
||||||
|
visible(visible),
|
||||||
|
wide(false),
|
||||||
|
vertical(false),
|
||||||
|
enabled(true),
|
||||||
|
parentControl(parentControl),
|
||||||
|
next(nullptr)
|
||||||
|
{
|
||||||
|
id = ++idCounter;
|
||||||
|
ControlChangeID = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Control::Control(const Control& Control)
|
||||||
|
: type(Control.type),
|
||||||
|
id(Control.id),
|
||||||
|
label(Control.label),
|
||||||
|
callback(Control.callback),
|
||||||
|
value(Control.value),
|
||||||
|
color(Control.color),
|
||||||
|
visible(Control.visible),
|
||||||
|
parentControl(Control.parentControl),
|
||||||
|
next(Control.next),
|
||||||
|
ControlChangeID(Control.ControlChangeID)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void Control::SendCallback(int type)
|
||||||
|
{
|
||||||
|
if(callback)
|
||||||
|
{
|
||||||
|
callback(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::DeleteControl()
|
||||||
|
{
|
||||||
|
_ToBeDeleted = true;
|
||||||
|
callback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Control::MarshalControl(JsonObject & _item,
|
||||||
|
bool refresh,
|
||||||
|
uint32_t StartingOffset,
|
||||||
|
uint32_t AvailMarshaledLength,
|
||||||
|
uint32_t &EstimatedMarshaledLength)
|
||||||
|
{
|
||||||
|
// this code assumes MaxMarshaledLength > JsonMarshalingRatio
|
||||||
|
// Serial.println(String("MarshalControl: StartingOffset: ") + String(StartingOffset));
|
||||||
|
// Serial.println(String("MarshalControl: AvailMarshaledLength: ") + String(AvailMarshaledLength));
|
||||||
|
// Serial.println(String("MarshalControl: Control ID: ") + String(id));
|
||||||
|
|
||||||
|
bool ControlIsFragmented = false;
|
||||||
|
// create a new item in the response document
|
||||||
|
JsonObject & item = _item;
|
||||||
|
|
||||||
|
// how much space do we expect to use?
|
||||||
|
uint32_t ValueMarshaledLength = (value.length() - StartingOffset) * JsonMarshalingRatio;
|
||||||
|
uint32_t LabelMarshaledLength = strlen(label) * JsonMarshalingRatio;
|
||||||
|
uint32_t MinimumMarshaledLength = LabelMarshaledLength + JsonMarshaledOverhead;
|
||||||
|
uint32_t MaximumMarshaledLength = ValueMarshaledLength + MinimumMarshaledLength;
|
||||||
|
uint32_t SpaceForMarshaledValue = AvailMarshaledLength - MinimumMarshaledLength;
|
||||||
|
// Serial.println(String("MarshalControl: value.length(): ") + String(value.length()));
|
||||||
|
// Serial.println(String("MarshalControl: ValueMarshaledLength: ") + String(ValueMarshaledLength));
|
||||||
|
// Serial.println(String("MarshalControl: LabelMarshaledLength: ") + String(LabelMarshaledLength));
|
||||||
|
// Serial.println(String("MarshalControl: MaximumMarshaledLength: ") + String(MaximumMarshaledLength));
|
||||||
|
// Serial.println(String("MarshalControl: MinimumMarshaledLength: ") + String(MinimumMarshaledLength));
|
||||||
|
// Serial.println(String("MarshalControl: SpaceForMarshaledValue: ") + String(SpaceForMarshaledValue));
|
||||||
|
|
||||||
|
// will the item fit in the remaining space? Fragment if not
|
||||||
|
if (AvailMarshaledLength < MinimumMarshaledLength)
|
||||||
|
{
|
||||||
|
// Serial.println(String("MarshalControl: Cannot Marshal control. Not enough space for basic headers."));
|
||||||
|
EstimatedMarshaledLength = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t MaxValueLength = (SpaceForMarshaledValue / JsonMarshalingRatio);
|
||||||
|
// Serial.println(String("MarshalControl: MaxValueLength: ") + String(MaxValueLength));
|
||||||
|
|
||||||
|
uint32_t ValueLenToSend = min((value.length() - StartingOffset), MaxValueLength);
|
||||||
|
// Serial.println(String("MarshalControl: ValueLenToSend: ") + String(ValueLenToSend));
|
||||||
|
|
||||||
|
uint32_t AdjustedMarshaledLength = (ValueLenToSend * JsonMarshalingRatio) + MinimumMarshaledLength;
|
||||||
|
// Serial.println(String("MarshalControl: AdjustedMarshaledLength: ") + String(AdjustedMarshaledLength));
|
||||||
|
|
||||||
|
bool NeedToFragment = (ValueLenToSend < value.length());
|
||||||
|
// Serial.println(String("MarshalControl: NeedToFragment: ") + String(NeedToFragment));
|
||||||
|
|
||||||
|
if ((AdjustedMarshaledLength > AvailMarshaledLength) && (0 != ValueLenToSend))
|
||||||
|
{
|
||||||
|
// Serial.println(String("MarshalControl: Cannot Marshal control. Not enough space for marshaled control."));
|
||||||
|
EstimatedMarshaledLength = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EstimatedMarshaledLength = AdjustedMarshaledLength;
|
||||||
|
|
||||||
|
// are we fragmenting?
|
||||||
|
if(NeedToFragment || StartingOffset)
|
||||||
|
{
|
||||||
|
// Serial.println(String("MarshalControl:Start Fragment Processing"));
|
||||||
|
// Serial.println(String("MarshalControl:id: ") + String(id));
|
||||||
|
// Serial.println(String("MarshalControl:StartingOffset: ") + String(StartingOffset));
|
||||||
|
/*
|
||||||
|
if(0 == StartingOffset)
|
||||||
|
{
|
||||||
|
Serial.println(String("MarshalControl: New control to fragement. ID: ") + String(id));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.println(String("MarshalControl: Next fragement. ID: ") + String(id));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// indicate that no additional controls should be sent
|
||||||
|
ControlIsFragmented = true;
|
||||||
|
|
||||||
|
// fill in the fragment header
|
||||||
|
_item[F("type")] = uint32_t(ControlType::Fragment);
|
||||||
|
_item[F("id")] = id;
|
||||||
|
|
||||||
|
// Serial.println(String("MarshalControl:Final length: ") + String(length));
|
||||||
|
|
||||||
|
_item[F("offset")] = StartingOffset;
|
||||||
|
_item[F("length")] = ValueLenToSend;
|
||||||
|
_item[F("total")] = value.length();
|
||||||
|
AllocateNamedJsonObject(item, _item, F("control"));
|
||||||
|
}
|
||||||
|
|
||||||
|
item[F("id")] = id;
|
||||||
|
ControlType TempType = (ControlType::Password == type) ? ControlType::Text : type;
|
||||||
|
if(refresh)
|
||||||
|
{
|
||||||
|
item[F("type")] = uint32_t(TempType) + uint32_t(ControlType::UpdateOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item[F("type")] = uint32_t(TempType);
|
||||||
|
}
|
||||||
|
|
||||||
|
item[F("label")] = label;
|
||||||
|
item[F ("value")] = (ControlType::Password == type) ? F ("--------") : value.substring(StartingOffset, StartingOffset + ValueLenToSend);
|
||||||
|
item[F("visible")] = visible;
|
||||||
|
item[F("color")] = (int)color;
|
||||||
|
item[F("enabled")] = enabled;
|
||||||
|
|
||||||
|
if (!panelStyle.isEmpty()) {item[F("panelStyle")] = panelStyle;}
|
||||||
|
if (!elementStyle.isEmpty()) {item[F("elementStyle")] = elementStyle;}
|
||||||
|
if (!inputType.isEmpty()) {item[F("inputType")] = inputType;}
|
||||||
|
if (wide == true) {item[F("wide")] = true;}
|
||||||
|
if (vertical == true) {item[F("vertical")] = true;}
|
||||||
|
if (parentControl != Control::noParent)
|
||||||
|
{
|
||||||
|
item[F("parentControl")] = String(parentControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for selects: to preselect an option, you have to add
|
||||||
|
// "selected" to <option>
|
||||||
|
if (ControlType::Option == type)
|
||||||
|
{
|
||||||
|
Control* ParentControl = ESPUI.getControlNoLock(parentControl);
|
||||||
|
if (nullptr == ParentControl)
|
||||||
|
{
|
||||||
|
item[F("selected")] = emptyString;
|
||||||
|
}
|
||||||
|
else if (ParentControl->value == value)
|
||||||
|
{
|
||||||
|
item[F("selected")] = F("selected");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item[F("selected")] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial.println(String("MarshalControl:Done"));
|
||||||
|
return ControlIsFragmented;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::MarshalErrorMessage(JsonObject & item)
|
||||||
|
{
|
||||||
|
item[F("id")] = id;
|
||||||
|
item[F("type")] = uint32_t(ControlType::Label);
|
||||||
|
item[F("label")] = ControlError.c_str();
|
||||||
|
item[F("value")] = ControlError;
|
||||||
|
item[F("visible")] = true;
|
||||||
|
item[F("color")] = (int)ControlColor::Carrot;
|
||||||
|
item[F("enabled")] = true;
|
||||||
|
|
||||||
|
if (parentControl != Control::noParent)
|
||||||
|
{
|
||||||
|
item[F("parentControl")] = String(parentControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::onWsEvent(String & cmd, String& data)
|
||||||
|
{
|
||||||
|
do // once
|
||||||
|
{
|
||||||
|
// Serial.println(String(F("Control::onWsEvent")));
|
||||||
|
SetControlChangedId(ESPUI.GetNextControlChangeId());
|
||||||
|
if (!HasCallback())
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity)
|
||||||
|
{
|
||||||
|
Serial.println(String(F("Control::onWsEvent:No callback found for ID ")) + String(id));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial.println("Control::onWsEvent:Generating callback");
|
||||||
|
if (cmd.equals(F("bdown")))
|
||||||
|
{
|
||||||
|
SendCallback(B_DOWN);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.equals(F("bup")))
|
||||||
|
{
|
||||||
|
SendCallback(B_UP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.equals(F("pfdown")))
|
||||||
|
{
|
||||||
|
SendCallback(P_FOR_DOWN);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.equals(F("pfup")))
|
||||||
|
{
|
||||||
|
SendCallback(P_FOR_UP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.equals(F("pldown")))
|
||||||
|
{
|
||||||
|
SendCallback(P_LEFT_DOWN);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (cmd.equals(F("plup")))
|
||||||
|
{
|
||||||
|
SendCallback(P_LEFT_UP);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("prdown")))
|
||||||
|
{
|
||||||
|
SendCallback(P_RIGHT_DOWN);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("prup")))
|
||||||
|
{
|
||||||
|
SendCallback(P_RIGHT_UP);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("pbdown")))
|
||||||
|
{
|
||||||
|
SendCallback(P_BACK_DOWN);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("pbup")))
|
||||||
|
{
|
||||||
|
SendCallback(P_BACK_UP);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("pcdown")))
|
||||||
|
{
|
||||||
|
SendCallback(P_CENTER_DOWN);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("pcup")))
|
||||||
|
{
|
||||||
|
SendCallback(P_CENTER_UP);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("sactive")))
|
||||||
|
{
|
||||||
|
value = "1";
|
||||||
|
SendCallback(S_ACTIVE);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("sinactive")))
|
||||||
|
{
|
||||||
|
value = "0";
|
||||||
|
// updateControl(c, client->id());
|
||||||
|
SendCallback(S_INACTIVE);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("slvalue")))
|
||||||
|
{
|
||||||
|
value = data;
|
||||||
|
// updateControl(c, client->id());
|
||||||
|
SendCallback(SL_VALUE);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("nvalue")))
|
||||||
|
{
|
||||||
|
value = data;
|
||||||
|
// updateControl(c, client->id());
|
||||||
|
SendCallback(N_VALUE);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("tvalue")))
|
||||||
|
{
|
||||||
|
value = data;
|
||||||
|
// updateControl(c, client->id());
|
||||||
|
SendCallback(T_VALUE);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("tabvalue")))
|
||||||
|
{
|
||||||
|
SendCallback(0);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("svalue")))
|
||||||
|
{
|
||||||
|
value = data;
|
||||||
|
// updateControl(c, client->id());
|
||||||
|
SendCallback(S_VALUE);
|
||||||
|
}
|
||||||
|
else if (cmd.equals(F("time")))
|
||||||
|
{
|
||||||
|
value = data;
|
||||||
|
// updateControl(c, client->id());
|
||||||
|
SendCallback(TM_VALUE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESPUI)
|
||||||
|
if (ESPUI.verbosity)
|
||||||
|
{
|
||||||
|
Serial.println(F("Control::onWsEvent:Malformed message from the websocket"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} while (false);
|
||||||
|
}
|
133
src/ESPUIcontrol.h
Normal file
133
src/ESPUIcontrol.h
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
enum ControlType : uint8_t
|
||||||
|
{
|
||||||
|
// fixed Controls
|
||||||
|
Title = 0,
|
||||||
|
|
||||||
|
// updatable Controls
|
||||||
|
Pad,
|
||||||
|
PadWithCenter,
|
||||||
|
Button,
|
||||||
|
Label,
|
||||||
|
Switcher,
|
||||||
|
Slider,
|
||||||
|
Number,
|
||||||
|
Text,
|
||||||
|
Graph,
|
||||||
|
GraphPoint,
|
||||||
|
Tab,
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
|
Min,
|
||||||
|
Max,
|
||||||
|
Step,
|
||||||
|
Gauge,
|
||||||
|
Accel,
|
||||||
|
Separator,
|
||||||
|
Time,
|
||||||
|
FileDisplay,
|
||||||
|
|
||||||
|
Fragment = 98,
|
||||||
|
Password = 99,
|
||||||
|
UpdateOffset = 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ControlColor : uint8_t
|
||||||
|
{
|
||||||
|
Turquoise,
|
||||||
|
Emerald,
|
||||||
|
Peterriver,
|
||||||
|
Wetasphalt,
|
||||||
|
Sunflower,
|
||||||
|
Carrot,
|
||||||
|
Alizarin,
|
||||||
|
Dark,
|
||||||
|
None = 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
class Control
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ControlType type;
|
||||||
|
uint16_t id; // just mirroring the id here for practical reasons
|
||||||
|
const char* label;
|
||||||
|
std::function<void(Control*, int)> callback;
|
||||||
|
String value;
|
||||||
|
ControlColor color;
|
||||||
|
bool visible;
|
||||||
|
bool wide;
|
||||||
|
bool vertical;
|
||||||
|
bool enabled;
|
||||||
|
uint16_t parentControl;
|
||||||
|
String panelStyle;
|
||||||
|
String elementStyle;
|
||||||
|
String inputType;
|
||||||
|
Control* next;
|
||||||
|
|
||||||
|
static constexpr uint16_t noParent = 0xffff;
|
||||||
|
|
||||||
|
Control(ControlType type,
|
||||||
|
const char* label,
|
||||||
|
std::function<void(Control*, int)> callback,
|
||||||
|
const String& value,
|
||||||
|
ControlColor color,
|
||||||
|
bool visible,
|
||||||
|
uint16_t parentControl);
|
||||||
|
|
||||||
|
Control(const Control& Control);
|
||||||
|
|
||||||
|
void SendCallback(int type);
|
||||||
|
bool HasCallback() { return (nullptr != callback); }
|
||||||
|
bool MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset, uint32_t MaxLength, uint32_t & EstimmatedUsedSpace);
|
||||||
|
void MarshalErrorMessage(ArduinoJson::JsonObject& item);
|
||||||
|
void DeleteControl();
|
||||||
|
void onWsEvent(String& cmd, String& data);
|
||||||
|
inline bool ToBeDeleted() { return _ToBeDeleted; }
|
||||||
|
inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);}
|
||||||
|
void SetControlChangedId(uint32_t value) {ControlChangeID = value;}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _ToBeDeleted = false;
|
||||||
|
uint32_t ControlChangeID = 0;
|
||||||
|
String OldValue = emptyString;
|
||||||
|
|
||||||
|
// multiplier for converting a typical controller label or value to a Json object
|
||||||
|
#define JsonMarshalingRatio 3
|
||||||
|
// Marshaed Control overhead length
|
||||||
|
#define JsonMarshaledOverhead 64
|
||||||
|
};
|
||||||
|
|
||||||
|
#define UI_TITLE ControlType::Title
|
||||||
|
#define UI_LABEL ControlType::Label
|
||||||
|
#define UI_BUTTON ControlType::Button
|
||||||
|
#define UI_SWITCHER ControlType::Switcher
|
||||||
|
#define UI_PAD ControlType::Pad
|
||||||
|
#define UI_CPAD ControlType::Cpad
|
||||||
|
#define UI_SLIDER ControlType::Slider
|
||||||
|
#define UI_NUMBER ControlType::Number
|
||||||
|
#define UI_TEXT_INPUT ControlType::Text
|
||||||
|
#define UI_GRAPH ControlType::Graph
|
||||||
|
#define UI_ADD_GRAPH_POINT ControlType::GraphPoint
|
||||||
|
|
||||||
|
#define UPDATE_LABEL ControlType::UpdateLabel
|
||||||
|
#define UPDATE_SWITCHER ControlType::UpdateSwitcher
|
||||||
|
#define UPDATE_SLIDER ControlType::UpdateSlider
|
||||||
|
#define UPDATE_NUMBER ControlType::UpdateNumber
|
||||||
|
#define UPDATE_TEXT_INPUT ControlType::UpdateText
|
||||||
|
#define CLEAR_GRAPH ControlType::ClearGraph
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define COLOR_TURQUOISE ControlColor::Turquoise
|
||||||
|
#define COLOR_EMERALD ControlColor::Emerald
|
||||||
|
#define COLOR_PETERRIVER ControlColor::Peterriver
|
||||||
|
#define COLOR_WETASPHALT ControlColor::Wetasphalt
|
||||||
|
#define COLOR_SUNFLOWER ControlColor::Sunflower
|
||||||
|
#define COLOR_CARROT ControlColor::Carrot
|
||||||
|
#define COLOR_ALIZARIN ControlColor::Alizarin
|
||||||
|
#define COLOR_DARK ControlColor::Dark
|
||||||
|
#define COLOR_NONE ControlColor::None
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
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(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 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===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));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);}}}
|
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);}
|
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;}
|
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 "+
|
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+
|
||||||
@ -16,4 +16,4 @@ function renderGraphSvg(dataArray,renderId){var figure=document.getElementById(r
|
|||||||
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);}
|
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[1245] PROGMEM = { 31,139,8,0,143,181,138,98,2,255,205,87,95,111,219,54,16,127,247,167,112,4,44,16,107,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,219,82,134,58,45,186,135,56,226,253,231,241,119,119,228,98,157,69,130,229,217,56,101,25,253,192,73,177,116,11,194,105,38,188,234,38,138,104,89,230,220,147,246,11,237,162,60,43,197,120,203,98,177,196,175,95,5,161,89,47,41,75,150,2,207,27,66,178,22,130,114,60,183,235,130,85,52,45,63,81,254,200,162,127,240,85,16,46,172,219,108,189,162,156,69,143,156,100,229,34,231,176,112,99,34,200,29,203,60,253,159,84,94,81,169,21,252,146,10,237,54,132,143,21,227,61,91,44,112,45,49,181,26,69,165,201,90,116,106,212,20,235,51,1,87,216,48,103,86,217,139,242,156,199,134,101,105,51,35,19,114,42,214,60,219,137,252,157,146,121,99,131,213,145,161,157,225,234,133,245,140,94,52,126,38,218,111,184,247,68,254,30,104,173,178,246,215,104,235,149,137,17,189,104,67,153,212,6,195,253,62,220,143,154,44,145,138,149,159,105,22,83,14,249,201,57,131,243,81,210,153,39,108,222,76,102,148,220,7,158,175,11,28,231,17,100,54,19,126,196,41,17,244,215,148,170,213,253,131,235,44,133,40,222,204,102,219,237,214,223,94,249,57,79,102,175,130,32,152,149,155,196,241,156,196,65,161,53,244,137,192,33,159,105,167,0,93,48,213,196,227,151,84,220,8,193,217,223,107,65,93,39,74,73,89,58,94,103,39,19,103,170,132,107,247,42,45,216,128,168,94,147,10,107,212,77,59,84,169,164,12,246,14,201,32,92,175,217,162,155,46,140,177,83,57,104,103,119,119,20,85,12,129,223,141,157,137,242,62,113,224,67,154,143,91,77,35,85,67,67,33,164,220,85,190,24,214,167,205,174,177,18,8,217,100,130,118,224,211,101,83,125,178,63,29,2,31,227,224,242,146,93,96,173,100,78,76,208,74,156,155,100,165,11,9,83,255,124,150,101,148,255,246,120,119,139,239,212,198,22,41,64,202,109,208,225,50,84,203,29,238,184,114,60,214,203,144,142,103,54,218,195,139,129,233,188,164,171,131,243,37,69,1,248,124,183,100,105,236,42,29,4,8,222,195,214,233,121,201,110,104,80,243,157,100,75,157,228,107,44,117,210,219,100,203,167,146,45,219,100,3,253,155,148,71,194,89,124,11,13,243,153,229,241,195,15,191,26,58,99,57,4,140,24,180,156,169,255,179,62,254,33,112,248,87,154,109,179,244,95,39,207,142,107,76,185,182,71,117,128,44,107,113,136,111,144,55,128,74,171,161,161,57,234,23,178,96,69,161,153,130,39,76,107,161,237,205,106,112,54,189,185,103,104,122,85,51,220,60,249,120,216,175,211,231,99,168,241,232,115,10,121,118,81,40,79,40,80,36,23,173,220,146,148,247,144,39,23,125,249,114,33,79,169,118,68,193,30,85,136,202,205,3,156,93,150,96,115,106,205,22,218,205,250,153,209,52,53,219,10,200,99,129,112,11,137,164,110,79,44,151,151,189,161,180,222,39,216,128,100,244,100,0,35,21,193,232,201,16,246,163,244,20,147,245,64,114,74,184,140,80,53,133,210,94,220,182,225,244,2,36,213,208,236,96,163,200,89,38,206,5,135,86,126,118,187,106,173,12,108,89,223,99,180,84,249,157,225,244,181,135,175,39,191,186,68,253,78,210,53,197,199,167,109,110,6,184,139,135,70,216,48,101,171,44,251,148,37,238,34,229,72,57,98,60,74,207,174,78,163,13,9,53,31,199,185,87,189,119,136,167,102,239,0,143,131,229,121,61,131,190,235,240,232,100,18,138,122,6,69,213,97,118,51,53,48,87,6,47,21,195,67,229,229,147,35,101,234,255,162,249,29,56,119,43,207,100,107,144,93,223,72,70,61,53,219,106,40,129,22,76,184,231,101,210,34,112,197,50,23,121,157,53,169,96,29,76,204,181,211,235,94,85,161,130,158,52,42,143,140,202,35,163,7,23,220,198,133,153,114,77,107,209,73,111,172,250,230,237,113,44,163,242,127,42,243,21,243,203,175,95,68,135,102,52,169,78,253,183,48,212,233,159,92,155,211,207,210,135,77,162,159,92,55,156,19,233,25,198,199,216,52,137,5,75,214,188,83,170,9,21,117,37,188,149,31,99,183,17,174,199,143,17,87,157,70,67,224,62,143,105,169,250,77,77,231,116,149,111,168,65,71,77,130,86,41,52,1,213,83,17,202,232,220,218,83,191,40,132,223,35,152,111,24,221,190,205,85,37,4,227,96,252,122,30,140,231,243,160,87,178,80,141,152,111,232,77,89,208,72,232,119,35,104,193,117,42,254,3,254,198,43,74,69,61,193,204,131,30,44,120,238,193,35,214,3,168,121,43,251,158,102,56,176,79,222,186,251,182,175,86,219,190,199,236,90,41,250,41,205,18,177,132,231,109,214,47,166,132,254,132,155,249,95,126,5,66,122,98,116,165,148,171,189,242,222,163,186,210,207,102,136,170,143,7,247,126,245,36,70,29,12,232,142,4,74,170,156,83,233,170,165,215,112,161,120,138,118,207,121,99,40,87,113,33,84,43,147,234,28,101,244,3,178,41,255,167,217,148,207,201,166,86,134,43,89,93,101,221,182,12,152,133,90,251,23,198,24,146,161,158,18,0,0 };
|
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 };
|
||||||
|
@ -2,4 +2,4 @@ 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><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>
|
||||||
)=====";
|
)=====";
|
||||||
|
|
||||||
const uint8_t HTML_INDEX_GZIP[916] PROGMEM = { 31,139,8,0,143,181,138,98,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[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 };
|
||||||
|
@ -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:hidden}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: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}
|
||||||
)=====";
|
)=====";
|
||||||
|
|
||||||
const uint8_t CSS_NORMALIZE_GZIP[861] PROGMEM = { 31,139,8,0,143,181,138,98,2,255,149,84,237,142,155,58,16,125,149,104,171,74,183,146,137,216,237,199,94,25,221,39,137,242,99,176,7,112,227,47,217,38,155,20,241,238,119,12,132,36,219,108,165,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,133,5,200,159,125,76,252,185,44,63,87,197,27,214,7,149,30,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,103,39,136,153,224,116,220,127,89,83,88,103,177,234,80,181,93,162,238,118,157,146,18,237,158,37,52,244,59,225,221,185,17,134,26,196,33,247,98,101,33,156,118,129,167,64,12,123,8,104,211,8,28,168,163,35,145,195,59,71,112,6,215,167,12,33,211,86,215,97,151,84,210,184,31,106,23,136,147,162,118,41,57,195,159,253,105,35,233,21,229,88,179,72,240,108,59,43,248,54,131,170,157,150,163,108,236,28,140,233,172,145,171,68,61,138,177,123,94,130,36,25,127,65,83,45,42,109,127,188,162,217,148,35,125,30,110,16,243,79,77,83,86,51,236,79,101,89,142,209,128,214,55,41,254,37,181,99,79,40,122,127,19,125,253,254,185,154,104,190,176,84,121,23,85,86,142,7,36,142,168,225,15,185,207,153,146,243,188,216,126,71,147,115,15,75,215,197,246,37,71,148,105,23,58,136,163,120,108,39,153,120,32,239,124,25,50,131,141,118,111,124,214,100,156,141,117,113,226,51,117,248,173,244,167,177,11,67,97,220,47,162,243,148,241,42,219,242,44,51,233,145,67,213,7,225,85,113,79,41,215,74,208,39,55,10,71,198,62,212,146,76,135,44,130,241,119,3,101,156,117,164,183,64,182,190,85,87,174,8,213,88,247,212,161,101,202,250,62,49,231,211,108,125,34,132,236,206,242,136,145,89,96,152,101,80,182,163,217,76,83,134,245,99,157,181,57,211,21,222,81,69,85,107,188,84,152,83,14,211,212,78,54,108,92,48,179,81,151,19,29,173,131,205,4,100,151,206,30,255,123,154,227,79,123,118,27,164,193,194,244,46,70,82,25,69,193,225,178,27,192,123,4,42,34,144,207,73,42,209,135,72,45,120,167,136,214,176,148,220,209,188,0,97,148,251,219,226,107,112,88,46,73,108,160,215,105,185,196,249,164,96,227,68,31,11,101,45,45,140,233,222,239,241,213,44,149,7,41,179,168,229,56,29,29,110,29,106,137,7,208,227,109,63,162,67,113,32,225,223,183,14,180,27,158,242,72,174,46,89,167,243,244,190,198,114,199,246,166,198,240,180,39,116,11,55,19,180,34,122,101,139,91,241,63,60,79,107,225,254,252,176,0,159,252,119,39,3,113,46,186,199,50,100,221,27,133,90,86,127,242,255,229,226,95,141,199,67,12,87,252,115,164,16,25,134,126,212,242,135,87,36,10,23,32,239,142,71,29,77,214,157,90,34,67,94,164,206,251,49,58,173,228,38,42,77,147,176,142,199,230,197,95,37,218,126,165,117,178,217,254,120,153,30,175,121,183,104,108,209,202,71,142,89,135,240,126,240,47,179,250,251,250,77,217,189,151,189,77,147,171,193,71,228,151,151,106,249,145,151,193,82,64,178,212,13,215,130,255,3,4,241,118,208,151,7,0,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 };
|
||||||
|
@ -12,4 +12,4 @@ function sliderDiscrete_tmplt(){var tmplt='<div class="slider">'+
|
|||||||
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);}}
|
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,143,181,138,98,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 };
|
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
Reference in New Issue
Block a user