mirror of
https://github.com/s00500/ESPUI.git
synced 2025-07-03 19:50:20 +00:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
83
README.md
83
README.md
@ -43,6 +43,7 @@ The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker,
|
||||
* [Grouped controls](#grouped-controls)
|
||||
* [Wide controls](#wide-controls)
|
||||
* [Graph (Experimental)](#graph--experimental-)
|
||||
* [Captive Portal](#captive-portal)
|
||||
- [Notes for Development](#notes-for-development)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
@ -146,26 +147,59 @@ more program memory to work with.
|
||||
|
||||
## Documentation
|
||||
|
||||
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.
|
||||
|
||||
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 value)` 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.
|
||||
|
||||
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.
|
||||
<br><br>
|
||||
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.
|
||||
<br><br>
|
||||
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.
|
||||
<br><br>
|
||||
The below example creates a button and defines a lambda function to implicitly create an `ExtendedCallback` which then invokes a more specialized button callback handler. The example uses the `UserParameter` to hold the `this` pointer to an object instance, providing a mechanism for sending the event to a specific object without the need for a switch / map / lookup translation of the Sender Id to an object reference.
|
||||
```
|
||||
void YourClassName::setup()
|
||||
{
|
||||
ButtonElementId = ESPUI.addControl(
|
||||
ControlType::Button,
|
||||
ButtonLabel.c_str(),
|
||||
" Button Face Text ",
|
||||
ControlColor::None,
|
||||
ParentElementId,
|
||||
[](Control *sender, int eventname, void* param)
|
||||
{
|
||||
if(param)
|
||||
{
|
||||
reinterpret_cast<YourClassName*>(param)->myButtonCallback(sender, eventname);
|
||||
}
|
||||
},
|
||||
this); // <-Third parameter for the extended callback
|
||||
|
||||
// or
|
||||
ButtonElementId = ESPUI.button(
|
||||
" Button Face Text ",
|
||||
[](Control *sender, int eventname, void* param)
|
||||
{
|
||||
if(param)
|
||||
{
|
||||
reinterpret_cast<YourClassName*>(param)->myButtonCallback(sender, eventname);
|
||||
}
|
||||
},
|
||||
this); // <-Third parameter for the extended callback
|
||||
}
|
||||
```
|
||||
```
|
||||
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
|
||||
|
||||

|
||||
@ -610,6 +644,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!_
|
||||
|
||||
### 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
|
||||
|
||||
If you want to work on the HTML/CSS/JS files, do make changes in the _data_
|
||||
|
118
data/js/controls.js
vendored
118
data/js/controls.js
vendored
@ -115,6 +115,7 @@ function colorClass(colorId) {
|
||||
|
||||
var websock;
|
||||
var websockConnected = false;
|
||||
var WebSocketTimer = null;
|
||||
|
||||
function requestOrientationPermission() {
|
||||
/*
|
||||
@ -183,12 +184,14 @@ function restoreGraphData(id) {
|
||||
function restart() {
|
||||
$(document).add("*").off();
|
||||
$("#row").html("");
|
||||
websock.close();
|
||||
conStatusError();
|
||||
start();
|
||||
}
|
||||
|
||||
function conStatusError() {
|
||||
if (true === websockConnected) {
|
||||
websockConnected = false;
|
||||
websock.close();
|
||||
$("#conStatus").removeClass("color-green");
|
||||
$("#conStatus").addClass("color-red");
|
||||
$("#conStatus").html("Error / No Connection ↻");
|
||||
@ -196,6 +199,7 @@ function conStatusError() {
|
||||
$("#conStatus").on({
|
||||
click: restart,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
@ -217,6 +221,20 @@ function start() {
|
||||
} else {
|
||||
websock = new WebSocket("ws://" + window.location.hostname + "/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) {
|
||||
console.log("websock open");
|
||||
$("#conStatus").addClass("color-green");
|
||||
@ -230,13 +248,23 @@ function start() {
|
||||
};
|
||||
|
||||
websock.onerror = function (evt) {
|
||||
console.log("websock Error");
|
||||
console.log(evt);
|
||||
conStatusError();
|
||||
|
||||
restart();
|
||||
};
|
||||
|
||||
var handleEvent = function (evt) {
|
||||
console.log(evt);
|
||||
try {
|
||||
var data = JSON.parse(evt.data);
|
||||
}
|
||||
catch (Event) {
|
||||
console.error(Event);
|
||||
// start the update over again
|
||||
websock.send("uiok:" + 0);
|
||||
return;
|
||||
}
|
||||
var e = document.body;
|
||||
var center = "";
|
||||
|
||||
@ -260,7 +288,7 @@ function start() {
|
||||
//If there are more elements in the complete UI, then request them
|
||||
//Note: we subtract 1 from data.controls.length because the controls always
|
||||
//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));
|
||||
}
|
||||
break;
|
||||
@ -274,7 +302,7 @@ function start() {
|
||||
});
|
||||
|
||||
//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)));
|
||||
}
|
||||
break;
|
||||
@ -434,9 +462,9 @@ function start() {
|
||||
case UI_MIN:
|
||||
if (data.parentControl) {
|
||||
//Is it applied to a slider?
|
||||
if($('#sl' + data.parentControl).length) {
|
||||
if ($('#sl' + data.parentControl).length) {
|
||||
$('#sl' + data.parentControl).attr("min", data.value);
|
||||
} else if($('#num' + data.parentControl).length) {
|
||||
} else if ($('#num' + data.parentControl).length) {
|
||||
//Or a number
|
||||
$('#num' + data.parentControl).attr("min", data.value);
|
||||
}
|
||||
@ -446,12 +474,12 @@ function start() {
|
||||
case UI_MAX:
|
||||
if (data.parentControl) {
|
||||
//Is it applied to a slider?
|
||||
if($('#sl' + data.parentControl).length) {
|
||||
if ($('#sl' + data.parentControl).length) {
|
||||
$('#sl' + data.parentControl).attr("max", data.value);
|
||||
} else if($('#text' + data.parentControl).length) {
|
||||
} else if ($('#text' + data.parentControl).length) {
|
||||
//Is it a text element
|
||||
$('#text' + data.parentControl).attr("maxlength", data.value);
|
||||
} else if($('#num' + data.parentControl).length) {
|
||||
} else if ($('#num' + data.parentControl).length) {
|
||||
//Or a number
|
||||
$('#num' + data.parentControl).attr("max", data.value);
|
||||
}
|
||||
@ -500,46 +528,46 @@ function start() {
|
||||
*/
|
||||
case UPDATE_LABEL:
|
||||
$("#l" + data.id).html(data.value);
|
||||
if(data.hasOwnProperty('elementStyle')) {
|
||||
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')) {
|
||||
if (data.hasOwnProperty('elementStyle')) {
|
||||
$("#sl" + data.id).attr("style", data.elementStyle);
|
||||
}
|
||||
break;
|
||||
|
||||
case UPDATE_SLIDER:
|
||||
$("#sl" + data.id).attr("value", data.value)
|
||||
slider_move($("#id" + data.id), data.value, "100", false);
|
||||
if(data.hasOwnProperty('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')) {
|
||||
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')) {
|
||||
if (data.hasOwnProperty('elementStyle')) {
|
||||
$("#text" + data.id).attr("style", data.elementStyle);
|
||||
}
|
||||
if(data.hasOwnProperty('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')) {
|
||||
if (data.hasOwnProperty('elementStyle')) {
|
||||
$("#select" + data.id).attr("style", data.elementStyle);
|
||||
}
|
||||
break;
|
||||
@ -547,7 +575,7 @@ function start() {
|
||||
case UPDATE_BUTTON:
|
||||
$("#btn" + data.id).val(data.value);
|
||||
$("#btn" + data.id).text(data.value);
|
||||
if(data.hasOwnProperty('elementStyle')) {
|
||||
if (data.hasOwnProperty('elementStyle')) {
|
||||
$("#btn" + data.id).attr("style", data.elementStyle);
|
||||
}
|
||||
break;
|
||||
@ -557,7 +585,7 @@ function start() {
|
||||
break;
|
||||
case UPDATE_GAUGE:
|
||||
$("#gauge" + data.id).val(data.value);
|
||||
if(data.hasOwnProperty('elementStyle')) {
|
||||
if (data.hasOwnProperty('elementStyle')) {
|
||||
$("#gauge" + data.id).attr("style", data.elementStyle);
|
||||
}
|
||||
break;
|
||||
@ -583,12 +611,12 @@ function start() {
|
||||
//An "update" message was just recieved and processed
|
||||
var element = $("#id" + data.id);
|
||||
|
||||
if(data.hasOwnProperty('panelStyle')) {
|
||||
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'])
|
||||
$("#id" + data.id).show();
|
||||
else
|
||||
$("#id" + data.id).hide();
|
||||
@ -609,7 +637,7 @@ function start() {
|
||||
processEnabled(data);
|
||||
}
|
||||
|
||||
$(".range-slider__range").each(function(){
|
||||
$(".range-slider__range").each(function () {
|
||||
$(this)[0].value = $(this).attr("value");
|
||||
$(this).next().html($(this).attr("value"));
|
||||
});
|
||||
@ -622,7 +650,7 @@ function sliderchange(number) {
|
||||
var val = $("#sl" + number).val();
|
||||
websock.send("slvalue:" + val + ":" + number);
|
||||
|
||||
$(".range-slider__range").each(function(){
|
||||
$(".range-slider__range").each(function () {
|
||||
$(this).attr("value", $(this)[0].value);
|
||||
});
|
||||
}
|
||||
@ -653,7 +681,7 @@ function buttonclick(number, isdown) {
|
||||
}
|
||||
|
||||
function padclick(type, number, isdown) {
|
||||
if($("#id" + number + " nav").hasClass("disabled")) {
|
||||
if ($("#id" + number + " nav").hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
@ -700,21 +728,23 @@ function switcher(number, state) {
|
||||
|
||||
var rangeSlider = function (isDiscrete) {
|
||||
var range = $(".range-slider__range");
|
||||
var slidercb = function() {
|
||||
var slidercb = function () {
|
||||
sliderchange($(this).attr("id").replace(/^\D+/g, ""));
|
||||
};
|
||||
|
||||
range.on({input: function() {
|
||||
$(this).next().html(this.value)}
|
||||
range.on({
|
||||
input: function () {
|
||||
$(this).next().html(this.value)
|
||||
}
|
||||
});
|
||||
|
||||
range.each(function() {
|
||||
range.each(function () {
|
||||
$(this).next().html(this.value);
|
||||
if($(this).attr("callbackSet") != "true") {
|
||||
if ($(this).attr("callbackSet") != "true") {
|
||||
if (!isDiscrete) {
|
||||
$(this).on({input: slidercb}); //input fires when dragging
|
||||
$(this).on({ input: slidercb }); //input fires when dragging
|
||||
} else {
|
||||
$(this).on({change: slidercb}); //change fires only once released
|
||||
$(this).on({ change: slidercb }); //change fires only once released
|
||||
}
|
||||
$(this).attr("callbackSet", "true");
|
||||
}
|
||||
@ -722,18 +752,18 @@ var rangeSlider = function (isDiscrete) {
|
||||
};
|
||||
|
||||
|
||||
var addToHTML = function(data) {
|
||||
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) {
|
||||
if (!data.hasOwnProperty('parentControl') || $("#tab" + data.parentControl).length > 0) {
|
||||
//We add the control with its own panel
|
||||
var parent = data.hasOwnProperty('parentControl') ?
|
||||
$("#tab" + data.parentControl) :
|
||||
$("#row");
|
||||
|
||||
var html = "";
|
||||
switch(data.type) {
|
||||
switch (data.type) {
|
||||
case UI_LABEL:
|
||||
case UI_BUTTON:
|
||||
case UI_SWITCHER:
|
||||
@ -769,11 +799,11 @@ var addToHTML = function(data) {
|
||||
}
|
||||
}
|
||||
|
||||
var elementHTML = function(data) {
|
||||
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) {
|
||||
switch (data.type) {
|
||||
case UI_LABEL:
|
||||
return "<span id='l" + id + "' " + elementStyle +
|
||||
" class='label label-wrap'>" + data.value + "</span>";
|
||||
@ -815,13 +845,13 @@ var elementHTML = function(data) {
|
||||
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 +
|
||||
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 +
|
||||
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 +
|
||||
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>";
|
||||
@ -838,14 +868,14 @@ var elementHTML = function(data) {
|
||||
|
||||
|
||||
|
||||
var processEnabled = function(data) {
|
||||
var processEnabled = function (data) {
|
||||
//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
|
||||
//The switcher and pads also require the addition of the "disabled" class
|
||||
switch(data.type) {
|
||||
switch (data.type) {
|
||||
case UI_SWITCHER:
|
||||
case UPDATE_SWITCHER:
|
||||
if(data.enabled) {
|
||||
if (data.enabled) {
|
||||
$("#sl" + data.id).removeClass('disabled');
|
||||
$("#s" + data.id).prop("disabled", false);
|
||||
} else {
|
||||
@ -883,7 +913,7 @@ var processEnabled = function(data) {
|
||||
case UI_CPAD:
|
||||
case UPDATE_PAD:
|
||||
case UPDATE_CPAD:
|
||||
if(data.enabled) {
|
||||
if (data.enabled) {
|
||||
$("#id" + data.id + " nav").removeClass('disabled');
|
||||
} else {
|
||||
$("#id" + data.id + " nav").addClass('disabled');
|
||||
|
19
data/js/controls.min.js
vendored
19
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"";}}
|
||||
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 restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);return savedData[id];}
|
||||
return[];}
|
||||
function restart(){$(document).add("*").off();$("#row").html("");websock.close();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 restart(){$(document).add("*").off();$("#row").html("");conStatusError();start();}
|
||||
function conStatusError(){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 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");}
|
||||
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;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log("websock Error");console.log(evt);restart();};var handleEvent=function(evt){console.log(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));}
|
||||
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);},});}
|
||||
@ -33,7 +36,7 @@ break;case ADD_GRAPH_POINT:var ts=Math.round(new Date().getTime()/1000);graphDat
|
||||
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_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_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);}
|
||||
@ -93,9 +96,9 @@ 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+"' "+
|
||||
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+
|
||||
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_TEXT_INPUT:return"<input "+inputType+"style='color:black;' "+elementStyle+" id='text"+id+
|
||||
"' value='"+data.value+"' onchange='textchange("+id+")' />";case UI_SELECT:return"<select style='color:black;' "+elementStyle+" id='select"+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; "+data.elementStyle+"' id='text"+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+
|
||||
"' 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"";}}
|
||||
|
@ -49,6 +49,7 @@ void getTimeCallback(Control *sender, int type);
|
||||
void graphAddCallback(Control *sender, int type);
|
||||
void graphClearCallback(Control *sender, int type);
|
||||
void randomString(char *buf, int len);
|
||||
void extendedCallback(Control* sender, int type, void* param);
|
||||
|
||||
//UI handles
|
||||
uint16_t wifi_ssid_text, wifi_pass_text;
|
||||
@ -81,7 +82,7 @@ void setUpUI() {
|
||||
auto maintab = ESPUI.addControl(Tab, "", "Basic controls");
|
||||
|
||||
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);
|
||||
mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback);
|
||||
|
||||
@ -360,7 +361,23 @@ void generalCallback(Control *sender, int type) {
|
||||
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.println(String("param = ") + String((int)param));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
randomSeed(0);
|
||||
|
@ -55,8 +55,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(int(param)));
|
||||
switch (type)
|
||||
{
|
||||
case B_DOWN:
|
||||
@ -210,7 +211,8 @@ void setup(void)
|
||||
}
|
||||
#else
|
||||
uint32_t chipid = ESP.getChipId();
|
||||
#endif char ap_ssid[25];
|
||||
#endif
|
||||
char ap_ssid[25];
|
||||
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
||||
WiFi.softAP(ap_ssid);
|
||||
|
||||
@ -249,7 +251,7 @@ void setup(void)
|
||||
button1 = ESPUI.addControl(
|
||||
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
|
||||
ESPUI.addControl(
|
||||
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample);
|
||||
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample, (void*)19);
|
||||
ESPUI.addControl(
|
||||
ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
|
||||
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
|
||||
|
@ -60,8 +60,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(int(param)));
|
||||
switch (type)
|
||||
{
|
||||
case B_DOWN:
|
||||
@ -227,7 +228,7 @@ void setup(void)
|
||||
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
||||
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
||||
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.pad("Pad without center", &padExample, ControlColor::Carrot);
|
||||
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
||||
@ -255,7 +256,6 @@ void setup(void)
|
||||
* since it is transmitted in cleartext. Just add a string as username and
|
||||
* password, for example begin("ESPUI Control", "username", "password")
|
||||
*/
|
||||
|
||||
ESPUI.begin("ESPUI Control");
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,12 @@
|
||||
void setup(void)
|
||||
{
|
||||
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()
|
||||
|
@ -54,8 +54,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(int(param)));
|
||||
switch (type)
|
||||
{
|
||||
case B_DOWN:
|
||||
@ -251,7 +252,7 @@ void setup(void)
|
||||
ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1);
|
||||
button1 = ESPUI.addControl(
|
||||
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback);
|
||||
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample);
|
||||
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample, (void*)19);
|
||||
ESPUI.addControl(ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, tab2, &padExample);
|
||||
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample);
|
||||
switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample);
|
||||
|
@ -25,6 +25,7 @@ beginLITTLEFS KEYWORD2
|
||||
print KEYWORD2
|
||||
updateSwitcher KEYWORD2
|
||||
updateSlider KEYWORD2
|
||||
captivePortal LITERAL1
|
||||
|
||||
#######################################
|
||||
# Instances (KEYWORD2)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -13,25 +13,31 @@ src_dir = ./src
|
||||
data_dir = ../../data
|
||||
|
||||
[env]
|
||||
lib_extra_dirs = ../../
|
||||
framework = arduino
|
||||
board_build.filesystem = littlefs
|
||||
lib_extra_dirs = ../../
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson @ ^6.18.5
|
||||
https://github.com/esphome/ESPAsyncWebServer @ 3.0.0 ; Updated lib, seems to have recent patches.
|
||||
|
||||
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
|
||||
;extra_scripts =
|
||||
; LittleFSBuilder.py
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266
|
||||
framework = arduino
|
||||
board = nodemcuv2
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson @ ^6.18.5
|
||||
me-no-dev/ESP Async WebServer @ ^1.2.3
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.flash_mode = dout
|
||||
lib_deps =
|
||||
lorol/LittleFS_esp32@^1.0.6
|
||||
bblanchon/ArduinoJson @ ^6.18.5
|
||||
me-no-dev/ESP Async WebServer @ ^1.2.3
|
||||
${env.lib_deps}
|
||||
me-no-dev/AsyncTCP@1.1.1
|
||||
|
@ -60,8 +60,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(int(param)));
|
||||
switch (type)
|
||||
{
|
||||
case B_DOWN:
|
||||
@ -228,7 +229,7 @@ void setup(void)
|
||||
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
||||
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
||||
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.pad("Pad without center", &padExample, ControlColor::Carrot);
|
||||
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
||||
|
1021
src/ESPUI.cpp
1021
src/ESPUI.cpp
File diff suppressed because it is too large
Load Diff
267
src/ESPUI.h
267
src/ESPUI.h
@ -1,18 +1,29 @@
|
||||
#ifndef ESPUI_h
|
||||
#define ESPUI_h
|
||||
#pragma once
|
||||
|
||||
// comment out to turn off debug output
|
||||
#define DEBUG_ESPUI true
|
||||
#define WS_AUTHENTICATION false
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "stdlib_noniso.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#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)
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <LITTLEFS.h>
|
||||
|
||||
#include "WiFi.h"
|
||||
|
||||
#else
|
||||
@ -21,9 +32,7 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <Hash.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#define FILE_WRITE "w"
|
||||
|
||||
@ -31,158 +40,18 @@
|
||||
|
||||
// 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,
|
||||
Reload = 201,
|
||||
ExtendGUI = 210
|
||||
ExtendGUI = 210,
|
||||
UpdateGui = 220,
|
||||
ExtendedUpdateGui = 230,
|
||||
};
|
||||
|
||||
#define UI_INITIAL_GUI ControlType::InitialGui
|
||||
#define UI_RELOAD ControlType::Reload
|
||||
#define UI_EXTEND_GUI ControlType::ExtendGUI
|
||||
|
||||
#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;
|
||||
};
|
||||
#define UI_INITIAL_GUI MessageTypes::InitialGui
|
||||
#define UI_EXTEND_GUI MessageTypes::ExtendGUI
|
||||
#define UI_RELOAD MessageTypes::Reload
|
||||
|
||||
// Values
|
||||
#define B_DOWN -1
|
||||
@ -224,10 +93,16 @@ public:
|
||||
jsonUpdateDocumentSize = 2000;
|
||||
jsonInitialDocumentSize = 8000;
|
||||
sliderContinuous = false;
|
||||
#ifdef ESP32
|
||||
ControlsSemaphore = xSemaphoreCreateMutex();
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
#endif // def ESP32
|
||||
}
|
||||
unsigned int jsonUpdateDocumentSize;
|
||||
unsigned int jsonInitialDocumentSize;
|
||||
bool sliderContinuous;
|
||||
void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||
bool captivePortal = true;
|
||||
|
||||
void setVerbosity(Verbosity verbosity);
|
||||
void begin(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||
@ -237,31 +112,41 @@ public:
|
||||
void beginLITTLEFS(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||
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
|
||||
void list(); // Lists LITTLEFS directory
|
||||
|
||||
uint16_t addControl(ControlType type, const char* label, const String& value = String(""),
|
||||
ControlColor color = ControlColor::Turquoise, uint16_t parentControl = Control::noParent,
|
||||
void (*callback)(Control*, int) = nullptr);
|
||||
bool removeControl(uint16_t id, bool force_reload_ui = false);
|
||||
uint16_t addControl(ControlType type, const char* label);
|
||||
uint16_t addControl(ControlType type, const char* label, const String& value);
|
||||
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color);
|
||||
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, void (*callback)(Control*, int));
|
||||
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, void (*callback)(Control*, int, void *), void* UserData);
|
||||
|
||||
bool removeControl(uint16_t id, bool force_rebuild_ui = false);
|
||||
|
||||
// create Elements
|
||||
uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color,
|
||||
const String& value = ""); // Create Event Button
|
||||
uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color,
|
||||
bool startState = false); // Create Toggle Button
|
||||
uint16_t pad(const char* label, void (*callback)(Control*, int),
|
||||
ControlColor color); // Create Pad Control
|
||||
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int),
|
||||
ControlColor color); // Create Pad Control with Centerbutton
|
||||
// Create Event Button
|
||||
uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value = "");
|
||||
uint16_t button(const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData);
|
||||
|
||||
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
|
||||
uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color, bool startState = false); // Create Toggle Button
|
||||
uint16_t switcher(const char* label, void (*callback)(Control*, int, void*), ControlColor color, bool startState, void* UserData); // Create Toggle Button
|
||||
|
||||
uint16_t pad(const char* label, void (*callback)(Control*, int), ControlColor color); // Create Pad Control
|
||||
uint16_t pad(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); // Create Pad Control
|
||||
|
||||
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int), ControlColor color); // Create Pad Control with Centerbutton
|
||||
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); // Create Pad Control with Centerbutton
|
||||
|
||||
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 slider(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, int min, int max, void* UserData); // 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 number(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, int min, int max, void* UserData); // 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
|
||||
uint16_t text(const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData); // Create a Text Input Control
|
||||
|
||||
// Output only
|
||||
uint16_t label(const char* label, ControlColor color,
|
||||
@ -273,15 +158,20 @@ public:
|
||||
|
||||
// Input only
|
||||
uint16_t accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color);
|
||||
uint16_t accelerometer(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData);
|
||||
|
||||
// Update Elements
|
||||
|
||||
Control* getControl(uint16_t id);
|
||||
Control* getControlNoLock(uint16_t id);
|
||||
|
||||
// Update Elements
|
||||
void updateControlValue(uint16_t id, 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(Control* control, int clientId = -1);
|
||||
|
||||
@ -313,22 +203,37 @@ public:
|
||||
const char* ui_title = "ESPUI"; // Store UI Title and Header Name
|
||||
Control* controls = nullptr;
|
||||
void jsonReload();
|
||||
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr);
|
||||
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false);
|
||||
|
||||
Verbosity verbosity;
|
||||
|
||||
protected:
|
||||
friend class ESPUIclient;
|
||||
friend class ESPUIcontrol;
|
||||
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t ControlsSemaphore = NULL;
|
||||
#endif // def ESP32
|
||||
|
||||
void RemoveToBeDeletedControls();
|
||||
|
||||
AsyncWebServer* server;
|
||||
AsyncWebSocket* ws;
|
||||
|
||||
private:
|
||||
const char* basicAuthUsername = nullptr;
|
||||
const char* basicAuthPassword = nullptr;
|
||||
bool basicAuth = true;
|
||||
|
||||
uint16_t controlCount = 0;
|
||||
|
||||
void prepareJSONChunk(AsyncWebSocketClient* client, uint16_t startindex, JsonArray* items);
|
||||
#define ClientUpdateType_t ESPUIclient::ClientUpdateType_t
|
||||
void NotifyClients(ClientUpdateType_t newState);
|
||||
void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState);
|
||||
void ClearControlUpdateFlags();
|
||||
|
||||
bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document, uint16_t clientId);
|
||||
|
||||
std::map<uint32_t, ESPUIclient*> MapOfClients;
|
||||
|
||||
};
|
||||
|
||||
extern ESPUIClass ESPUI;
|
||||
#endif
|
||||
|
491
src/ESPUIclient.cpp
Normal file
491
src/ESPUIclient.cpp
Normal file
@ -0,0 +1,491 @@
|
||||
#include "ESPUI.h"
|
||||
#include "ESPUIclient.h"
|
||||
#include "ESPUIcontrol.h"
|
||||
|
||||
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(DynamicJsonDocument& document)
|
||||
{
|
||||
document[F("type")] = UI_EXTEND_GUI;
|
||||
document[F("sliderContinuous")] = ESPUI.sliderContinuous;
|
||||
document[F("startindex")] = 0;
|
||||
document[F("totalcontrols")] = ESPUI.controlCount;
|
||||
JsonArray items = document.createNestedArray(F("controls"));
|
||||
JsonObject titleItem = items.createNestedObject();
|
||||
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::NotifyClient"));
|
||||
break;
|
||||
}
|
||||
|
||||
DynamicJsonDocument 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();
|
||||
|
||||
#ifdef OldWay
|
||||
do // once
|
||||
{
|
||||
// Serial.println(String("ESPUIclient::NotifyClient: State: ") + String(int(newState)));
|
||||
SetState(newState);
|
||||
|
||||
if (HasBeenNotified)
|
||||
{
|
||||
// do not need to do anything
|
||||
break;
|
||||
}
|
||||
|
||||
if(TransferIsInprogress)
|
||||
{
|
||||
// record that a notification was needed while we were transfering data to the client
|
||||
DelayedNotification = true;
|
||||
break;
|
||||
}
|
||||
|
||||
DelayedNotification = false;
|
||||
|
||||
if (SendJsonDocToWebSocket(document))
|
||||
{
|
||||
HasBeenNotified = true;
|
||||
}
|
||||
|
||||
} while (false);
|
||||
|
||||
return HasBeenNotified;
|
||||
#endif // def OldWay
|
||||
}
|
||||
|
||||
// Handle Websockets Communication
|
||||
void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||
{
|
||||
// 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(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck"));
|
||||
pCurrentFsmState->ProcessAck(id);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd.equals(F("uiuok")))
|
||||
{
|
||||
// Serial.println(F("WS_EVT_DATA: uiuok. Unlock new async notifications"));
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// Serial.println(F("ESPUIclient::OnWsEvent:default"));
|
||||
break;
|
||||
}
|
||||
} // end switch
|
||||
}
|
||||
|
||||
/*
|
||||
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,
|
||||
DynamicJsonDocument & rootDoc,
|
||||
bool InUpdateMode)
|
||||
{
|
||||
#ifdef ESP32
|
||||
xSemaphoreTake(ESPUI.ControlsSemaphore, portMAX_DELAY);
|
||||
#endif // def ESP32
|
||||
|
||||
// Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode));
|
||||
int elementcount = 0;
|
||||
|
||||
do // once
|
||||
{
|
||||
// Follow the list until control points to the startindex'th node
|
||||
Control* control = ESPUI.controls;
|
||||
uint32_t currentIndex = 0;
|
||||
JsonArray items = rootDoc[F("controls")];
|
||||
|
||||
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->IsUpdated())
|
||||
{
|
||||
++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())
|
||||
{
|
||||
// Serial.println(String("prepareJSONChunk: Ignoring Deleted control: ") + String(control->id));
|
||||
control = control->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(InUpdateMode)
|
||||
{
|
||||
if(control->IsUpdated())
|
||||
{
|
||||
// dont skip this control
|
||||
}
|
||||
else
|
||||
{
|
||||
// control has not been updated. Skip it
|
||||
control = control->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject item = items.createNestedObject();
|
||||
elementcount++;
|
||||
control->MarshalControl(item, InUpdateMode);
|
||||
|
||||
if (rootDoc.overflowed())
|
||||
{
|
||||
// String("prepareJSONChunk: too much data in the message. Remove the last entry");
|
||||
if (1 == elementcount)
|
||||
{
|
||||
Serial.println(String(F("ERROR: prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser."));
|
||||
rootDoc.clear();
|
||||
item = items.createNestedObject();
|
||||
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
|
||||
{
|
||||
control = control->next;
|
||||
}
|
||||
} // end while (control != nullptr)
|
||||
|
||||
} while (false);
|
||||
|
||||
#ifdef ESP32
|
||||
xSemaphoreGive(ESPUI.ControlsSemaphore);
|
||||
#endif // def ESP32
|
||||
|
||||
// Serial.println(String("prepareJSONChunk: 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)
|
||||
{
|
||||
bool Response = false;
|
||||
// Serial.println(String("ESPUIclient:SendControlsToClient:startidx: ") + String(startidx));
|
||||
do // once
|
||||
{
|
||||
if(!CanSend())
|
||||
{
|
||||
// Serial.println("ESPUIclient:SendControlsToClient: Cannot Send to clients.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (startidx >= ESPUI.controlCount)
|
||||
{
|
||||
// Serial.println("ESPUIclient:SendControlsToClient: No more controls to send.");
|
||||
Response = true;
|
||||
break;
|
||||
}
|
||||
|
||||
DynamicJsonDocument document(ESPUI.jsonInitialDocumentSize);
|
||||
FillInHeader(document);
|
||||
document[F("startindex")] = startidx;
|
||||
document[F("totalcontrols")] = 65534; // 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;
|
||||
}
|
||||
// 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))
|
||||
{
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||
{
|
||||
Serial.println(F("ESPUIclient:SendControlsToClient: Sending elements --------->"));
|
||||
String json;
|
||||
serializeJson(document, json);
|
||||
Serial.println(json);
|
||||
}
|
||||
#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(DynamicJsonDocument& 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;
|
||||
json.reserve(document.size() / 2);
|
||||
json.clear();
|
||||
serializeJson(document, json);
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
62
src/ESPUIclient.h
Normal file
62
src/ESPUIclient.h
Normal file
@ -0,0 +1,62 @@
|
||||
#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::DynamicJsonDocument& document);
|
||||
uint32_t prepareJSONChunk(uint16_t startindex, DynamicJsonDocument& rootDoc, bool InUpdateMode);
|
||||
bool SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode);
|
||||
|
||||
bool SendClientNotification(ClientUpdateType_t value);
|
||||
|
||||
public:
|
||||
ESPUIclient(AsyncWebSocketClient * _client);
|
||||
ESPUIclient(const ESPUIclient & source);
|
||||
virtual ~ESPUIclient();
|
||||
void NotifyClient(ClientUpdateType_t value);
|
||||
void 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::DynamicJsonDocument& document);
|
||||
};
|
107
src/ESPUIclientFsm.cpp
Normal file
107
src/ESPUIclientFsm.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#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)
|
||||
{
|
||||
// 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"));
|
||||
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)
|
||||
{
|
||||
// Serial.println(F("fsm_EspuiClient_state_SendingUpdate: ProcessAck"));
|
||||
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded))
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
//----------------------------------------------
|
||||
//----------------------------------------------
|
||||
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)
|
||||
{
|
||||
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: ProcessAck"));
|
||||
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::RebuildNeeded))
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
79
src/ESPUIclientFsm.h
Normal file
79
src/ESPUIclientFsm.h
Normal file
@ -0,0 +1,79 @@
|
||||
#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) = 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 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 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() {}
|
||||
|
||||
virtual bool NotifyClient();
|
||||
virtual void ProcessAck(uint16_t id);
|
||||
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() {}
|
||||
|
||||
virtual bool NotifyClient() { return false; }
|
||||
virtual void ProcessAck(uint16_t) {}
|
||||
String GetStateName() { return String(F("Reloading")); }
|
||||
|
||||
}; // fsm_EspuiClient_state_Reloading
|
||||
|
251
src/ESPUIcontrol.cpp
Normal file
251
src/ESPUIcontrol.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#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, void (*callback)(Control*, int, void*), void* UserData,
|
||||
const String& value, ControlColor color, bool visible, uint16_t parentControl)
|
||||
: type(type),
|
||||
label(label),
|
||||
callback(nullptr),
|
||||
extendedCallback(callback),
|
||||
user(UserData),
|
||||
value(value),
|
||||
color(color),
|
||||
visible(visible),
|
||||
wide(false),
|
||||
vertical(false),
|
||||
enabled(true),
|
||||
parentControl(parentControl),
|
||||
next(nullptr)
|
||||
{
|
||||
id = ++idCounter;
|
||||
}
|
||||
|
||||
Control::Control(const Control& Control)
|
||||
: type(Control.type),
|
||||
id(Control.id),
|
||||
label(Control.label),
|
||||
callback(Control.callback),
|
||||
extendedCallback(Control.extendedCallback),
|
||||
user(Control.user),
|
||||
value(Control.value),
|
||||
color(Control.color),
|
||||
visible(Control.visible),
|
||||
parentControl(Control.parentControl),
|
||||
next(Control.next)
|
||||
{ }
|
||||
|
||||
void Control::SendCallback(int type)
|
||||
{
|
||||
if(callback)
|
||||
{
|
||||
callback(this, type);
|
||||
}
|
||||
|
||||
if (extendedCallback)
|
||||
{
|
||||
extendedCallback(this, type, user);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::DeleteControl()
|
||||
{
|
||||
ControlSyncState = ControlSyncState_t::deleted;
|
||||
extendedCallback = nullptr;
|
||||
callback = nullptr;
|
||||
}
|
||||
|
||||
void Control::MarshalControl(JsonObject & item, bool refresh)
|
||||
{
|
||||
item[F("id")] = id;
|
||||
if(refresh)
|
||||
{
|
||||
item[F("type")] = uint32_t(type) + uint32_t(ControlType::UpdateOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
item[F("type")] = uint32_t(type);
|
||||
}
|
||||
item[F("label")] = label;
|
||||
item[F("value")] = value;
|
||||
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")] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
132
src/ESPUIcontrol.h
Normal file
132
src/ESPUIcontrol.h
Normal file
@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
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,
|
||||
|
||||
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;
|
||||
void (*callback)(Control*, int);
|
||||
void (*extendedCallback)(Control*, int, void*);
|
||||
void* user;
|
||||
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, void*),
|
||||
void* UserData,
|
||||
const String& value,
|
||||
ControlColor color,
|
||||
bool visible,
|
||||
uint16_t parentControl);
|
||||
|
||||
Control(const Control& Control);
|
||||
|
||||
void SendCallback(int type);
|
||||
bool HasCallback() { return ((nullptr != callback) || (nullptr != extendedCallback)); }
|
||||
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh);
|
||||
void MarshalErrorMessage(ArduinoJson::JsonObject& item);
|
||||
bool ToBeDeleted() { return (ControlSyncState_t::deleted == ControlSyncState); }
|
||||
void DeleteControl();
|
||||
bool IsUpdated() { return ControlSyncState_t::synchronized != ControlSyncState; }
|
||||
void HasBeenUpdated() { ControlSyncState = ControlSyncState_t::updated; }
|
||||
void HasBeenSynchronized() {ControlSyncState = ControlSyncState_t::synchronized;}
|
||||
void onWsEvent(String& cmd, String& data);
|
||||
|
||||
private:
|
||||
enum ControlSyncState_t
|
||||
{
|
||||
synchronized = 0,
|
||||
updated,
|
||||
deleted,
|
||||
};
|
||||
ControlSyncState_t ControlSyncState = ControlSyncState_t::synchronized;
|
||||
};
|
||||
|
||||
#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
@ -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);}
|
||||
)=====";
|
||||
|
||||
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[1245] PROGMEM = { 31,139,8,0,210,8,51,99,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 };
|
||||
|
@ -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>
|
||||
)=====";
|
||||
|
||||
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,210,8,51,99,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 };
|
||||
|
@ -2,4 +2,4 @@ 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}
|
||||
)=====";
|
||||
|
||||
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[861] PROGMEM = { 31,139,8,0,210,8,51,99,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 };
|
||||
|
@ -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);}}
|
||||
)=====";
|
||||
|
||||
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,210,8,51,99,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