diff --git a/README.md b/README.md
index 20ee741..7cfed9b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# ESPUI
+# ESPUI (v2.X)
![ESPUI](https://github.com/s00500/ESPUI/blob/master/docs/ui_complete.png)
@@ -14,71 +14,90 @@ So if you either don't know how or just don't want to waste time: this is your
simple solution user interface without the need of internet connectivity or any
additional servers.
-I completely rewrote the EasyUI Library created by ayushsharma82
-[Here](https://github.com/ayushsharma82/) Now it uses ESPAsyncWebserver and is
-mainly to be used with the ESP32 Processor.
+The Library runs fine on any kind of **ESP8266** and **ESP32** (NodeMCU Boards, usw)
-# Important notes
+## Changelog for 2.0:
-Currently ESPUI only supports ArduinoJSON 5.x, please keep that in mind! Version
-6 support is work in progress
+- ArduinoJSON 6.10.0 Support
+- split pad into pad and padWithCenter
+- Cleaned Order or parameters on switch
+- cleaned Order of parameters on pad
+- Changes all numbers to actually be numbers (slider value, number value, min and max)
+
+### Added features
+
+- Tabs by @eringerli #45
+- Generic API by @eringerli
+- Min Max on slider by @eringerli
+- OptionList by @eringerli
+- Public Access to ESPAsyncServer
+- Graph Widget (Persist save graph in local storage #10)
+
+## Further Roadmap
+
+- Slider css issues
+- implement Gauge
+- File upload ?
## Dependencies
This library is dependent on the following libraries to function properly.
- [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer)
-- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) **(VERSIONS 5.x only
- currently)**
+- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) (Last tested with
+ version 6.10.0)
-**Plus for ESP8266**
-
-- [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP)
-
-**Additionally necessary for ESP32**
-
-- [AsyncTCP](https://github.com/me-no-dev/AsyncTCP)
+- (_For ESP8266_) [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP)
+- (_For ESP32_) [AsyncTCP](https://github.com/me-no-dev/AsyncTCP)
## How to Install
Make sure all the dependencies are installed, then install like so:
+#### Using PlattformIO (_recommended_)
+
+Just include this library as a dependency on lib_deps like so:
+
+```
+lib_deps =
+ ESPUI
+ ESPAsyncWebserver
+ ESPAsyncTCP # or AsyncTCP on ESP32
+```
+
#### Directly Through Arduino IDE (_recommended_)
You can find this Library in the Arduino IDE library manager Go to Sketch >
Include Library > Library Manager > Search for "ESPUI" > Install
-#### Manual Install
+#### Manual Install Arduino IDE
-For Windows: Download the
+_For Windows:_ Download the
[Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the
.zip in Documents>Arduino>Libraries>{Place "ESPUI" folder Here}
-For Linux: Download the
+_For Linux:_ Download the
[Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the
.zip in Sketchbook/Libraries/{Place "ESPUI" folder Here}
-For macOs: Download the
+_For macOs:_ Download the
[Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the
.zip in ~/Documents/Arduino/libraries/{Place "ESPUI" folder Here}
-#### Manually through IDE
-
-Download the [Repository](https://github.com/s00500/ESPUI/archive/master.zip),
Go to Sketch>Include Library>Add .zip Library> Select the Downloaded .zip File.
## Getting started
-ESPUI serves several files to the browser to build up its webinterface. This can
-be achieved in 2 ways: _PROGMEM_ or _SPIFFS_
+ESPUI serves several files to the browser to build up its web interface. This
+can be achieved in 2 ways: _PROGMEM_ or _SPIFFS_
_When `ESPUI.begin()` is called the default is serving files from Memory and
ESPUI should work out of the box!_
-But if this causes your program to _use too much memory_ you can burn the files
-into the SPIFFS filesystem on the ESP. There are now two ways to do this: you
-can either use the ESP file upload tool or you use the library function
-`ESPUI.prepareFileSystem()`
+**OPTIONAL:** But if this causes your program to _use too much memory_ you can
+burn the files into the SPIFFS filesystem on the ESP. There are now two ways to
+do this: you can either use the ESP file upload tool or you use the library
+function `ESPUI.prepareFileSystem()`
#### Simple filesystem preparation (_recommended_)
@@ -88,58 +107,37 @@ will create all needed files. Congratulations, you are done, from now on you
just need to to this again when there is a library update, or when you want to
use another chip :-) Now you can upload your normal sketch, when you do not call
the `ESPUI.prepareFileSystem()` function the compiler will strip out all the
-unnecessary that is already saved in the chip's filesystem and you have more
-programm memory to work with.
-
-#### Manual way (mainly for development)
-
-To do this download and install me-no-devs wonderful
-[ESP32 sketch data uploader](https://github.com/me-no-dev/arduino-esp32fs-plugin)
-or for ESP8266
-[ESP8266 sketch data uploader](https://github.com/esp8266/arduino-esp8266fs-plugin)
-
-Then open the **gui** example sketch and select "Upload Sketch Data" from the
-Tools menu for your processor. Now you are set to go and use any code you want
-to with this library
+unnecessary strings that are already saved in the chip's filesystem and you have
+more program memory to work with.
## User interface Elements
-- Label (updateable)
+- Label
- Button
-- Switch (updateable)
+- Switch
- Control pad
- Control pad with center button
- Slider
-- Text Input (updateable)
-- Numberinput (updateable)
+- Text Input
+- Numberinput
+- Graph
+- Option select
-Checkout the example for the usage
+Checkout the example for the usage or see the detailed info below
## Available colors:
-- COLOR_TURQUOISE
-- COLOR_EMERALD
-- COLOR_PETERRIVER
-- COLOR_WETASPHALT
-- COLOR_SUNFLOWER
-- COLOR_CARROT
-- COLOR_ALIZARIN
-- COLOR_NONE
+- Turquoise
+- Emerald
+- Peterriver
+- Wetasphalt
+- Sunflower
+- Carrot
+- Alizarin
+- Dark
+- None
-## Roadmap :
-
-- ~~Setup SPIFFS using values in program memory~~
-- ~~ESP8266 support~~
-- ~~PlattformIO Integration~~
-- ~~Multiline Labels~~
-- ~~GZip Files and serve from memory~~
-- Datagraph output -> _WIP_
-- ~~Number input ~~
-- ~~Text input ~~
-- Dokumentation for Text and number widget
-- Number min and max value
-- proper return value (as int and not as string) for slider
-- Maybe a slider range setting, meanwhile please use _map()_
+(Use like `ControlColor::Sunflower`)
## Documentation
@@ -147,17 +145,15 @@ 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 Click Events Etc. The
-communication between the _ESP32_ and the client browser works using web
+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 assets are loaded from the internal SPIFFS filesystem of the
-ESP32.
+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. As of now the Facilino blocks are not implemented. In the
-arduino setup() routine the interface can be customised by adding UI Elements.
+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” function when changed. All buttons and
+`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
@@ -169,8 +165,11 @@ of the UI library:
![Buttons](https://github.com/s00500/ESPUI/blob/master/docs/ui_button.png)
-Buttons have a name and a callback value. They have one event for press and one
-for release.
+Buttons have a name and a callback value. They have one event for press (`B_DOWN`) and one
+for release (`B_UP`).
+
+- B_DOWN
+- B_UP
#### Switch
@@ -178,8 +177,11 @@ for release.
Switches sync their state on all connected devices. This means when you change
their value they change visibly on all tablets or computers that currently
-display the interface. They also have two types of events: one for turning on
-and one for turning off.
+display the interface. They also have two types of events: one when turning on (`S_ACTIVE`)
+and one when turning off (`S_INACTIVE`).
+
+- S_ACTIVE
+- S_INACTIVE
#### Buttonpad
@@ -190,13 +192,24 @@ useful for con-trolling all kinds of movements of vehicles or also of course our
walking robots. They use a single callback per pad and have 8 or 10 different
event types to differentiate the button actions.
+- P_LEFT_DOWN
+- P_LEFT_UP
+- P_RIGHT_DOWN
+- P_RIGHT_UP
+- P_FOR_DOWN
+- P_FOR_UP
+- P_BACK_DOWN
+- P_BACK_UP
+- P_CENTER_DOWN
+- P_CENTER_UP
+
#### Labels
![labels](https://github.com/s00500/ESPUI/blob/master/docs/ui_labels.png)
Labels are a nice tool to get information from the robot to the user interface.
This can be done to show states, values of sensors and configuration parameters.
-To send data from the code use `ESP.print(labelId, “Text”);` . Labels get a name
+To send data from the code use `ESP.print(labelId, "Text");` . Labels get a name
on creation and a initial value. The name is not changeable once the UI
initialised.
@@ -205,36 +218,140 @@ the normal ` ` tag in the string you print to the label
#### Slider
-![labels](https://github.com/s00500/ESPUI/blob/master/docs/ui_slider.png)
+![slider](https://github.com/s00500/ESPUI/blob/master/docs/ui_slider.png)
The Slider can be used to slide through a value from 1 to 100. Slides provide
realtime data, are touch compatible and can be used to for example control a
Servo. The current value is shown while the slider is dragged in a little bubble
-over the handle.
+over the handle. In the Callback the slider does not return an int but a String.
+Use the .toInt function to convert the value, see the **gui** example to check how it works.
-#### Initialisation of the UI
+A slider usually only sends a new value when it is released to save the esps from being spammed with values. This behaviour can be cahnged globally using a property of the ESPUI object before `begin()`:
-After all the elements are configured you can use `ESPUI.begin(“Some Title”);`
-to start the UI interface. (Or `ESPUI.beginSPIFFS(“Some Title”);` respectively)
+```
+ ESPUI.sliderContinuous = true;
+ ESPUI.begin("ESPUI Control");
+```
+
+#### Number Input
+
+![number](https://github.com/s00500/ESPUI/blob/master/docs/ui_number.png)
+
+The numberinput can be used to directly input numbers to your program. You can
+enter a Value into it and when you are done with your change it is sent to the
+ESP.
+
+A number box needs to have a min and a max value. To set it up just use:
+
+`ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);`
+
+#### Text Input
+
+![text](https://github.com/s00500/ESPUI/blob/master/docs/ui_text.png)
+
+The textinput works very similar like the number input but with a string. You
+can enter a String into it and when you are done with your change it is sent to
+the ESP.
+
+#### Graph
+
+![graph](https://github.com/s00500/ESPUI/blob/master/docs/ui_graph.png)
+
+The graph widget can display graph points with timestamp at wich they arrive
+
+Use `ESPUI.addGraphPoint(graphId, random(1, 50));` to add a new value at the current time, use `ESPUI.clearGraph(graphId)` to clear the entire graph.
+Graph points are saved in the browser in **localstorage** to be persistant, clear local storageto remove the points or use clearGraph() from a bbutton callback to provide a clear button.
+
+#### Option select
+
+![option1](https://github.com/s00500/ESPUI/blob/master/docs/ui_select1.png)
+![option2](https://github.com/s00500/ESPUI/blob/master/docs/ui_select2.png)
+
+The option select works by first creating a select widget like so
+
+`uint16_t select1 = ESPUI.addControl( ControlType::Select, "Select:", "", ControlColor::Alizarin, tab1, &selectExample );`
+
+And then adding Options to it like seperate widgets, specifying the select as the parent:
+
+```
+ESPUI.addControl( ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1 );
+ESPUI.addControl( ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1 );
+ESPUI.addControl( ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1 );
+```
+
+Check the **tabbedGui** example for a working demo
+
+### Using Tabs
+
+![tabs](https://github.com/s00500/ESPUI/blob/master/docs/ui_tabs.png)
+
+Tabs can be used to organize your widgets in pages. Check the tabbedGui example.
+Tabs can be created using the generic functions like so:
+`ESPUI.addControl( ControlType::Tab, "Settings 1", "Settings 1" );`
+
+Then all widgets for the tab need to be added to it by specifying the tab as the parrent (widgets not added to a tab will be shown above the tab selctor)
+
+`ESPUI.addControl( ControlType::Text, "Text Title:", "a Text Field", ControlColor::Alizarin, tab1, &textCall );`
+
+### Initialisation of the UI
+
+After all the elements are configured you can use `ESPUI.begin("Some Title");`
+to start the UI interface. (Or `ESPUI.beginSPIFFS("Some Title");` respectively)
Make sure you setup a working network connection or AccesPoint **before** (See
-example). The web interface can then be used from multiple devices at once and
-also shows an connection status in the top bar. The library is designed to be
-easy to use and can still be extended with a lot of more functionality.
+gui.ino example). The web interface can then be used from multiple devices at once and
+also shows an connection status in the top bar.
+
+### Advanced: Generic creation and updates of control widgets
+
+There are 2 generic functions to create and update controls, to see them in action check the **gui-generic-api** example.
+
+To create a generic control use:
+`uint16_t switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switchExample);`
+
+Then its value can be updated by doing:
+
+`ESPUI.updateControlValue(status, "Start");`
+
+You can also update other parameters of the control like its color using:
+
+```
+ ESPUI.getControl(switchOne)->color = ControlColor::Carrot;
+ ESPUI.updateControl(switchOne);
+```
+
+### Log output
+
+ESPUI has several different log levels. You can set them using the
+`ESPUI.setVerbosity(Verbosity::VerboseJSON)` function.
+
+Loglevels are:
+
+- `Verbosity::Quiet` (default)
+- `Verbosity::Verbose`
+- `Verbosity::VerboseJSON`
+
+VerboseJSON outputs the most debug information.
+
+### Advanced properties
+
+If you have many different widgets it might be necessary to adjust the JSON Buffers used internally in ESPUI before .begin() :
+
+```
+ ESPUI.jsonUpdateDocumentSize = 2000; // This is the default, and this value is not affected by the amount of widgets
+ ESPUI.jsonInitialDocumentSize = 8000; // This is the default, adjust when you have too many widgets or options
+ ESPUI.begin("ESPUI Control");
+```
# Notes for Development
-If you want to work on the HTML/CSS/JS files, do make changes in the
-`examples/gui/data` directory. When you need to transfer that code to the ESP,
-run `tools/prepare_static_ui_sources.py -a` (this script needs python3 with the
-modules htmlmin, jsmin and csscompressor). This will generate a) minified files
-next to the original files to be uploaded with the ESP32 sketch data uploader
-mentioned above and b) the C header files in `src` that contain the minified and
-gzipped HTML/CSS/JS data (which are used by the **prepareFileSystem** example
-sketch or when they are served from PROGMEM; see above in the section "Getting
-started"). Alternatively, you can duplicate the `examples/gui` directory and
-work on the copy. Then specify the `--source` and `--target` arguments to the
+If you want to work on the HTML/CSS/JS files, do make changes in the _data_
+directory. When you need to transfer that code to the ESP, run
+`tools/prepare_static_ui_sources.py -a` (this script needs **python3** with the
+modules **htmlmin**, **jsmin** and **csscompressor**). This will generate a) minified files
+next to the original files and b) the C header files in `src` that contain the minified and
+gzipped HTML/CSS/JS data. Alternatively, you can specify the `--source` and `--target` arguments to the
`prepare_static_ui_sources.py` script (run the script without arguments for
-help).
+help) if you want to use different locations.
If you don't have a python environment, you need to minify and gzip the
HTML/CSS/JS files manually. I wrote a little useful jsfiddle for this,
@@ -242,9 +359,11 @@ HTML/CSS/JS files manually. I wrote a little useful jsfiddle for this,
If you change something in HTML/CSS/JS and want to create a pull request, please
do include the minified versions and corresponding C header files in your
-commits.
+commits. (Do **NOT** commit all the minified versions for the non changed files)
# Contribute
Liked this Library? You can **support** me by sending me a :coffee:
-[Coffee](https://paypal.me/lukasbachschwell/3).
+[Coffee](https://paypal.me/lukasbachschwell/5).
+
+Otherwise I really welcome **Pull Requests**.
diff --git a/examples/gui/data/css/normalize.css b/data/css/normalize.css
similarity index 100%
rename from examples/gui/data/css/normalize.css
rename to data/css/normalize.css
diff --git a/examples/gui/data/css/normalize.min.css b/data/css/normalize.min.css
similarity index 100%
rename from examples/gui/data/css/normalize.min.css
rename to data/css/normalize.min.css
diff --git a/examples/gui/data/css/style.css b/data/css/style.css
similarity index 84%
rename from examples/gui/data/css/style.css
rename to data/css/style.css
index 6721a93..7cb162f 100644
--- a/examples/gui/data/css/style.css
+++ b/data/css/style.css
@@ -18,10 +18,16 @@
padding-left: 20px;
padding-right: 20px;
margin-bottom: 10px;
- min-width: 150px;
+ min-width: 500px;
color: #fff;
}
+@media (max-width: 630px) {
+ .card {
+ min-width: 98%;
+ }
+}
+
.card-slider {
padding-bottom: 10px;
}
@@ -873,6 +879,17 @@ input {
background: rgba(255, 255, 255, 0.8);
}
+select {
+ margin: 0 auto 1.2rem auto;
+ padding: 2px 5px;
+ width: 100%;
+ box-sizing: border-box;
+ border: none;
+ border-radius: 4px;
+ box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+ background: rgba(255, 255, 255, 0.8);
+}
+
input[id^="num"] {
max-width: 6em;
width: auto;
@@ -880,3 +897,164 @@ input[id^="num"] {
font-weight: bold;
font-size: 115%;
}
+
+body div > ul.navigation {
+ margin: 0;
+ padding: 0;
+ border-bottom: 3px solid #666;
+ overflow: hidden;
+}
+ul.navigation li {
+ list-style: none;
+ float: left;
+ margin-right: 4px;
+}
+ul.navigation li.controls {
+ float: right;
+}
+ul.navigation li a {
+ font-weight: bold;
+ display: inline-block;
+ padding: 6px 12px;
+ color: #888;
+ outline: 0;
+ text-decoration: none;
+ background: #f3f3f3;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(#eee), to(#e4e4e4));
+ background: -moz-linear-gradient(#eee, #e4e4e4);
+ background: linear-gradient(#eee, #e4e4e4);
+ -pie-background: linear-gradient(#eee, #e4e4e4);
+}
+
+ul.navigation li.active a {
+ pointer-events: none;
+ color: white;
+ background: #666;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(#888), to(#666));
+ background: -moz-linear-gradient(#888, #666);
+ background: linear-gradient(#888, #666);
+ -pie-background: linear-gradient(#888, #666);
+}
+
+div.tabscontent > div {
+ padding: 0 15px;
+}
+
+#tabsnav:empty {
+ display: none;
+}
+
+.range-slider {
+ margin: 0 0 0 0;
+}
+.range-slider {
+ width: 100%;
+}
+.range-slider__range {
+ -webkit-appearance: none;
+ width: calc(100% - (45px));
+ height: 10px;
+ border-radius: 5px;
+ outline: 0;
+ padding: 0;
+ margin: 0;
+}
+.range-slider__range::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: background 0.15s ease-in-out;
+}
+.range-slider__range::-webkit-slider-thumb:hover {
+ background: #1abc9c;
+}
+.range-slider__range:active::-webkit-slider-thumb {
+ background: #1abc9c;
+}
+.range-slider__range::-moz-range-thumb {
+ width: 20px;
+ height: 20px;
+ border: 0;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: background 0.15s ease-in-out;
+}
+.range-slider__range:focus::-webkit-slider-thumb {
+ box-shadow: 0 0 0 3px #fff, 0 0 0 6px #1abc9c;
+}
+.range-slider__value {
+ display: inline-block;
+ position: relative;
+ width: 30px;
+ color: #fff;
+ line-height: 20px;
+ text-align: center;
+ border-radius: 3px;
+ padding: 5px 5px;
+ margin-left: 2px;
+}
+.range-slider__value:after {
+ position: absolute;
+ top: 8px;
+ left: -7px;
+ width: 0;
+ height: 0;
+ /*border-top:1px solid transparent;
+ border-right:1px solid #2c3e50;
+ border-bottom:1px solid transparent;*/
+ content: "";
+}
+::-moz-range-track {
+ border: 0;
+}
+input::-moz-focus-inner,
+input::-moz-focus-outer {
+ border: 0;
+}
+
+/* Styles for Graph widget */
+
+svg {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.y-axis path,
+.x-axis path {
+ stroke: gray;
+ stroke-width: 1;
+ fill: none;
+}
+
+.series {
+ stroke: steelblue;
+ stroke-width: 3;
+ fill: none;
+}
+
+.data-points circle {
+ stroke: steelblue;
+ stroke-width: 2;
+ fill: white;
+}
+
+.data-points text {
+ display: none;
+}
+
+.data-points circle:hover {
+ fill: steelblue;
+ stroke-width: 6;
+}
+
+.data-points circle:hover + text {
+ display: inline-block;
+}
+
+text {
+ text-anchor: end;
+}
diff --git a/data/css/style.min.css b/data/css/style.min.css
new file mode 100644
index 0000000..9ff9b42
--- /dev/null
+++ b/data/css/style.min.css
@@ -0,0 +1 @@
+.container{position:relative;width:79%;margin:20px;box-sizing:border-box}.column,.columns{width:100%;float:left}.card{margin-top:2%;border-radius:6px;box-shadow:0 4px 4px rgba(204,197,185,0.5);padding-left:20px;padding-right:20px;margin-bottom:10px;min-width:500px;color:#fff}@media(max-width:630px){.card{min-width:98%}}.card-slider{padding-bottom:10px}.turquoise{background:#1abc9c;border-bottom:#16a085 3px solid}.emerald{background:#2ecc71;border-bottom:#27ae60 3px solid}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.dark{background:#444857;border-bottom:#444857 3px solid}.label{box-sizing:border-box;white-space:nowrap;border-radius:.2em;padding:.12em .4em .14em;text-align:center;color:#fff;font-weight:700;line-height:1;margin-bottom:5px;display:inline-block;white-space:nowrap;vertical-align:baseline;position:relative;top:-.15em;background-color:#999;margin-bottom:10px}.label-wrap{width:90%;white-space:pre-wrap;word-wrap:break-word}.label.color-blue{background-color:#6f9ad1}.label.color-red{background-color:#d37c7c}.label.color-green{background-color:#9bc268}.label.color-orange{background-color:#dea154}.label.color-yellow{background-color:#e9d641}.label.color-purple{background-color:#9f83d1}@media(min-width:400px){.container{width:84%}}@media(min-width:630px){.container{width:98%}.column,.columns{margin-right:2%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.66666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.66666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}html{font-size:62.5%}body{margin:0;font-size:1.5em;line-height:1;font-weight:400;font-family:"Open Sans",sans-serif;color:#222;background-color:#ecf0f1}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media(min-width:630px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2rem}h6{font-size:1.5rem}}p{margin-top:0}a{color:#1eaedb}a:hover{color:#0fa0ce}button{display:inline-block;padding:10px;border-radius:3px;color:#fff;background-color:#999}#mainHeader{display:inline-block}#conStatus{position:inherit;font-size:.75em}button,.button{margin-bottom:1rem}.u-full-width{width:100%;box-sizing:border-box}.u-max-full-width{max-width:100%;box-sizing:border-box}.u-pull-right{float:right}.u-pull-left{float:left}.tcenter{text-align:center}hr{margin-top:.5rem;margin-bottom:1.2rem;border-width:0;border-top:1px solid #e1e1e1}.container:after,.row:after,.u-cf{content:"";display:table;clear:both}.control{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.1),hsla(0,0%,100%,0.1));border-radius:50%;box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.75),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.75);height:9em;margin:3em auto;position:relative;width:9em}.control ul{height:100%;padding:0;transform:rotate(45deg)}.control li{border-radius:100% 0 0 0;box-shadow:inset -1px -1px 1px hsla(0,0%,100%,0.5),0 0 1px hsla(0,0%,0%,0.75);display:inline-block;height:50%;overflow:hidden;width:50%}.control ul li:nth-child(2){transform:rotate(90deg)}.control ul li:nth-child(3){transform:rotate(-90deg)}.control ul li:nth-child(4){transform:rotate(180deg)}.control ul a{height:200%;position:relative;transform:rotate(-45deg);width:200%}.control a:hover,.control a:focus{background-color:hsla(0,0%,100%,0.25)}.control a{border-radius:50%;color:#333;display:block;font:bold 1em/3 sans-serif;text-align:center;text-decoration:none;text-shadow:0 1px 1px hsla(0,0%,100%,0.4);transition:.15s}.control .confirm{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.15),hsla(0,0%,100%,0.25));box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.25),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.85);left:50%;line-height:3;margin:-1.5em;position:absolute;top:50%;width:3em}.control .confirm:hover,.control .confirm:focus{background-color:#eee}.switch{display:inline-block !important;background-color:#bebebe;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);color:#fff;cursor:pointer;display:block;font-size:14px;height:26px;margin-bottom:12px;position:relative;width:60px;-webkit-transition:background-color .2s ease-in-out;-moz-transition:background-color .2s ease-in-out;-o-transition:background-color .2s ease-in-out;-ms-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.switch.checked{background-color:#76d21d}.switch input[type="checkbox"]{display:none;cursor:pointer;height:10px;left:12px;position:absolute;top:8px;width:10px}.in{position:absolute;top:8px;left:12px;-webkit-transition:left .08s ease-in-out;-moz-transition:left .08s ease-in-out;-o-transition:left .08s ease-in-out;-ms-transition:left .08s ease-in-out;transition:left .08s ease-in-out}.switch.checked div{left:38px}.switch .in:before{background:#fff;background:-moz-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#f0f0f0));background:-webkit-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-o-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-ms-linear-gradient(top,#fff 0,#f0f0f0 100%);background:linear-gradient(to bottom,#fff 0,#f0f0f0 100%);border:1px solid #fff;border-radius:2px;box-shadow:0 0 4px rgba(0,0,0,0.3);content:"";height:18px;position:absolute;top:-5px;left:-9px;width:26px}.switch .in:after{background:#f0f0f0;background:-moz-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f0f0f0),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-o-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-ms-linear-gradient(top,#f0f0f0 0,#fff 100%);background:linear-gradient(to bottom,#f0f0f0 0,#fff 100%);border-radius:10px;content:"";height:12px;margin:-1px 0 0 -1px;position:absolute;width:12px}.rkmd-slider{display:block;position:relative;font-size:16px;font-family:"Roboto",sans-serif}.rkmd-slider input[type="range"]{overflow:hidden;position:absolute;width:1px;height:1px;opacity:0}.rkmd-slider input[type="range"]+.slider{display:block;position:relative;width:100%;height:27px;border-radius:13px;background-color:#bebebe}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider{height:4px;border-radius:0}}.rkmd-slider input[type="range"]+.slider .slider-fill{display:block;position:absolute;width:0;height:100%;user-select:none;z-index:1}.rkmd-slider input[type="range"]+.slider .slider-handle{cursor:pointer;position:absolute;top:12px;left:0;width:15px;height:15px;margin-left:-8px;border-radius:50%;transition:all .2s ease;user-select:none;z-index:2}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider .slider-handle{top:-5.5px}}.rkmd-slider input[type="range"]:disabled+.slider{background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle{cursor:default !important;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle .slider-label{display:none;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active{top:-5.5px;width:15px;height:15px;margin-left:-8px}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active .slider-label{display:none;border-radius:50%;transform:none}.rkmd-slider input[type="range"]:disabled+.slider .slider-handle:active{box-shadow:none !important;transform:scale(1) !important}.rkmd-slider.slider-discrete .slider .slider-handle{position:relative;z-index:1}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{position:absolute;top:-17.5px;left:4px;width:30px;height:30px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;border-radius:50%;-webkit-transform:scale(1) rotate(-45deg);transform:scale(1) rotate(-45deg);-webkit-transition:all .2s ease;transition:all .2s ease}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{left:-2px;-webkit-transform:scale(0.5) rotate(-45deg);transform:scale(0.5) rotate(-45deg)}}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{position:absolute;top:7px;left:0;width:100%;color:#fff;font-size:16px;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{font-size:12px}}.rkmd-slider.slider-discrete .slider .slider-handle.is-active{top:0;margin-left:-2px;width:4px;height:4px}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label{top:-15px;left:-2px;border-radius:15px 15px 15px 0;-webkit-transform:rotate(-45deg) translate(23px,-25px);transform:rotate(-45deg) translate(23px,-25px)}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label span{opacity:1}.rkmd-slider.slider-discrete.slider-turquoise .slider-label{background-color:#16a085}.rkmd-slider.slider-discrete.slider-emerald .slider-label{background-color:#27ae60}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.rkmd-slider.slider-discrete.slider-peterriver .slider-label{background-color:#2980b9}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.rkmd-slider.slider-discrete.slider-wetasphalt .slider-label{background-color:#2c3e50}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.rkmd-slider.slider-discrete.slider-sunflower .slider-label{background-color:#e6bb0f}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.rkmd-slider.slider-discrete.slider-carrot .slider-label{background-color:#d35400}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.rkmd-slider.slider-discrete.slider-alizarin .slider-label{background-color:#c0392b}input{margin:0 auto 1.2rem auto;padding:2px 5px;width:100%;box-sizing:border-box;border:0;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background:rgba(255,255,255,0.8)}select{margin:0 auto 1.2rem auto;padding:2px 5px;width:100%;box-sizing:border-box;border:0;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background:rgba(255,255,255,0.8)}input[id^="num"]{max-width:6em;width:auto;text-align:right;font-weight:bold;font-size:115%}body div>ul.navigation{margin:0;padding:0;border-bottom:3px solid #666;overflow:hidden}ul.navigation li{list-style:none;float:left;margin-right:4px}ul.navigation li.controls{float:right}ul.navigation li a{font-weight:bold;display:inline-block;padding:6px 12px;color:#888;outline:0;text-decoration:none;background:#f3f3f3;background:-webkit-gradient(linear,0 0,0 bottom,from(#eee),to(#e4e4e4));background:-moz-linear-gradient(#eee,#e4e4e4);background:linear-gradient(#eee,#e4e4e4);-pie-background:linear-gradient(#eee,#e4e4e4)}ul.navigation li.active a{pointer-events:none;color:white;background:#666;background:-webkit-gradient(linear,0 0,0 bottom,from(#888),to(#666));background:-moz-linear-gradient(#888,#666);background:linear-gradient(#888,#666);-pie-background:linear-gradient(#888,#666)}div.tabscontent>div{padding:0 15px}#tabsnav:empty{display:none}.range-slider{margin:0}.range-slider{width:100%}.range-slider__range{-webkit-appearance:none;width:calc(100% - (45px));height:10px;border-radius:5px;outline:0;padding:0;margin:0}.range-slider__range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:20px;height:20px;border-radius:50%;cursor:pointer;transition:background .15s ease-in-out}.range-slider__range::-webkit-slider-thumb:hover{background:#1abc9c}.range-slider__range:active::-webkit-slider-thumb{background:#1abc9c}.range-slider__range::-moz-range-thumb{width:20px;height:20px;border:0;border-radius:50%;cursor:pointer;transition:background .15s ease-in-out}.range-slider__range:focus::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 6px #1abc9c}.range-slider__value{display:inline-block;position:relative;width:30px;color:#fff;line-height:20px;text-align:center;border-radius:3px;padding:5px 5px;margin-left:2px}.range-slider__value:after{position:absolute;top:8px;left:-7px;width:0;height:0;content:""}::-moz-range-track{border:0}input::-moz-focus-inner,input::-moz-focus-outer{border:0}svg{display:block;width:100%;height:100%}.y-axis path,.x-axis path{stroke:gray;stroke-width:1;fill:none}.series{stroke:steelblue;stroke-width:3;fill:none}.data-points circle{stroke:steelblue;stroke-width:2;fill:white}.data-points text{display:none}.data-points circle:hover{fill:steelblue;stroke-width:6}.data-points circle:hover+text{display:inline-block}text{text-anchor:end}
\ No newline at end of file
diff --git a/data/index.htm b/data/index.htm
new file mode 100644
index 0000000..dbb5e84
--- /dev/null
+++ b/data/index.htm
@@ -0,0 +1,35 @@
+
+
+
+
+ Control
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Control
+ Offline
+
+
+
+
+
+
diff --git a/examples/gui/data/index.min.htm b/data/index.min.htm
similarity index 68%
rename from examples/gui/data/index.min.htm
rename to data/index.min.htm
index b135bf6..9ae11a7 100644
--- a/examples/gui/data/index.min.htm
+++ b/data/index.min.htm
@@ -1 +1 @@
- Control
\ No newline at end of file
+ Control
\ No newline at end of file
diff --git a/data/js/controls.js b/data/js/controls.js
new file mode 100644
index 0000000..0cee9b1
--- /dev/null
+++ b/data/js/controls.js
@@ -0,0 +1,895 @@
+const UI_INITIAL_GUI = 200;
+const UPDATE_OFFSET = 100;
+
+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 UPTDATE_GAUGE = 117;
+const UI_ACCEL = 18;
+const UPTDATE_ACCEL = 117;
+
+const UP = 0;
+const DOWN = 1;
+const LEFT = 2;
+const RIGHT = 3;
+const CENTER = 4;
+
+// Colors
+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_NONE:
+ return "dark";
+ default:
+ return "";
+ }
+}
+
+var websock;
+var websockConnected = false;
+
+function requestOrientationPermission() {
+ /*
+ // Currently this fails, since it needs secure context on IOS safari
+ if (typeof DeviceMotionEvent.requestPermission === "function") {
+ DeviceOrientationEvent.requestPermission()
+ .then(response => {
+ if (response == "granted") {
+ window.addEventListener("deviceorientation", handleOrientation);
+ }
+ })
+ .catch(console.error);
+ } else {
+ // Non IOS 13
+ window.addEventListener("deviceorientation", handleOrientation);
+ }
+ */
+}
+/*
+function handleOrientation(event) {
+ var x = event.beta; // In degree in the range [-180,180]
+ var y = event.gamma; // In degree in the range [-90,90]
+
+ var output = document.querySelector(".output");
+ output.innerHTML = "beta : " + x + "\n";
+ output.innerHTML += "gamma: " + y + "\n";
+
+ // Because we don't want to have the device upside down
+ // We constrain the x value to the range [-90,90]
+ if (x > 90) {
+ x = 90;
+ }
+ if (x < -90) {
+ x = -90;
+ }
+
+ // To make computation easier we shift the range of
+ // x and y to [0,180]
+ x += 90;
+ y += 90;
+
+ // 10 is half the size of the ball
+ // It center the positioning point to the center of the ball
+ var ball = document.querySelector(".ball");
+ var garden = document.querySelector(".garden");
+ var maxX = garden.clientWidth - ball.clientWidth;
+ var maxY = garden.clientHeight - ball.clientHeight;
+ ball.style.top = (maxY * y) / 180 - 10 + "px";
+ ball.style.left = (maxX * x) / 180 - 10 + "px";
+}
+*/
+
+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 handleVisibilityChange() {
+ if (!websockConnected && !document.hidden) {
+ restart();
+ }
+}
+
+function start() {
+ document.addEventListener("visibilitychange", handleVisibilityChange, false);
+ 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:
+ if (data.sliderContinuous) {
+ sliderContinuous = data.sliderContinuous;
+ }
+ data.controls.forEach(element => {
+ var fauxEvent = {
+ data: JSON.stringify(element)
+ };
+ handleEvent(fauxEvent);
+ });
+ break;
+
+ case UI_TITEL:
+ document.title = data.label;
+ $("#mainHeader").html(data.label);
+ break;
+
+ case UI_LABEL:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "" +
+ data.value +
+ " " +
+ ""
+ );
+ break;
+
+ case UI_BUTTON:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "" +
+ data.value +
+ " "
+ );
+ $("#btn" + data.id).on({
+ touchstart: function(e) {
+ e.preventDefault();
+ buttonclick(data.id, true);
+ },
+ touchend: function(e) {
+ e.preventDefault();
+ buttonclick(data.id, false);
+ }
+ });
+ break;
+
+ case UI_SWITCHER:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "
" +
+ "
" +
+ " " +
+ "
"
+ );
+ switcher(data.id, data.value);
+ break;
+
+ case UI_CPAD:
+ case UI_PAD:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "
" +
+ "" +
+ "▲ " +
+ "▲ " +
+ "▲ " +
+ "▲ " +
+ " " +
+ (data.type == UI_CPAD
+ ? "OK "
+ : "") +
+ " " +
+ "
"
+ );
+
+ $("#pf" + data.id).on({
+ touchstart: function(e) {
+ e.preventDefault();
+ padclick(UP, data.id, true);
+ },
+ touchend: function(e) {
+ e.preventDefault();
+ padclick(UP, data.id, false);
+ }
+ });
+ $("#pl" + data.id).on({
+ touchstart: function(e) {
+ e.preventDefault();
+ padclick(LEFT, data.id, true);
+ },
+ touchend: function(e) {
+ e.preventDefault();
+ padclick(LEFT, data.id, false);
+ }
+ });
+ $("#pr" + data.id).on({
+ touchstart: function(e) {
+ e.preventDefault();
+ padclick(RIGHT, data.id, true);
+ },
+ touchend: function(e) {
+ e.preventDefault();
+ padclick(RIGHT, data.id, false);
+ }
+ });
+ $("#pb" + data.id).on({
+ touchstart: function(e) {
+ e.preventDefault();
+ padclick(DOWN, data.id, true);
+ },
+ touchend: function(e) {
+ e.preventDefault();
+ padclick(DOWN, data.id, false);
+ }
+ });
+ $("#pc" + data.id).on({
+ touchstart: function(e) {
+ e.preventDefault();
+ padclick(CENTER, data.id, true);
+ },
+ touchend: function(e) {
+ e.preventDefault();
+ padclick(CENTER, data.id, false);
+ }
+ });
+
+ break;
+
+ //https://codepen.io/seanstopnik/pen/CeLqA
+ case UI_SLIDER:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "
" +
+ " " +
+ "" +
+ data.value +
+ " " +
+ "
" +
+ "
"
+ );
+ rangeSlider(!sliderContinuous);
+ break;
+
+ case UI_NUMBER:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ " " +
+ ""
+ );
+ break;
+
+ case UI_TEXT_INPUT:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ " " +
+ ""
+ );
+ break;
+
+ case UI_TAB:
+ $("#tabsnav").append(
+ "" + data.value + " "
+ );
+ $("#tabscontent").append("
");
+
+ tabs = $(".tabscontent")
+ .tabbedContent({ loop: true })
+ .data("api");
+ // switch to tab...
+ $("a")
+ .filter(function() {
+ return $(this).attr("href") === "#click-to-switch";
+ })
+ .on("click", function(e) {
+ var tab = prompt("Tab to switch to (number or id)?");
+ if (!tabs.switchTab(tab)) {
+ alert("That tab does not exist :\\");
+ }
+ e.preventDefault();
+ });
+ break;
+
+ case UI_SELECT:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ " " +
+ ""
+ );
+ break;
+
+ case UI_OPTION:
+ if (data.parentControl) {
+ var parent = $("#select" + data.parentControl);
+ parent.append(
+ "" +
+ data.label +
+ " "
+ );
+ }
+ break;
+
+ case UI_MIN:
+ if (data.parentControl) {
+ var parent = $("#id" + data.parentControl + " input");
+ if (parent.size()) {
+ parent.attr("min", data.value);
+ }
+ }
+ break;
+
+ case UI_MAX:
+ if (data.parentControl) {
+ var parent = $("#id" + data.parentControl + " input");
+ if (parent.size()) {
+ parent.attr("max", data.value);
+ }
+ }
+ break;
+
+ case UI_STEP:
+ if (data.parentControl) {
+ var parent = $("#id" + data.parentControl + " input");
+ if (parent.size()) {
+ parent.attr("step", data.value);
+ }
+ }
+ break;
+ case UI_GRAPH:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "" +
+ "" +
+ data.label +
+ " " +
+ " " +
+ ""
+ );
+ graphData[data.id] = restoreGraphData(data.id);
+ renderGraphSvg(graphData[data.id], "graph" + data.id);
+ break;
+ case ADD_GRAPH_POINT:
+ var ts = Math.round(new Date().getTime() / 1000);
+ graphData[data.id].push({ x: ts, y: data.value });
+ saveGraphData();
+ renderGraphSvg(graphData[data.id], "graph" + data.id);
+ break;
+ case CLEAR_GRAPH:
+ graphData[data.id] = [];
+ saveGraphData();
+ renderGraphSvg(graphData[data.id], "graph" + data.id);
+ break;
+ case UI_GAUGE:
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "WILL BE A GAUGE " +
+ ""
+ );
+ break;
+
+ case UI_ACCEL:
+ if (hasAccel) break;
+ var parent;
+ if (data.parentControl) {
+ parent = $("#tab" + data.parentControl);
+ } else {
+ parent = $("#row");
+ }
+ hasAccel = true;
+ parent.append(
+ "" +
+ "
" +
+ data.label +
+ " " +
+ "ACCEL // Not implemented fully!
"
+ );
+
+ requestOrientationPermission();
+ break;
+
+ case UPDATE_LABEL:
+ $("#l" + data.id).html(data.value);
+ break;
+
+ case UPDATE_SWITCHER:
+ switcher(data.id, data.value == "0" ? 0 : 1);
+ break;
+
+ case UPDATE_SLIDER:
+ slider_move($("#sl" + data.id), data.value, "100", false);
+ break;
+
+ case UPDATE_NUMBER:
+ $("#num" + data.id).val(data.value);
+ break;
+
+ case UPDATE_TEXT_INPUT:
+ $("#text" + data.id).val(data.value);
+ break;
+
+ case UPDATE_SELECT:
+ $("#select" + data.id).val(data.value);
+ break;
+
+ case UPDATE_BUTTON:
+ case UPDATE_PAD:
+ case UPDATE_CPAD:
+ break;
+ case UPDATE_GAUGE:
+ $("#gauge" + data.id).val(data.value);
+ break;
+ case UPDATE_ACCEL:
+ break;
+
+ default:
+ console.error("Unknown type or event");
+ break;
+ }
+
+ if (data.type >= UPDATE_OFFSET && data.type < UI_INITIAL_GUI) {
+ var element = $("#id" + data.id);
+ if (data.type == UPDATE_SLIDER) {
+ element.removeClass(
+ "slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin"
+ );
+ element.addClass("slider-" + colorClass(data.color));
+ } else {
+ element.removeClass(
+ "turquoise emerald peterriver wetasphalt sunflower carrot alizarin"
+ );
+ element.addClass(colorClass(data.color));
+ }
+ }
+ };
+
+ websock.onmessage = handleEvent;
+}
+
+function sliderchange(number) {
+ var val = $("#sl" + number).val();
+ websock.send("slvalue:" + val + ":" + number);
+}
+
+function numberchange(number) {
+ var val = $("#num" + number).val();
+ websock.send("nvalue:" + val + ":" + number);
+}
+
+function textchange(number) {
+ var val = $("#text" + number).val();
+ websock.send("tvalue:" + val + ":" + number);
+}
+
+function selectchange(number) {
+ var val = $("#select" + number).val();
+ websock.send("svalue:" + val + ":" + number);
+}
+
+function buttonclick(number, isdown) {
+ if (isdown) websock.send("bdown:" + number);
+ else websock.send("bup:" + number);
+}
+
+function padclick(type, number, isdown) {
+ switch (type) {
+ case CENTER:
+ if (isdown) websock.send("pcdown:" + number);
+ else websock.send("pcup:" + number);
+ break;
+ case UP:
+ if (isdown) websock.send("pfdown:" + number);
+ else websock.send("pfup:" + number);
+ break;
+ case DOWN:
+ if (isdown) websock.send("pbdown:" + number);
+ else websock.send("pbup:" + number);
+ break;
+ case LEFT:
+ if (isdown) websock.send("pldown:" + number);
+ else websock.send("plup:" + number);
+ break;
+ case RIGHT:
+ if (isdown) websock.send("prdown:" + number);
+ else websock.send("prup:" + number);
+ break;
+ }
+}
+
+function switcher(number, state) {
+ if (state == null) {
+ if ($("#s" + number).is(":checked")) {
+ websock.send("sactive:" + number);
+ $("#sl" + number).addClass("checked");
+ } else {
+ websock.send("sinactive:" + number);
+ $("#sl" + number).removeClass("checked");
+ }
+ } else if (state == 1) {
+ $("#sl" + number).addClass("checked");
+ $("#sl" + number).prop("checked", true);
+ } else if (state == 0) {
+ $("#sl" + number).removeClass("checked");
+ $("#sl" + number).prop("checked", false);
+ }
+}
+
+var rangeSlider = function(isDiscrete) {
+ var slider = $(".range-slider"),
+ range = $(".range-slider__range"),
+ value = $(".range-slider__value");
+
+ slider.each(function() {
+ value.each(function() {
+ var value = $(this)
+ .prev()
+ .attr("value");
+ $(this).html(value);
+ });
+
+ if (!isDiscrete) {
+ range.on({
+ input: function() {
+ sliderchange(
+ $(this)
+ .attr("id")
+ .replace(/^\D+/g, "")
+ );
+ }
+ });
+ } else {
+ range.on({
+ input: function() {
+ $(this)
+ .next()
+ .html(this.value);
+ },
+ change: function() {
+ sliderchange(
+ $(this)
+ .attr("id")
+ .replace(/^\D+/g, "")
+ );
+ }
+ });
+ }
+ });
+};
diff --git a/data/js/controls.min.js b/data/js/controls.min.js
new file mode 100644
index 0000000..2d2c253
--- /dev/null
+++ b/data/js/controls.min.js
@@ -0,0 +1,248 @@
+const UI_INITIAL_GUI=200;const UPDATE_OFFSET=100;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 UPTDATE_GAUGE=117;const UI_ACCEL=18;const UPTDATE_ACCEL=117;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_NONE:return"dark";default:return"";}}
+var websock;var websockConnected=false;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 handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
+function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);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){var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;}
+data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element)};handleEvent(fauxEvent);});break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+""+
+data.value+
+" "+
+"");break;case UI_BUTTON:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+""+
+data.value+
+" ");$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);}});break;case UI_SWITCHER:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+"
"+
+"
"+
+" "+
+"
");switcher(data.id,data.value);break;case UI_CPAD:case UI_PAD:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+"
"+
+""+
+"▲ "+
+"▲ "+
+"▲ "+
+"▲ "+
+" "+
+(data.type==UI_CPAD?"OK ":"")+
+" "+
+"
");$("#pf"+data.id).on({touchstart:function(e){e.preventDefault();padclick(UP,data.id,true);},touchend:function(e){e.preventDefault();padclick(UP,data.id,false);}});$("#pl"+data.id).on({touchstart:function(e){e.preventDefault();padclick(LEFT,data.id,true);},touchend:function(e){e.preventDefault();padclick(LEFT,data.id,false);}});$("#pr"+data.id).on({touchstart:function(e){e.preventDefault();padclick(RIGHT,data.id,true);},touchend:function(e){e.preventDefault();padclick(RIGHT,data.id,false);}});$("#pb"+data.id).on({touchstart:function(e){e.preventDefault();padclick(DOWN,data.id,true);},touchend:function(e){e.preventDefault();padclick(DOWN,data.id,false);}});$("#pc"+data.id).on({touchstart:function(e){e.preventDefault();padclick(CENTER,data.id,true);},touchend:function(e){e.preventDefault();padclick(CENTER,data.id,false);}});break;case UI_SLIDER:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+"
"+
+" "+
+""+
+data.value+
+" "+
+"
"+
+"
");rangeSlider(!sliderContinuous);break;case UI_NUMBER:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+" "+
+"");break;case UI_TEXT_INPUT:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+" "+
+"");break;case UI_TAB:$("#tabsnav").append("
"+data.value+" ");$("#tabscontent").append("
");tabs=$(".tabscontent").tabbedContent({loop:true}).data("api");$("a").filter(function(){return $(this).attr("href")==="#click-to-switch";}).on("click",function(e){var tab=prompt("Tab to switch to (number or id)?");if(!tabs.switchTab(tab)){alert("That tab does not exist :\\");}
+e.preventDefault();});break;case UI_SELECT:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+" "+
+"");break;case UI_OPTION:if(data.parentControl){var parent=$("#select"+data.parentControl);parent.append("
"+
+data.label+
+" ");}
+break;case UI_MIN:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("min",data.value);}}
+break;case UI_MAX:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("max",data.value);}}
+break;case UI_STEP:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("step",data.value);}}
+break;case UI_GRAPH:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+""+
+""+
+data.label+
+" "+
+" "+
+"");graphData[data.id]=restoreGraphData(data.id);renderGraphSvg(graphData[data.id],"graph"+data.id);break;case ADD_GRAPH_POINT:var ts=Math.round(new Date().getTime()/1000);graphData[data.id].push({x:ts,y:data.value});saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case CLEAR_GRAPH:graphData[data.id]=[];saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case UI_GAUGE:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("
"+
+"
"+
+data.label+
+" "+
+"WILL BE A GAUGE "+
+"");break;case UI_ACCEL:if(hasAccel)break;var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+hasAccel=true;parent.append("
"+
+"
"+
+data.label+
+" "+
+"ACCEL // Not implemented fully!
");requestOrientationPermission();break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);break;case UPDATE_SWITCHER:switcher(data.id,data.value=="0"?0:1);break;case UPDATE_SLIDER:slider_move($("#sl"+data.id),data.value,"100",false);break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);break;case UPDATE_SELECT:$("#select"+data.id).val(data.value);break;case UPDATE_BUTTON:case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);break;case UPDATE_ACCEL:break;default:console.error("Unknown type or event");break;}
+if(data.type>=UPDATE_OFFSET&&data.type
' +
+ '
' +
+ '' +
+ " ";
+
+ return tmplt;
+}
+
+function slider_move(parents, newW, sliderW, send) {
+ var slider_new_val = parseInt(Math.round((newW / sliderW) * 100));
+
+ var slider_fill = parents.find(".slider-fill");
+ var slider_handle = parents.find(".slider-handle");
+ var range = parents.find('input[type="range"]');
+
+ 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);
+ }
+}
diff --git a/data/js/slider.min.js b/data/js/slider.min.js
new file mode 100644
index 0000000..1469ffd
--- /dev/null
+++ b/data/js/slider.min.js
@@ -0,0 +1,11 @@
+function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find(".slider");slider_fill=slider.find(".slider-fill");slider_handle=slider.find(".slider-handle");slider_label=slider.find(".slider-label");var range_val=parseInt(range.val());slider_fill.css("width",range_val+"%");slider_handle.css("left",range_val+"%");slider_label.find("span").text(range_val);});self.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2){return false;}
+var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
+$(this).addClass("is-active");var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find(".is-active").removeClass("is-active");};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu};$(document).on(handlers);});self.on("mousedown touchstart",".slider",function(e){if(e.button===2){return false;}
+var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
+var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}
+var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu};$(document).on(handlers);});}
+function sliderDiscrete_tmplt(){var tmplt='
";return tmplt;}
+function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round((newW/sliderW)*100));var slider_fill=parents.find(".slider-fill");var slider_handle=parents.find(".slider-handle");var range=parents.find('input[type="range"]');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);}}
\ No newline at end of file
diff --git a/data/js/tabbedcontent.js b/data/js/tabbedcontent.js
new file mode 100644
index 0000000..2a97a0a
--- /dev/null
+++ b/data/js/tabbedcontent.js
@@ -0,0 +1,351 @@
+/**
+ * Tabs plugin for jQuery created by Òscar Casajuana < elboletaire at underave dot net >
+ *
+ * @copyright Copyright 2013-2016 Òscar Casajuana
+ * @license MIT
+ * @author Òscar Casajuana Alonso
+*/
+;(function($, document, window, undefined) {
+ "use strict";
+
+ var Tabbedcontent = function(tabcontent, options) {
+ var defaults = {
+ links : tabcontent.prev().find('a').length ? tabcontent.prev().find('a') : '.tabs a', // the tabs itself. By default it selects the links contained in the previous wrapper or the links inside ".tabs a" if there's no previous item
+ errorSelector : '.error-message', // false to disable
+ speed : false, // speed of the show effect. Set to null or false to disable
+ onSwitch : false, // onSwitch callback
+ onInit : false, // onInit callback
+ currentClass : 'active', // current selected tab class (is set to the element)
+ tabErrorClass : 'has-errors', // a class to be added to the tab where errorSelector is detected
+ history : true, // set to false to disable HTML5 history
+ historyOnInit : true, // allows to deactivate the history for the intial autmatically tab switch on load
+ loop : false // if set to true will loop between tabs when using the next() and prev() api methods
+ },
+ firstTime = false,
+ children = tabcontent.children(),
+ history = window.history,
+ loc = document.location,
+ current = null
+ ;
+
+ options = $.extend(defaults, options);
+
+ if (!(options.links instanceof $)) {
+ options.links = $(options.links);
+ }
+
+ /**
+ * Checks if the specified tab id exists.
+ *
+ * @param string tab Tab #id
+ * @return bool
+ */
+ function tabExists(tab) {
+ return Boolean(children.filter(tab).length);
+ }
+ /**
+ * Checks if the current tab is the
+ * first one in the tabs set.
+ *
+ * @return bool
+ */
+ function isFirst() {
+ return current === 0;
+ }
+ /**
+ * Checks if the passed number is an integer.
+ *
+ * @param mixed num The value to be checked.
+ * @return bool
+ */
+ function isInt(num) {
+ return num % 1 === 0;
+ }
+ /**
+ * Checks if the current tab is the
+ * last one in the tabs set.
+ *
+ * @return {Boolean} [description]
+ */
+ function isLast() {
+ return current === children.length - 1;
+ }
+ /**
+ * Filters a tab based on current links href.
+ *
+ * Method for compatibility with Zepto.js
+ *
+ * @param string tab Tab #href
+ * @return bool
+ */
+ function filterTab(tab) {
+ return $(this).attr('href').match(new RegExp(tab + '$'));
+ }
+ /**
+ * Returns an object containing two jQuery instances:
+ * one for the tab content and the other for its link.
+ *
+ * @param mixed tab A tab id, #id or index.
+ * @return object With thi
+ */
+ function getTab(tab) {
+ if (tab instanceof $) {
+ return {
+ tab : tab,
+ link : options.links.eq(tab.index())
+ };
+ }
+ if (isInt(tab)) {
+ return {
+ tab : children.eq(tab),
+ link : options.links.eq(tab)
+ };
+ }
+ if (children.filter(tab).length) {
+ return {
+ tab : children.filter(tab),
+ link : options.links.filter(function() {
+ return filterTab.apply(this, [tab]);
+ })
+ };
+ }
+ // assume it's an id without #
+ return {
+ tab : children.filter('#' + tab),
+ link : options.links.filter(function() {
+ return filterTab.apply(this, ['#' + tab]);
+ })
+ };
+ }
+ /**
+ * Returns the index of the current tab.
+ *
+ * @return int
+ */
+ function getCurrent() {
+ return options.links.parent().filter('.' + options.currentClass).index();
+ }
+ /**
+ * Go to the next tab in the tabs set.
+ *
+ * @param bool loop If defined will overwrite options.loop
+ * @return mixed
+ */
+ function next(loop) {
+ ++current;
+
+ if (loop === undefined) loop = options.loop;
+
+ if (current < children.length) {
+ return switchTab(current, true);
+ } else if (loop && current >= children.length) {
+ return switchTab(0, true);
+ }
+
+ return false;
+ }
+ /**
+ * Go to the previous tab in the tabs set.
+ *
+ * @param bool loop If defined will overwrite options.loop
+ * @return mixed
+ */
+ function prev(loop) {
+ --current;
+
+ if (loop === undefined) loop = options.loop;
+
+ if (current >= 0) {
+ return switchTab(current, true);
+ } else if (loop && current < 0) {
+ return switchTab(children.length - 1, true);
+ }
+
+ return false;
+ }
+ /**
+ * onSwitch callback for switchTab.
+ *
+ * @param string tab The tab #id
+ * @return void
+ */
+ function onSwitch(tab) {
+ if (options.history && options.historyOnInit && firstTime && history !== undefined && ('pushState' in history)) {
+ firstTime = false;
+ window.setTimeout(function() {
+ history.replaceState(null, '', tab);
+ }, 100);
+ }
+ current = getCurrent();
+ if (options.onSwitch && typeof options.onSwitch === 'function') {
+ options.onSwitch(tab, api());
+ }
+ tabcontent.trigger('tabcontent.switch', [tab, api()]);
+ }
+ /**
+ * Switch to specified tab.
+ *
+ * @param mixed tab The tab to switch to.
+ * @param bool api Set to true to force history writing.
+ * @return bool Returns false if tab does not exist; true otherwise.
+ */
+ function switchTab(tab, api) {
+ if (!tab.toString().match(/^#/)) {
+ tab = '#' + getTab(tab).tab.attr('id');
+ }
+
+ if (!tabExists(tab)) {
+ return false;
+ }
+
+ // Toggle active class
+ options.links.attr('aria-selected','false').parent().removeClass(options.currentClass);
+ options.links.filter(function() {
+ return filterTab.apply(this, [tab]);
+ }).attr('aria-selected','true').parent().addClass(options.currentClass);
+ // Hide tabs
+ children.hide();
+
+ // We need to force the change of the hash if we're using the API
+ if (options.history && api) {
+ if (history !== undefined && ('pushState' in history)) {
+ history.pushState(null, '', tab);
+ } else {
+ // force hash change to add it to the history
+ window.location.hash = tab;
+ }
+ }
+
+ // Show tabs
+ children.attr('aria-hidden','true').filter(tab).show(options.speed, function() {
+ if (options.speed) {
+ onSwitch(tab);
+ }
+ }).attr('aria-hidden','false');
+ if (!options.speed) {
+ onSwitch(tab);
+ }
+
+ return true;
+ }
+ /**
+ * Api method to switch tabs.
+ *
+ * @param mixed tab Tab to switch to.
+ * @return bool Returns false if tab does not exist; true otherwise.
+ */
+ function apiSwitch(tab) {
+ return switchTab(tab, true);
+ }
+ /**
+ * Method used to switch tabs using the
+ * browser query hash.
+ *
+ * @param object e Event.
+ * @return void
+ */
+ function hashSwitch(e) {
+ switchTab(loc.hash);
+ }
+ /**
+ * Initialization method.
+ *
+ * The tab checking preference is:
+ * - document.location.hash
+ * - options.errorSelector
+ * - first tab in the set of tabs
+ *
+ * The onInit method is called at the
+ * end of this method.
+ *
+ * @return void
+ */
+ function init() {
+ // Switch to tab using location.hash
+ if (tabExists(loc.hash)) {
+ // Switch to current hash tab
+ switchTab(loc.hash);
+ }
+ // If there's a tab link with the options.currentClass set,
+ // switch to that tab.
+ else if (options.links.parent().filter('.' + options.currentClass).length) {
+ switchTab(options.links.parent().filter('.' + options.currentClass).index());
+ }
+ // Switch to tab containing class options.errorSelector
+ else if (options.errorSelector && children.find(options.errorSelector).length) {
+ // Search for errors and show first tab containing one
+ children.each(function() {
+ if ($(this).find(options.errorSelector).length) {
+ switchTab("#" + $(this).attr("id"));
+ return false;
+ }
+ });
+ }
+ // Open first tab
+ else {
+ switchTab("#" + children.filter(":first-child").attr("id"));
+ }
+ // Add a class to every tab containing errors
+ if (options.errorSelector) {
+ children.find(options.errorSelector).each(function() {
+ var tab = getTab($(this).parent());
+ tab.link.parent().addClass(options.tabErrorClass);
+ });
+ }
+
+ // Binding
+ if ('onhashchange' in window) {
+ $(window).bind('hashchange', hashSwitch);
+ } else { // old browsers
+ var current_href = loc.href;
+ window.setInterval(function() {
+ if (current_href !== loc.href) {
+ hashSwitch.call(window.event);
+ current_href = loc.href;
+ }
+ }, 100);
+ }
+ // Bind click event on links, to ensure we don't rewrite the URI in
+ // case history is disabled
+ $(options.links).on('click', function(e) {
+ switchTab($(this).attr('href').replace(/^[^#]+/, ''), options.history);
+ e.preventDefault();
+ });
+
+ // onInit callback
+ if (options.onInit && typeof options.onInit === 'function') {
+ options.onInit(api());
+ }
+ tabcontent.trigger('tabcontent.init', [api()]);
+ }
+ /**
+ * Returns the methods exposed in the api.
+ *
+ * @return object Containing each api method.
+ */
+ function api() {
+ return {
+ 'switch' : apiSwitch,
+ 'switchTab' : apiSwitch, // for old browsers
+ 'getCurrent' : getCurrent,
+ 'getTab' : getTab,
+ 'next' : next,
+ 'prev' : prev,
+ 'isFirst' : isFirst,
+ 'isLast' : isLast
+ };
+ }
+
+ init();
+
+ return api();
+ };
+
+ $.fn.tabbedContent = function(options) {
+ return this.each(function() {
+ var tabs = new Tabbedcontent($(this), options);
+ $(this).data('api', tabs);
+ });
+ };
+
+})(window.jQuery || window.Zepto || window.$, document, window);
diff --git a/data/js/tabbedcontent.min.js b/data/js/tabbedcontent.min.js
new file mode 100644
index 0000000..efbf454
--- /dev/null
+++ b/data/js/tabbedcontent.min.js
@@ -0,0 +1,35 @@
+;(function($,document,window,undefined){"use strict";var Tabbedcontent=function(tabcontent,options){var defaults={links:tabcontent.prev().find('a').length?tabcontent.prev().find('a'):'.tabs a',errorSelector:'.error-message',speed:false,onSwitch:false,onInit:false,currentClass:'active',tabErrorClass:'has-errors',history:true,historyOnInit:true,loop:false},firstTime=false,children=tabcontent.children(),history=window.history,loc=document.location,current=null;options=$.extend(defaults,options);if(!(options.links instanceof $)){options.links=$(options.links);}
+function tabExists(tab){return Boolean(children.filter(tab).length);}
+function isFirst(){return current===0;}
+function isInt(num){return num%1===0;}
+function isLast(){return current===children.length-1;}
+function filterTab(tab){return $(this).attr('href').match(new RegExp(tab+'$'));}
+function getTab(tab){if(tab instanceof $){return{tab:tab,link:options.links.eq(tab.index())};}
+if(isInt(tab)){return{tab:children.eq(tab),link:options.links.eq(tab)};}
+if(children.filter(tab).length){return{tab:children.filter(tab),link:options.links.filter(function(){return filterTab.apply(this,[tab]);})};}
+return{tab:children.filter('#'+tab),link:options.links.filter(function(){return filterTab.apply(this,['#'+tab]);})};}
+function getCurrent(){return options.links.parent().filter('.'+options.currentClass).index();}
+function next(loop){++current;if(loop===undefined)loop=options.loop;if(current=children.length){return switchTab(0,true);}
+return false;}
+function prev(loop){--current;if(loop===undefined)loop=options.loop;if(current>=0){return switchTab(current,true);}else if(loop&¤t<0){return switchTab(children.length-1,true);}
+return false;}
+function onSwitch(tab){if(options.history&&options.historyOnInit&&firstTime&&history!==undefined&&('pushState'in history)){firstTime=false;window.setTimeout(function(){history.replaceState(null,'',tab);},100);}
+current=getCurrent();if(options.onSwitch&&typeof options.onSwitch==='function'){options.onSwitch(tab,api());}
+tabcontent.trigger('tabcontent.switch',[tab,api()]);}
+function switchTab(tab,api){if(!tab.toString().match(/^#/)){tab='#'+getTab(tab).tab.attr('id');}
+if(!tabExists(tab)){return false;}
+options.links.attr('aria-selected','false').parent().removeClass(options.currentClass);options.links.filter(function(){return filterTab.apply(this,[tab]);}).attr('aria-selected','true').parent().addClass(options.currentClass);children.hide();if(options.history&&api){if(history!==undefined&&('pushState'in history)){history.pushState(null,'',tab);}else{window.location.hash=tab;}}
+children.attr('aria-hidden','true').filter(tab).show(options.speed,function(){if(options.speed){onSwitch(tab);}}).attr('aria-hidden','false');if(!options.speed){onSwitch(tab);}
+return true;}
+function apiSwitch(tab){return switchTab(tab,true);}
+function hashSwitch(e){switchTab(loc.hash);}
+function init(){if(tabExists(loc.hash)){switchTab(loc.hash);}
+else if(options.links.parent().filter('.'+options.currentClass).length){switchTab(options.links.parent().filter('.'+options.currentClass).index());}
+else if(options.errorSelector&&children.find(options.errorSelector).length){children.each(function(){if($(this).find(options.errorSelector).length){switchTab("#"+$(this).attr("id"));return false;}});}
+else{switchTab("#"+children.filter(":first-child").attr("id"));}
+if(options.errorSelector){children.find(options.errorSelector).each(function(){var tab=getTab($(this).parent());tab.link.parent().addClass(options.tabErrorClass);});}
+if('onhashchange'in window){$(window).bind('hashchange',hashSwitch);}else{var current_href=loc.href;window.setInterval(function(){if(current_href!==loc.href){hashSwitch.call(window.event);current_href=loc.href;}},100);}
+$(options.links).on('click',function(e){switchTab($(this).attr('href').replace(/^[^#]+/,''),options.history);e.preventDefault();});if(options.onInit&&typeof options.onInit==='function'){options.onInit(api());}
+tabcontent.trigger('tabcontent.init',[api()]);}
+function api(){return{'switch':apiSwitch,'switchTab':apiSwitch,'getCurrent':getCurrent,'getTab':getTab,'next':next,'prev':prev,'isFirst':isFirst,'isLast':isLast};}
+init();return api();};$.fn.tabbedContent=function(options){return this.each(function(){var tabs=new Tabbedcontent($(this),options);$(this).data('api',tabs);});};})(window.jQuery||window.Zepto||window.$,document,window);
\ No newline at end of file
diff --git a/examples/gui/data/js/zepto.min.js b/data/js/zepto.min.js
similarity index 100%
rename from examples/gui/data/js/zepto.min.js
rename to data/js/zepto.min.js
diff --git a/docs/ui_graph.png b/docs/ui_graph.png
new file mode 100644
index 0000000..2fac78c
Binary files /dev/null and b/docs/ui_graph.png differ
diff --git a/docs/ui_number.png b/docs/ui_number.png
new file mode 100644
index 0000000..a8cb23d
Binary files /dev/null and b/docs/ui_number.png differ
diff --git a/docs/ui_select1.png b/docs/ui_select1.png
new file mode 100644
index 0000000..6eb8001
Binary files /dev/null and b/docs/ui_select1.png differ
diff --git a/docs/ui_select2.png b/docs/ui_select2.png
new file mode 100644
index 0000000..5e9ff7b
Binary files /dev/null and b/docs/ui_select2.png differ
diff --git a/docs/ui_tabs.png b/docs/ui_tabs.png
new file mode 100644
index 0000000..e034e2e
Binary files /dev/null and b/docs/ui_tabs.png differ
diff --git a/docs/ui_text.png b/docs/ui_text.png
new file mode 100644
index 0000000..0637747
Binary files /dev/null and b/docs/ui_text.png differ
diff --git a/examples/gui-generic-api/gui-generic-api.ino b/examples/gui-generic-api/gui-generic-api.ino
new file mode 100644
index 0000000..9449728
--- /dev/null
+++ b/examples/gui-generic-api/gui-generic-api.ino
@@ -0,0 +1,262 @@
+#include
+#include
+
+const byte DNS_PORT = 53;
+IPAddress apIP(192, 168, 1, 1);
+DNSServer dnsServer;
+
+#if defined(ESP32)
+#include
+#else
+#include
+#endif
+
+const char *ssid = "ESPUI";
+const char *password = "espui";
+const char *hostname = "espui";
+
+uint16_t status;
+uint16_t button1;
+uint16_t millisLabelId;
+uint16_t switchOne;
+
+void numberCall( Control* sender, int type ) {
+ Serial.println( sender->value );
+}
+
+void textCall(Control *sender, int type) {
+ Serial.print("Text: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println(sender->value);
+}
+
+void slider(Control *sender, int type) {
+ Serial.print("Slider: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println(sender->value);
+}
+
+void buttonCallback(Control *sender, int type) {
+ switch (type) {
+ case B_DOWN:
+ Serial.println("Button DOWN");
+ break;
+
+ case B_UP:
+ Serial.println("Button UP");
+ break;
+ }
+}
+
+void buttonExample(Control *sender, int type) {
+ switch (type) {
+ case B_DOWN:
+ Serial.println("Status: Start");
+ ESPUI.updateControlValue(status, "Start");
+
+ ESPUI.getControl(button1)->color = ControlColor::Carrot;
+ ESPUI.updateControl(button1);
+ break;
+
+ case B_UP:
+ Serial.println("Status: Stop");
+ ESPUI.updateControlValue(status, "Stop");
+
+ ESPUI.getControl(button1)->color = ControlColor::Peterriver;
+ ESPUI.updateControl(button1);
+ break;
+ }
+}
+
+void padExample(Control *sender, int value) {
+ switch (value) {
+ case P_LEFT_DOWN:
+ Serial.print("left down");
+ break;
+
+ case P_LEFT_UP:
+ Serial.print("left up");
+ break;
+
+ case P_RIGHT_DOWN:
+ Serial.print("right down");
+ break;
+
+ case P_RIGHT_UP:
+ Serial.print("right up");
+ break;
+
+ case P_FOR_DOWN:
+ Serial.print("for down");
+ break;
+
+ case P_FOR_UP:
+ Serial.print("for up");
+ break;
+
+ case P_BACK_DOWN:
+ Serial.print("back down");
+ break;
+
+ case P_BACK_UP:
+ Serial.print("back up");
+ break;
+
+ case P_CENTER_DOWN:
+ Serial.print("center down");
+ break;
+
+ case P_CENTER_UP:
+ Serial.print("center up");
+ break;
+ }
+
+ Serial.print(" ");
+ Serial.println(sender->id);
+}
+
+void switchExample(Control *sender, int value) {
+ switch (value) {
+ case S_ACTIVE:
+ Serial.print("Active:");
+ break;
+
+ case S_INACTIVE:
+ Serial.print("Inactive");
+ break;
+ }
+
+ Serial.print(" ");
+ Serial.println(sender->id);
+}
+
+void selectExample(Control *sender, int value) {
+ Serial.print("Select: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println(sender->value);
+}
+
+void otherSwitchExample(Control *sender, int value) {
+ switch (value) {
+ case S_ACTIVE:
+ Serial.print("Active:");
+ break;
+
+ case S_INACTIVE:
+ Serial.print("Inactive");
+ break;
+ }
+
+ Serial.print(" ");
+ Serial.println(sender->id);
+}
+
+void setup(void) {
+ ESPUI.setVerbosity(Verbosity::VerboseJSON);
+ Serial.begin(115200);
+
+#if defined(ESP32)
+ WiFi.setHostname(hostname);
+#else
+ WiFi.hostname(hostname);
+#endif
+
+ // try to connect to existing network
+ WiFi.begin(ssid, password);
+ Serial.print("\n\nTry to connect to existing network");
+
+ {
+ uint8_t timeout = 10;
+
+ // Wait for connection, 5s timeout
+ do {
+ delay(500);
+ Serial.print(".");
+ timeout--;
+ } while (timeout && WiFi.status() != WL_CONNECTED);
+
+ // not connected -> create hotspot
+ if (WiFi.status() != WL_CONNECTED) {
+ Serial.print("\n\nCreating hotspot");
+
+ WiFi.mode(WIFI_AP);
+ WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
+ WiFi.softAP(ssid);
+
+ timeout = 5;
+
+ do {
+ delay(500);
+ Serial.print(".");
+ timeout--;
+ } while (timeout);
+ }
+ }
+
+ dnsServer.start(DNS_PORT, "*", apIP);
+
+ Serial.println("\n\nWiFi parameters:");
+ Serial.print("Mode: ");
+ Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
+ Serial.print("IP address: ");
+ Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
+
+ status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
+
+ uint16_t select1 = ESPUI.addControl(ControlType::Select, "Select:", "", ControlColor::Alizarin, Control::noParent, &selectExample);
+
+ ESPUI.addControl(ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1);
+ ESPUI.addControl(ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1);
+ ESPUI.addControl(ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1);
+
+ ESPUI.addControl(ControlType::Text, "Text Test:", "a Text Field", ControlColor::Alizarin, Control::noParent, &textCall);
+
+ millisLabelId = ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, Control::noParent);
+ button1 = ESPUI.addControl(ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
+ ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample);
+ ESPUI.addControl(ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
+ ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
+ switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switchExample);
+ ESPUI.addControl(ControlType::Switcher, "Switch two", "", ControlColor::None, Control::noParent, &otherSwitchExample);
+ ESPUI.addControl(ControlType::Slider, "Slider one", "30", ControlColor::Alizarin, Control::noParent, &slider);
+ ESPUI.addControl(ControlType::Slider, "Slider two", "100", ControlColor::Alizarin, Control::noParent, &slider);
+ ESPUI.addControl(ControlType::Number, "Number:", "50", ControlColor::Alizarin, Control::noParent, &numberCall);
+
+ /*
+ * .begin loads and serves all files from PROGMEM directly.
+ * If you want to serve the files from SPIFFS use ESPUI.beginSPIFFS
+ * (.prepareFileSystem has to be run in an empty sketch before)
+ */
+
+ // Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
+ // ESPUI.sliderContinuous = true;
+
+ /*
+ * Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
+ * SECURE way of limiting access.
+ * Anyone who is able to sniff traffic will be able to intercept your password
+ * since it is transmitted in cleartext. Just add a string as username and
+ * password, for example begin("ESPUI Control", "username", "password")
+ */
+
+
+ ESPUI.begin("ESPUI Control");
+}
+
+void loop(void) {
+ dnsServer.processNextRequest();
+
+ static long oldTime = 0;
+ static bool testSwitchState = false;
+
+ if (millis() - oldTime > 5000) {
+ ESPUI.updateControlValue(millisLabelId, String(millis()));
+ testSwitchState = !testSwitchState;
+ ESPUI.updateControlValue(switchOne, testSwitchState ? "1" : "0");
+
+ oldTime = millis();
+ }
+}
diff --git a/examples/gui/data/css/style.min.css b/examples/gui/data/css/style.min.css
deleted file mode 100644
index de53df2..0000000
--- a/examples/gui/data/css/style.min.css
+++ /dev/null
@@ -1 +0,0 @@
-.container{position:relative;width:79%;margin:20px;box-sizing:border-box}.column,.columns{width:100%;float:left}.card{margin-top:2%;border-radius:6px;box-shadow:0 4px 4px rgba(204,197,185,0.5);padding-left:20px;padding-right:20px;margin-bottom:10px;min-width:150px;color:#fff}.card-slider{padding-bottom:10px}.turquoise{background:#1abc9c;border-bottom:#16a085 3px solid}.emerald{background:#2ecc71;border-bottom:#27ae60 3px solid}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.dark{background:#444857;border-bottom:#444857 3px solid}.label{box-sizing:border-box;white-space:nowrap;border-radius:.2em;padding:.12em .4em .14em;text-align:center;color:#fff;font-weight:700;line-height:1;margin-bottom:5px;display:inline-block;white-space:nowrap;vertical-align:baseline;position:relative;top:-.15em;background-color:#999;margin-bottom:10px}.label-wrap{width:90%;white-space:pre-wrap;word-wrap:break-word}.label.color-blue{background-color:#6f9ad1}.label.color-red{background-color:#d37c7c}.label.color-green{background-color:#9bc268}.label.color-orange{background-color:#dea154}.label.color-yellow{background-color:#e9d641}.label.color-purple{background-color:#9f83d1}@media(min-width:400px){.container{width:84%}}@media(min-width:630px){.container{width:98%}.column,.columns{margin-right:2%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.66666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.66666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}html{font-size:62.5%}body{margin:0;font-size:1.5em;line-height:1;font-weight:400;font-family:"Open Sans",sans-serif;color:#222;background-color:#ecf0f1}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media(min-width:630px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2rem}h6{font-size:1.5rem}}p{margin-top:0}a{color:#1eaedb}a:hover{color:#0fa0ce}button{display:inline-block;padding:10px;border-radius:3px;color:#fff;background-color:#999}#mainHeader{display:inline-block}#conStatus{position:inherit;font-size:.75em}button,.button{margin-bottom:1rem}.u-full-width{width:100%;box-sizing:border-box}.u-max-full-width{max-width:100%;box-sizing:border-box}.u-pull-right{float:right}.u-pull-left{float:left}.tcenter{text-align:center}hr{margin-top:.5rem;margin-bottom:1.2rem;border-width:0;border-top:1px solid #e1e1e1}.container:after,.row:after,.u-cf{content:"";display:table;clear:both}.control{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.1),hsla(0,0%,100%,0.1));border-radius:50%;box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.75),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.75);height:9em;margin:3em auto;position:relative;width:9em}.control ul{height:100%;padding:0;transform:rotate(45deg)}.control li{border-radius:100% 0 0 0;box-shadow:inset -1px -1px 1px hsla(0,0%,100%,0.5),0 0 1px hsla(0,0%,0%,0.75);display:inline-block;height:50%;overflow:hidden;width:50%}.control ul li:nth-child(2){transform:rotate(90deg)}.control ul li:nth-child(3){transform:rotate(-90deg)}.control ul li:nth-child(4){transform:rotate(180deg)}.control ul a{height:200%;position:relative;transform:rotate(-45deg);width:200%}.control a:hover,.control a:focus{background-color:hsla(0,0%,100%,0.25)}.control a{border-radius:50%;color:#333;display:block;font:bold 1em/3 sans-serif;text-align:center;text-decoration:none;text-shadow:0 1px 1px hsla(0,0%,100%,0.4);transition:.15s}.control .confirm{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.15),hsla(0,0%,100%,0.25));box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.25),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.85);left:50%;line-height:3;margin:-1.5em;position:absolute;top:50%;width:3em}.control .confirm:hover,.control .confirm:focus{background-color:#eee}.switch{display:inline-block !important;background-color:#bebebe;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);color:#fff;cursor:pointer;display:block;font-size:14px;height:26px;margin-bottom:12px;position:relative;width:60px;-webkit-transition:background-color .2s ease-in-out;-moz-transition:background-color .2s ease-in-out;-o-transition:background-color .2s ease-in-out;-ms-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.switch.checked{background-color:#76d21d}.switch input[type="checkbox"]{display:none;cursor:pointer;height:10px;left:12px;position:absolute;top:8px;width:10px}.in{position:absolute;top:8px;left:12px;-webkit-transition:left .08s ease-in-out;-moz-transition:left .08s ease-in-out;-o-transition:left .08s ease-in-out;-ms-transition:left .08s ease-in-out;transition:left .08s ease-in-out}.switch.checked div{left:38px}.switch .in:before{background:#fff;background:-moz-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#f0f0f0));background:-webkit-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-o-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-ms-linear-gradient(top,#fff 0,#f0f0f0 100%);background:linear-gradient(to bottom,#fff 0,#f0f0f0 100%);border:1px solid #fff;border-radius:2px;box-shadow:0 0 4px rgba(0,0,0,0.3);content:"";height:18px;position:absolute;top:-5px;left:-9px;width:26px}.switch .in:after{background:#f0f0f0;background:-moz-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f0f0f0),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-o-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-ms-linear-gradient(top,#f0f0f0 0,#fff 100%);background:linear-gradient(to bottom,#f0f0f0 0,#fff 100%);border-radius:10px;content:"";height:12px;margin:-1px 0 0 -1px;position:absolute;width:12px}.rkmd-slider{display:block;position:relative;font-size:16px;font-family:"Roboto",sans-serif}.rkmd-slider input[type="range"]{overflow:hidden;position:absolute;width:1px;height:1px;opacity:0}.rkmd-slider input[type="range"]+.slider{display:block;position:relative;width:100%;height:27px;border-radius:13px;background-color:#bebebe}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider{height:4px;border-radius:0}}.rkmd-slider input[type="range"]+.slider .slider-fill{display:block;position:absolute;width:0;height:100%;user-select:none;z-index:1}.rkmd-slider input[type="range"]+.slider .slider-handle{cursor:pointer;position:absolute;top:12px;left:0;width:15px;height:15px;margin-left:-8px;border-radius:50%;transition:all .2s ease;user-select:none;z-index:2}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider .slider-handle{top:-5.5px}}.rkmd-slider input[type="range"]:disabled+.slider{background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle{cursor:default !important;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle .slider-label{display:none;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active{top:-5.5px;width:15px;height:15px;margin-left:-8px}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active .slider-label{display:none;border-radius:50%;transform:none}.rkmd-slider input[type="range"]:disabled+.slider .slider-handle:active{box-shadow:none !important;transform:scale(1) !important}.rkmd-slider.slider-discrete .slider .slider-handle{position:relative;z-index:1}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{position:absolute;top:-17.5px;left:4px;width:30px;height:30px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;border-radius:50%;-webkit-transform:scale(1) rotate(-45deg);transform:scale(1) rotate(-45deg);-webkit-transition:all .2s ease;transition:all .2s ease}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{left:-2px;-webkit-transform:scale(0.5) rotate(-45deg);transform:scale(0.5) rotate(-45deg)}}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{position:absolute;top:7px;left:0;width:100%;color:#fff;font-size:16px;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{font-size:12px}}.rkmd-slider.slider-discrete .slider .slider-handle.is-active{top:0;margin-left:-2px;width:4px;height:4px}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label{top:-15px;left:-2px;border-radius:15px 15px 15px 0;-webkit-transform:rotate(-45deg) translate(23px,-25px);transform:rotate(-45deg) translate(23px,-25px)}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label span{opacity:1}.rkmd-slider.slider-discrete.slider-turquoise .slider-label{background-color:#16a085}.rkmd-slider.slider-discrete.slider-emerald .slider-label{background-color:#27ae60}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.rkmd-slider.slider-discrete.slider-peterriver .slider-label{background-color:#2980b9}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.rkmd-slider.slider-discrete.slider-wetasphalt .slider-label{background-color:#2c3e50}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.rkmd-slider.slider-discrete.slider-sunflower .slider-label{background-color:#e6bb0f}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.rkmd-slider.slider-discrete.slider-carrot .slider-label{background-color:#d35400}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.rkmd-slider.slider-discrete.slider-alizarin .slider-label{background-color:#c0392b}input{margin:0 auto 1.2rem auto;padding:2px 5px;width:100%;box-sizing:border-box;border:0;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background:rgba(255,255,255,0.8)}input[id^="num"]{max-width:6em;width:auto;text-align:right;font-weight:bold;font-size:115%}
\ No newline at end of file
diff --git a/examples/gui/data/index.htm b/examples/gui/data/index.htm
deleted file mode 100644
index eccc1f3..0000000
--- a/examples/gui/data/index.htm
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
- Control
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/gui/data/js/controls.js b/examples/gui/data/js/controls.js
deleted file mode 100644
index c945d0b..0000000
--- a/examples/gui/data/js/controls.js
+++ /dev/null
@@ -1,487 +0,0 @@
-const UI_INITIAL_GUI = 100;
-const UI_TITEL = 0;
-
-const UI_LABEL = 1;
-const UPDATE_LABEL = 6;
-
-const UI_BUTTON = 2;
-
-const UI_SWITCHER = 3;
-const UPDATE_SWITCHER = 7;
-
-const UI_PAD = 4;
-const UI_CPAD = 5;
-
-const UI_SLIDER = 8;
-const UPDATE_SLIDER = 9;
-
-const UI_NUMBER = 10;
-const UPDATE_NUMBER = 11;
-
-const UI_TEXT_INPUT = 12;
-const UPDATE_TEXT_INPUT = 13;
-
-const UI_GRAPH = 14;
-const CLEAR_GRAPH = 15;
-const ADD_GRAPH_POINT = 16;
-
-const FOR = 0;
-const BACK = 1;
-const LEFT = 2;
-const RIGHT = 3;
-const CENTER = 4;
-
-// Colors
-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_NONE = 7;
-const C_DARK = 8;
-
-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_NONE:
- return "dark";
- default:
- return "";
- }
-}
-
-var websock;
-var websockConnected = false;
-
-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 handleVisibilityChange() {
- if (!websockConnected && !document.hidden) {
- restart();
- }
-}
-
-function start() {
- document.addEventListener("visibilitychange", handleVisibilityChange, false);
- 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:
- data.controls.forEach(element => {
- var fauxEvent = {
- data: JSON.stringify(element)
- };
- handleEvent(fauxEvent);
- });
- break;
- case UI_TITEL:
- document.title = data.label;
- $("#mainHeader").html(data.label);
- break;
- case UI_LABEL:
- $("#row").append(
- "
" +
- data.label +
- " " +
- data.value +
- " "
- );
- break;
- case UI_BUTTON:
- $("#row").append(
- "
" +
- data.label +
- " " +
- data.value +
- " "
- );
- $("#" + data.id).on({
- touchstart: function(e) {
- e.preventDefault();
- buttonclick(data.id, true);
- }
- });
- $("#" + data.id).on({
- touchend: function(e) {
- e.preventDefault();
- buttonclick(data.id, false);
- }
- });
- break;
- case UI_SWITCHER:
- var label = "";
- var input =
- "
";
- if (data.value == "0") {
- label = "";
- input =
- "
";
- }
- $("#row").append(
- "
" +
- data.label +
- " " +
- label +
- input +
- "" +
- ""
- );
- break;
- case UI_CPAD:
- center =
- "OK ";
- //NO BREAK
- case UI_PAD:
- $("#row").append(
- "" +
- data.label +
- " " +
- "
" +
- "" +
- "▲ " +
- "▲ " +
- "▲ " +
- "▲ " +
- " " +
- center +
- " " +
- "
"
- );
-
- $("#pf" + data.id).on({
- touchstart: function(e) {
- e.preventDefault();
- padclick(FOR, data.id, true);
- }
- });
- $("#pf" + data.id).on({
- touchend: function(e) {
- e.preventDefault();
- padclick(FOR, data.id, false);
- }
- });
- $("#pl" + data.id).on({
- touchstart: function(e) {
- e.preventDefault();
- padclick(LEFT, data.id, true);
- }
- });
- $("#pl" + data.id).on({
- touchend: function(e) {
- e.preventDefault();
- padclick(LEFT, data.id, false);
- }
- });
- $("#pr" + data.id).on({
- touchstart: function(e) {
- e.preventDefault();
- padclick(RIGHT, data.id, true);
- }
- });
- $("#pr" + data.id).on({
- touchend: function(e) {
- e.preventDefault();
- padclick(RIGHT, data.id, false);
- }
- });
- $("#pb" + data.id).on({
- touchstart: function(e) {
- e.preventDefault();
- padclick(BACK, data.id, true);
- }
- });
- $("#pb" + data.id).on({
- touchend: function(e) {
- e.preventDefault();
- padclick(BACK, data.id, false);
- }
- });
- $("#pc" + data.id).on({
- touchstart: function(e) {
- e.preventDefault();
- padclick(CENTER, data.id, true);
- }
- });
- $("#pc" + data.id).on({
- touchend: function(e) {
- e.preventDefault();
- padclick(CENTER, data.id, false);
- }
- });
-
- break;
- case UPDATE_LABEL:
- $("#l" + data.id).html(data.value);
- break;
- case UPDATE_SWITCHER:
- if (data.value == "0") switcher(data.id, 0);
- else switcher(data.id, 1);
- break;
- case UI_SLIDER:
- $("#row").append(
- "" +
- "
" +
- data.label +
- " " +
- "
" +
- " " +
- "
" +
- "
"
- );
- $("#row").append(
- ""
- );
- break;
-
- case UPDATE_SLIDER:
- slider_move($("#sl" + data.id), data.value, "100", false);
- break;
-
- case UI_NUMBER:
- $("#row").append(
- "" +
- "
" +
- data.label +
- " " +
- " " +
- ""
- );
- break;
-
- case UPDATE_NUMBER:
- $("#num" + data.id).val(data.value);
- break;
-
- case UI_TEXT_INPUT:
- $("#row").append(
- "" +
- "
" +
- data.label +
- " " +
- " " +
- ""
- );
- break;
-
- case UPDATE_TEXT_INPUT:
- $("#text" + data.id).val(data.value);
- break;
-
- default:
- console.error("Unknown type or event");
- break;
- }
- };
-
- websock.onmessage = handleEvent;
-}
-
-function numberchange(number) {
- var val = $("#num" + number).val();
- websock.send("nvalue:" + val + ":" + number);
- console.log(val);
-}
-
-function textchange(number) {
- var val = $("#text" + number).val();
- websock.send("tvalue:" + val + ":" + number);
- console.log(val);
-}
-
-function buttonclick(number, isdown) {
- if (isdown) websock.send("bdown:" + number);
- else websock.send("bup:" + number);
-}
-
-function padclick(type, number, isdown) {
- switch (type) {
- case CENTER:
- if (isdown) websock.send("pcdown:" + number);
- else websock.send("pcup:" + number);
- break;
- case FOR:
- if (isdown) websock.send("pfdown:" + number);
- else websock.send("pfup:" + number);
- break;
- case BACK:
- if (isdown) websock.send("pbdown:" + number);
- else websock.send("pbup:" + number);
- break;
- case LEFT:
- if (isdown) websock.send("pldown:" + number);
- else websock.send("plup:" + number);
- break;
- case RIGHT:
- if (isdown) websock.send("prdown:" + number);
- else websock.send("prup:" + number);
- break;
- }
-}
-
-function switcher(number, state) {
- if (state == null) {
- if ($("#s" + number).is(":checked")) {
- websock.send("sactive:" + number);
- $("#sl" + number).addClass("checked");
- } else {
- websock.send("sinactive:" + number);
- $("#sl" + number).removeClass("checked");
- }
- } else if (state == 1) {
- $("#sl" + number).addClass("checked");
- $("#sl" + number).prop("checked", true);
- } else if (state == 0) {
- $("#sl" + number).removeClass("checked");
- $("#sl" + number).prop("checked", false);
- }
-}
diff --git a/examples/gui/data/js/controls.min.js b/examples/gui/data/js/controls.min.js
deleted file mode 100644
index d9cf5e7..0000000
--- a/examples/gui/data/js/controls.min.js
+++ /dev/null
@@ -1,142 +0,0 @@
-const UI_INITIAL_GUI=100;const UI_TITEL=0;const UI_LABEL=1;const UPDATE_LABEL=6;const UI_BUTTON=2;const UI_SWITCHER=3;const UPDATE_SWITCHER=7;const UI_PAD=4;const UI_CPAD=5;const UI_SLIDER=8;const UPDATE_SLIDER=9;const UI_NUMBER=10;const UPDATE_NUMBER=11;const UI_TEXT_INPUT=12;const UPDATE_TEXT_INPUT=13;const UI_GRAPH=14;const CLEAR_GRAPH=15;const ADD_GRAPH_POINT=16;const FOR=0;const BACK=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_NONE=7;const C_DARK=8;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_NONE:return"dark";default:return"";}}
-var websock;var websockConnected=false;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 handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
-function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);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){var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element)};handleEvent(fauxEvent);});break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:$("#row").append("
"+
-data.label+
-" "+
-data.value+
-" ");break;case UI_BUTTON:$("#row").append("
"+
-data.label+
-" "+
-data.value+
-" ");$("#"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);}});$("#"+data.id).on({touchend:function(e){e.preventDefault();buttonclick(data.id,false);}});break;case UI_SWITCHER:var label="";var input="
";if(data.value=="0"){label="";input="
";}
-$("#row").append("
"+
-data.label+
-" "+
-label+
-input+
-""+
-"");break;case UI_CPAD:center="OK ";case UI_PAD:$("#row").append(""+
-data.label+
-" "+
-"
"+
-""+
-"▲ "+
-"▲ "+
-"▲ "+
-"▲ "+
-" "+
-center+
-" "+
-"
");$("#pf"+data.id).on({touchstart:function(e){e.preventDefault();padclick(FOR,data.id,true);}});$("#pf"+data.id).on({touchend:function(e){e.preventDefault();padclick(FOR,data.id,false);}});$("#pl"+data.id).on({touchstart:function(e){e.preventDefault();padclick(LEFT,data.id,true);}});$("#pl"+data.id).on({touchend:function(e){e.preventDefault();padclick(LEFT,data.id,false);}});$("#pr"+data.id).on({touchstart:function(e){e.preventDefault();padclick(RIGHT,data.id,true);}});$("#pr"+data.id).on({touchend:function(e){e.preventDefault();padclick(RIGHT,data.id,false);}});$("#pb"+data.id).on({touchstart:function(e){e.preventDefault();padclick(BACK,data.id,true);}});$("#pb"+data.id).on({touchend:function(e){e.preventDefault();padclick(BACK,data.id,false);}});$("#pc"+data.id).on({touchstart:function(e){e.preventDefault();padclick(CENTER,data.id,true);}});$("#pc"+data.id).on({touchend:function(e){e.preventDefault();padclick(CENTER,data.id,false);}});break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);break;case UPDATE_SWITCHER:if(data.value=="0")switcher(data.id,0);else switcher(data.id,1);break;case UI_SLIDER:$("#row").append(""+
-"
"+
-data.label+
-" "+
-"
"+
-" "+
-"
"+
-"
");$("#row").append("");break;case UPDATE_SLIDER:slider_move($("#sl"+data.id),data.value,"100",false);break;case UI_NUMBER:$("#row").append(""+
-"
"+
-data.label+
-" "+
-" "+
-"");break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);break;case UI_TEXT_INPUT:$("#row").append(""+
-"
"+
-data.label+
-" "+
-" "+
-"");break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);break;default:console.error("Unknown type or event");break;}};websock.onmessage=handleEvent;}
-function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);console.log(val);}
-function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);console.log(val);}
-function buttonclick(number,isdown){if(isdown)websock.send("bdown:"+number);else websock.send("bup:"+number);}
-function padclick(type,number,isdown){switch(type){case CENTER:if(isdown)websock.send("pcdown:"+number);else websock.send("pcup:"+number);break;case FOR:if(isdown)websock.send("pfdown:"+number);else websock.send("pfup:"+number);break;case BACK:if(isdown)websock.send("pbdown:"+number);else websock.send("pbup:"+number);break;case LEFT:if(isdown)websock.send("pldown:"+number);else websock.send("plup:"+number);break;case RIGHT:if(isdown)websock.send("prdown:"+number);else websock.send("prup:"+number);break;}}
-function switcher(number,state){if(state==null){if($("#s"+number).is(":checked")){websock.send("sactive:"+number);$("#sl"+number).addClass("checked");}else{websock.send("sinactive:"+number);$("#sl"+number).removeClass("checked");}}else if(state==1){$("#sl"+number).addClass("checked");$("#sl"+number).prop("checked",true);}else if(state==0){$("#sl"+number).removeClass("checked");$("#sl"+number).prop("checked",false);}}
\ No newline at end of file
diff --git a/examples/gui/data/js/slider.js b/examples/gui/data/js/slider.js
deleted file mode 100644
index 3e44efa..0000000
--- a/examples/gui/data/js/slider.js
+++ /dev/null
@@ -1,125 +0,0 @@
-/* -----------------------------------------------------
- Material Design Sliders
- CodePen URL: https://codepen.io/rkchauhan/pen/xVGGpR
- By: Ravikumar Chauhan
--------------------------------------------------------- */
-function rkmd_rangeSlider(selector) {
- var self, slider_width, slider_offset, curnt, sliderDiscrete, range, slider;
- self = $(selector);
- slider_width = self.width();
- slider_offset = self.offset().left;
- sliderDiscrete = self;
-
- sliderDiscrete.each(function(i, v) {
- curnt = $(this);
- curnt.append(sliderDiscrete_tmplt());
- range = curnt.find('input[type="range"]');
- slider = curnt.find('.slider');
- slider_fill = slider.find('.slider-fill');
- slider_handle = slider.find('.slider-handle');
- slider_label = slider.find('.slider-label');
-
- var range_val = parseInt(range.val());
- slider_fill.css('width', range_val + '%');
- slider_handle.css('left', range_val + '%');
- slider_label.find('span').text(range_val);
- });
-
- self.on('mousedown touchstart', '.slider-handle', function(e) {
- if (e.button === 2) {
- return false;
- }
- var parents = $(this).parents('.rkmd-slider');
- var slider_width = parents.width();
- var slider_offset = parents.offset().left;
- var check_range = parents.find('input[type="range"]').is(':disabled');
- if (check_range === true) {
- return false;
- }
- $(this).addClass('is-active');
- var moveFu =
- function(e) {
- var pageX = e.pageX || e.changedTouches[0].pageX;
- var slider_new_width = pageX - slider_offset;
- if (slider_new_width <= slider_width && !(slider_new_width < '0')) {
- slider_move(parents, slider_new_width, slider_width, true);
- }
- };
- var upFu =
- function(e) {
- $(this).off(handlers);
- parents.find('.is-active').removeClass('is-active');
- };
-
- var handlers = {
- mousemove: moveFu,
- touchmove: moveFu,
- mouseup: upFu,
- touchend: upFu
- };
- $(document).on(handlers);
- });
-
- self.on('mousedown touchstart', '.slider', function(e) {
- if (e.button === 2) {
- return false;
- }
-
- var parents = $(this).parents('.rkmd-slider');
- var slider_width = parents.width();
- var slider_offset = parents.offset().left;
- var check_range = parents.find('input[type="range"]').is(':disabled');
-
- if (check_range === true) {
- return false;
- }
-
- var slider_new_width = e.pageX - slider_offset;
- if (slider_new_width <= slider_width && !(slider_new_width < '0')) {
- slider_move(parents, slider_new_width, slider_width, true);
- }
- var upFu =
- function(e) {
- $(this).off(handlers);
- };
-
- var handlers = {
- mouseup: upFu,
- touchend: upFu
- };
- $(document).on(handlers);
-
- });
-};
-
-function sliderDiscrete_tmplt() {
- var tmplt = '';
-
- return tmplt;
-}
-
-function slider_move(parents, newW, sliderW, send) {
- var slider_new_val = parseInt(Math.round(newW / sliderW * 100));
-
- var slider_fill = parents.find('.slider-fill');
- var slider_handle = parents.find('.slider-handle');
- var range = parents.find('input[type="range"]');
-
- 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);
- }
-}
diff --git a/examples/gui/data/js/slider.min.js b/examples/gui/data/js/slider.min.js
deleted file mode 100644
index 4542122..0000000
--- a/examples/gui/data/js/slider.min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find('.slider');slider_fill=slider.find('.slider-fill');slider_handle=slider.find('.slider-handle');slider_label=slider.find('.slider-label');var range_val=parseInt(range.val());slider_fill.css('width',range_val+'%');slider_handle.css('left',range_val+'%');slider_label.find('span').text(range_val);});self.on('mousedown touchstart','.slider-handle',function(e){if(e.button===2){return false;}
-var parents=$(this).parents('.rkmd-slider');var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(':disabled');if(check_range===true){return false;}
-$(this).addClass('is-active');var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<'0')){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find('.is-active').removeClass('is-active');};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu};$(document).on(handlers);});self.on('mousedown touchstart','.slider',function(e){if(e.button===2){return false;}
-var parents=$(this).parents('.rkmd-slider');var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(':disabled');if(check_range===true){return false;}
-var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<'0')){slider_move(parents,slider_new_width,slider_width,true);}
-var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu};$(document).on(handlers);});};function sliderDiscrete_tmplt(){var tmplt='';return tmplt;}
-function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round(newW/sliderW*100));var slider_fill=parents.find('.slider-fill');var slider_handle=parents.find('.slider-handle');var range=parents.find('input[type="range"]');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);}}
\ No newline at end of file
diff --git a/examples/gui/gui.ino b/examples/gui/gui.ino
index 7ae4bb6..33916c8 100644
--- a/examples/gui/gui.ino
+++ b/examples/gui/gui.ino
@@ -12,174 +12,237 @@ DNSServer dnsServer;
#endif
const char *ssid = "ESPUI";
-const char *password = "";
+const char *password = "espui";
-long oldTime = 0;
-bool switchi = false;
+const char *hostname = "espui";
-void numberCall(Control sender, int type) { Serial.println(sender.value); }
+int statusLabelId;
+int graphId;
+int millisLabelId;
+int testSwitchId;
-void textCall(Control sender, int type) { Serial.println(sender.value); }
+void numberCall(Control *sender, int type) { Serial.println(sender->value); }
-void slider(Control sender, int type) { Serial.println(sender.value); }
+void textCall(Control *sender, int type) {
+ Serial.print("Text: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println(sender->value);
+}
-void buttonCallback(Control sender, int type) {
+void slider(Control *sender, int type) {
+ Serial.print("Slider: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println(sender->value);
+ // Like all Control Values in ESPUI slider values are Strings. To use them as int simply do this:
+ int sliderValueWithOffset = sender->value.toInt() + 100;
+ Serial.print("SliderValue with offset");
+ Serial.println(sliderValueWithOffset);
+}
+
+void buttonCallback(Control *sender, int type) {
switch (type) {
- case B_DOWN:
- Serial.println("Button DOWN");
- break;
- case B_UP:
- Serial.println("Button UP");
- break;
+ case B_DOWN:
+ Serial.println("Button DOWN");
+ break;
+
+ case B_UP:
+ Serial.println("Button UP");
+ break;
}
}
-void buttonExample(Control sender, int type) {
+void buttonExample(Control *sender, int type) {
switch (type) {
- case B_DOWN:
- Serial.println("Status: Start");
- ESPUI.print(0, "Status: Start");
- break;
- case B_UP:
- Serial.println("Status: Stop");
- ESPUI.print(0, "Status: Stop");
- break;
+ case B_DOWN:
+ Serial.println("Status: Start");
+ ESPUI.print(statusLabelId, "Start");
+ break;
+
+ case B_UP:
+ Serial.println("Status: Stop");
+ ESPUI.print(statusLabelId, "Stop");
+ break;
}
}
-void padExample(Control sender, int value) {
+void padExample(Control *sender, int value) {
switch (value) {
- case P_LEFT_DOWN:
- Serial.print("left down");
- break;
- case P_LEFT_UP:
- Serial.print("left up");
- break;
- case P_RIGHT_DOWN:
- Serial.print("right down");
- break;
- case P_RIGHT_UP:
- Serial.print("right up");
- break;
- case P_FOR_DOWN:
- Serial.print("for down");
- break;
- case P_FOR_UP:
- Serial.print("for up");
- break;
- case P_BACK_DOWN:
- Serial.print("back down");
- break;
- case P_BACK_UP:
- Serial.print("back up");
- break;
- case P_CENTER_DOWN:
- Serial.print("center down");
- break;
- case P_CENTER_UP:
- Serial.print("center up");
- break;
+ case P_LEFT_DOWN:
+ Serial.print("left down");
+ break;
+
+ case P_LEFT_UP:
+ Serial.print("left up");
+ break;
+
+ case P_RIGHT_DOWN:
+ Serial.print("right down");
+ break;
+
+ case P_RIGHT_UP:
+ Serial.print("right up");
+ break;
+
+ case P_FOR_DOWN:
+ Serial.print("for down");
+ break;
+
+ case P_FOR_UP:
+ Serial.print("for up");
+ break;
+
+ case P_BACK_DOWN:
+ Serial.print("back down");
+ break;
+
+ case P_BACK_UP:
+ Serial.print("back up");
+ break;
+
+ case P_CENTER_DOWN:
+ Serial.print("center down");
+ break;
+
+ case P_CENTER_UP:
+ Serial.print("center up");
+ break;
}
+
Serial.print(" ");
- Serial.println(sender.id);
+ Serial.println(sender->id);
}
-void switchExample(Control sender, int value) {
+void switchExample(Control *sender, int value) {
switch (value) {
- case S_ACTIVE:
- Serial.print("Active:");
- break;
- case S_INACTIVE:
- Serial.print("Inactive");
- break;
+ case S_ACTIVE:
+ Serial.print("Active:");
+ break;
+
+ case S_INACTIVE:
+ Serial.print("Inactive");
+ break;
}
+
Serial.print(" ");
- Serial.println(sender.id);
+ Serial.println(sender->id);
}
-void otherSwitchExample(Control sender, int value) {
+void otherSwitchExample(Control *sender, int value) {
switch (value) {
- case S_ACTIVE:
- Serial.print("Active:");
- break;
- case S_INACTIVE:
- Serial.print("Inactive");
- break;
+ case S_ACTIVE:
+ Serial.print("Active:");
+ break;
+
+ case S_INACTIVE:
+ Serial.print("Inactive");
+ break;
}
+
Serial.print(" ");
- Serial.println(sender.id);
+ Serial.println(sender->id);
}
void setup(void) {
+ ESPUI.setVerbosity(Verbosity::VerboseJSON);
Serial.begin(115200);
- WiFi.mode(WIFI_AP);
- WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
- /*
- #if defined(ESP32)
- WiFi.setHostname(ssid);
- #else
- WiFi.hostname(ssid);
- #endif
- */
- WiFi.softAP(ssid);
- // WiFi.softAP(ssid, password);
- Serial.println("");
- Serial.print("IP address: ");
- Serial.println(WiFi.softAPIP());
+#if defined(ESP32)
+ WiFi.setHostname(hostname);
+#else
+ WiFi.hostname(hostname);
+#endif
- // change the beginning to this if you want to join an existing network
- /*
- Serial.begin(115200);
- WiFi.begin(ssid, password);
- Serial.println("");
- // Wait for connection
- while (WiFi.status() != WL_CONNECTED) {
- delay(500);
- Serial.print(".");
- }
- Serial.println("");
- Serial.print("IP address: ");
- Serial.println(WiFi.localIP());
- */
+ // try to connect to existing network
+ WiFi.begin(ssid, password);
+ Serial.print("\n\nTry to connect to existing network");
- ESPUI.label("Status:", COLOR_TURQUOISE, "Stop");
- ESPUI.label("Millis:", COLOR_EMERALD, "0");
- ESPUI.button("Push Button", &buttonCallback, COLOR_PETERRIVER);
- ESPUI.button("Other Button", &buttonExample, COLOR_WETASPHALT, "Press");
- ESPUI.pad("Pad with center", true, &padExample, COLOR_SUNFLOWER);
- ESPUI.pad("Pad without center", false, &padExample, COLOR_CARROT);
- ESPUI.switcher("Switch one", false, &switchExample, COLOR_ALIZARIN);
- ESPUI.switcher("Switch two", true, &otherSwitchExample, COLOR_NONE);
- ESPUI.slider("Slider one", &slider, COLOR_ALIZARIN, "30");
- ESPUI.slider("Slider two", &slider, COLOR_NONE, "100");
- ESPUI.text("Text Test:", &textCall, COLOR_ALIZARIN, "a Text Field");
- ESPUI.number("Numbertest", &numberCall, COLOR_ALIZARIN, 5, 0, 10);
+ {
+ uint8_t timeout = 10;
- /*
- .begin loads and serves all files from PROGMEM directly.
- If you want to serve the files from SPIFFS use ESPUI.beginSPIFFS
- (.prepareFileSystem has to be run in an empty sketch before)
- */
+ // Wait for connection, 5s timeout
+ do {
+ delay(500);
+ Serial.print(".");
+ timeout--;
+ } while (timeout && WiFi.status() != WL_CONNECTED);
+
+ // not connected -> create hotspot
+ if (WiFi.status() != WL_CONNECTED) {
+ Serial.print("\n\nCreating hotspot");
+
+ WiFi.mode(WIFI_AP);
+ WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
+ WiFi.softAP(ssid);
+
+ timeout = 5;
+
+ do {
+ delay(500);
+ Serial.print(".");
+ timeout--;
+ } while (timeout);
+ }
+ }
dnsServer.start(DNS_PORT, "*", apIP);
+ Serial.println("\n\nWiFi parameters:");
+ Serial.print("Mode: ");
+ Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
+ Serial.print("IP address: ");
+ Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
+
+ 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.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
+ ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
+ testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
+ ESPUI.switcher("Switch two", &otherSwitchExample, ControlColor::None, true);
+ ESPUI.slider("Slider one", &slider, ControlColor::Alizarin, 30);
+ ESPUI.slider("Slider two", &slider, ControlColor::None, 100);
+ ESPUI.text("Text Test:", &textCall, ControlColor::Alizarin, "a Text Field");
+ ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);
+
+ graphId = ESPUI.graph("Graph Test", ControlColor::Wetasphalt);
+
+ /*
+ * .begin loads and serves all files from PROGMEM directly.
+ * If you want to serve the files from SPIFFS use ESPUI.beginSPIFFS
+ * (.prepareFileSystem has to be run in an empty sketch before)
+ */
+
+ // Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
+ // ESPUI.sliderContinuous = true;
+
/*
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
- SECURE way of limiting access.
+ * SECURE way of limiting access.
* Anyone who is able to sniff traffic will be able to intercept your password
- since it is transmitted in cleartext ESPUI.begin("ESPUI Control", "myuser",
- "mypassword");
- */
+ * since it is transmitted in cleartext. Just add a string as username and
+ * password, for example begin("ESPUI Control", "username", "password")
+ */
+
+
ESPUI.begin("ESPUI Control");
}
void loop(void) {
dnsServer.processNextRequest();
+ static long oldTime = 0;
+ static bool testSwitchState = false;
+
if (millis() - oldTime > 5000) {
- ESPUI.print("Millis:", String(millis()));
- switchi = !switchi;
- ESPUI.updateSwitcher("Switch one", switchi);
+ ESPUI.print(millisLabelId, String(millis()));
+
+ ESPUI.addGraphPoint(graphId, random(1, 50));
+
+ testSwitchState = !testSwitchState;
+ ESPUI.updateSwitcher(testSwitchId, testSwitchState);
+
oldTime = millis();
}
}
\ No newline at end of file
diff --git a/examples/prepareFilesystem/prepareFilesystem.ino b/examples/prepareFilesystem/prepareFilesystem.ino
index c65abb1..4659b09 100644
--- a/examples/prepareFilesystem/prepareFilesystem.ino
+++ b/examples/prepareFilesystem/prepareFilesystem.ino
@@ -1,5 +1,7 @@
#include
+ESPUIClass ESPUI( Verbosity::VerboseJSON );
+
void setup(void) {
Serial.begin(115200);
ESPUI.prepareFileSystem();
diff --git a/examples/tabbedGui/tabbedGui.ino b/examples/tabbedGui/tabbedGui.ino
new file mode 100644
index 0000000..66344aa
--- /dev/null
+++ b/examples/tabbedGui/tabbedGui.ino
@@ -0,0 +1,262 @@
+#include
+#include
+
+const byte DNS_PORT = 53;
+IPAddress apIP( 192, 168, 1, 1 );
+DNSServer dnsServer;
+
+#if defined(ESP32)
+#include
+#else
+#include
+#endif
+
+const char* ssid = "ESPUI";
+const char* password = "espui";
+const char* hostname = "espui";
+
+uint16_t button1;
+uint16_t switchOne;
+uint16_t status;
+
+void numberCall( Control* sender, int type ) {
+ Serial.println( sender->value );
+}
+
+void textCall( Control* sender, int type ) {
+ Serial.print("Text: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println( sender->value );}
+
+void slider( Control* sender, int type ) {
+ Serial.print("Slider: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println( sender->value );}
+
+void buttonCallback( Control* sender, int type ) {
+ switch ( type ) {
+ case B_DOWN:
+ Serial.println( "Button DOWN" );
+ break;
+
+ case B_UP:
+ Serial.println( "Button UP" );
+ break;
+ }
+}
+
+void buttonExample( Control* sender, int type ) {
+ switch ( type ) {
+ case B_DOWN:
+ Serial.println( "Status: Start" );
+ ESPUI.updateControlValue( status, "Start" );
+
+ ESPUI.getControl( button1 )->color = ControlColor::Carrot;
+ ESPUI.updateControl( button1 );
+ break;
+
+ case B_UP:
+ Serial.println( "Status: Stop" );
+ ESPUI.updateControlValue( status, "Stop" );
+
+ ESPUI.getControl( button1 )->color = ControlColor::Peterriver;
+ ESPUI.updateControl( button1 );
+ break;
+ }
+}
+
+void padExample( Control* sender, int value ) {
+ switch ( value ) {
+ case P_LEFT_DOWN:
+ Serial.print( "left down" );
+ break;
+
+ case P_LEFT_UP:
+ Serial.print( "left up" );
+ break;
+
+ case P_RIGHT_DOWN:
+ Serial.print( "right down" );
+ break;
+
+ case P_RIGHT_UP:
+ Serial.print( "right up" );
+ break;
+
+ case P_FOR_DOWN:
+ Serial.print( "for down" );
+ break;
+
+ case P_FOR_UP:
+ Serial.print( "for up" );
+ break;
+
+ case P_BACK_DOWN:
+ Serial.print( "back down" );
+ break;
+
+ case P_BACK_UP:
+ Serial.print( "back up" );
+ break;
+
+ case P_CENTER_DOWN:
+ Serial.print( "center down" );
+ break;
+
+ case P_CENTER_UP:
+ Serial.print( "center up" );
+ break;
+ }
+
+ Serial.print( " " );
+ Serial.println( sender->id );
+}
+
+void switchExample( Control* sender, int value ) {
+ switch ( value ) {
+ case S_ACTIVE:
+ Serial.print( "Active:" );
+ break;
+
+ case S_INACTIVE:
+ Serial.print( "Inactive" );
+ break;
+ }
+
+ Serial.print( " " );
+ Serial.println( sender->id );
+}
+
+void selectExample( Control* sender, int value ) {
+ Serial.print("Select: ID: ");
+ Serial.print(sender->id);
+ Serial.print(", Value: ");
+ Serial.println( sender->value );
+}
+
+void otherSwitchExample( Control* sender, int value ) {
+ switch ( value ) {
+ case S_ACTIVE:
+ Serial.print( "Active:" );
+ break;
+
+ case S_INACTIVE:
+ Serial.print( "Inactive" );
+ break;
+ }
+
+ Serial.print( " " );
+ Serial.println( sender->id );
+}
+
+void setup( void ) {
+ Serial.begin( 115200 );
+
+#if defined(ESP32)
+ WiFi.setHostname( hostname );
+#else
+ WiFi.hostname( hostname );
+#endif
+
+ // try to connect to existing network
+ WiFi.begin( ssid, password );
+ Serial.print( "\n\nTry to connect to existing network" );
+
+ {
+ uint8_t timeout = 10;
+
+ // Wait for connection, 5s timeout
+ do {
+ delay( 500 );
+ Serial.print( "." );
+ timeout--;
+ } while ( timeout && WiFi.status() != WL_CONNECTED );
+
+ // not connected -> create hotspot
+ if ( WiFi.status() != WL_CONNECTED ) {
+ Serial.print( "\n\nCreating hotspot" );
+
+ WiFi.mode( WIFI_AP );
+ WiFi.softAPConfig( apIP, apIP, IPAddress( 255, 255, 255, 0 ) );
+ WiFi.softAP( ssid );
+
+ timeout = 5;
+
+ do {
+ delay( 500 );
+ Serial.print( "." );
+ timeout--;
+ } while ( timeout );
+ }
+ }
+
+ dnsServer.start( DNS_PORT, "*", apIP );
+
+ Serial.println( "\n\nWiFi parameters:" );
+ Serial.print( "Mode: " );
+ Serial.println( WiFi.getMode() == WIFI_AP ? "Station" : "Client" );
+ Serial.print( "IP address: " );
+ Serial.println( WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP() );
+
+ uint16_t tab1 = ESPUI.addControl( ControlType::Tab, "Settings 1", "Settings 1" );
+ uint16_t tab2 = ESPUI.addControl( ControlType::Tab, "Settings 2", "Settings 2" );
+ uint16_t tab3 = ESPUI.addControl( ControlType::Tab, "Settings 3", "Settings 3" );
+
+ // shown above all tabs
+ status = ESPUI.addControl( ControlType::Label, "Status:", "Stop", ControlColor::Turquoise );
+
+ uint16_t select1 = ESPUI.addControl( ControlType::Select, "Select:", "", ControlColor::Alizarin, tab1, &selectExample );
+ ESPUI.addControl( ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1 );
+ ESPUI.addControl( ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1 );
+ ESPUI.addControl( ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1 );
+
+ ESPUI.addControl( ControlType::Text, "Text Test:", "a Text Field", ControlColor::Alizarin, tab1, &textCall );
+
+ // tabbed controls
+ ESPUI.addControl( ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1 );
+ button1 = ESPUI.addControl( ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback );
+ ESPUI.addControl( ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample );
+ ESPUI.addControl( ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, tab2, &padExample );
+ ESPUI.addControl( ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample );
+ switchOne = ESPUI.addControl( ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample );
+ ESPUI.addControl( ControlType::Switcher, "Switch two", "", ControlColor::None, tab3, &otherSwitchExample );
+ ESPUI.addControl( ControlType::Slider, "Slider one", "30", ControlColor::Alizarin, tab1, &slider );
+ ESPUI.addControl( ControlType::Slider, "Slider two", "100", ControlColor::Alizarin, tab3, &slider );
+ ESPUI.addControl( ControlType::Number, "Number:", "50", ControlColor::Alizarin, tab3, &numberCall );
+
+ /*
+ * .begin loads and serves all files from PROGMEM directly.
+ * If you want to serve the files from SPIFFS use ESPUI.beginSPIFFS
+ * (.prepareFileSystem has to be run in an empty sketch before)
+ */
+
+ // Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
+ // ESPUI.sliderContinuous = true;
+
+ /*
+ * Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
+ * SECURE way of limiting access.
+ * Anyone who is able to sniff traffic will be able to intercept your password
+ * since it is transmitted in cleartext. Just add a string as username and
+ * password, for example begin("ESPUI Control", "username", "password")
+ */
+
+
+ ESPUI.begin("ESPUI Control");
+}
+
+void loop( void ) {
+ dnsServer.processNextRequest();
+
+ static long oldTime = 0;
+ static bool switchi = false;
+
+ if ( millis() - oldTime > 5000 ) {
+ switchi = !switchi;
+ ESPUI.updateControlValue( switchOne, switchi ? "1" : "0" );
+
+ oldTime = millis();
+ }
+}
diff --git a/library.json b/library.json
index 7dcba23..5646188 100644
--- a/library.json
+++ b/library.json
@@ -27,7 +27,7 @@
"frameworks": "arduino"
}
],
- "version": "1.6.3",
+ "version": "2.0.0",
"frameworks": "arduino",
"platforms": "*"
}
diff --git a/library.properties b/library.properties
index 5e5beec..849b7df 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=ESPUI
-version=1.6.3
+version=2.0.0
author=Lukas Bachschwell
maintainer=Lukas Bachschwell
sentence=ESP32 and ESP8266 Web Interface Library
diff --git a/src/ESPUI.cpp b/src/ESPUI.cpp
index 898095f..e093b47 100644
--- a/src/ESPUI.cpp
+++ b/src/ESPUI.cpp
@@ -6,26 +6,38 @@
#include "dataStyleCSS.h"
#include "dataControlsJS.h"
+#include "dataGraphJS.h"
#include "dataSliderJS.h"
+#include "dataTabbedcontentJS.h"
#include "dataZeptoJS.h"
#include
#include
+uint16_t Control::idCounter = 0;
+
// ################# Spiffs functions
#if defined(ESP32)
void listDir(const char *dirname, uint8_t levels) {
- Serial.printf("Listing directory: %s\n", dirname);
+ if (ESPUI.verbosity) {
+ Serial.printf("Listing directory: %s\n", dirname);
+ }
File root = SPIFFS.open(dirname);
if (!root) {
- Serial.println("Failed to open directory");
+ if (ESPUI.verbosity) {
+ Serial.println("Failed to open directory");
+ }
+
return;
}
if (!root.isDirectory()) {
- Serial.println("Not a directory");
+ if (ESPUI.verbosity) {
+ Serial.println("Not a directory");
+ }
+
return;
}
@@ -33,16 +45,21 @@ void listDir(const char *dirname, uint8_t levels) {
while (file) {
if (file.isDirectory()) {
- Serial.print(" DIR : ");
- Serial.println(file.name());
+ if (ESPUI.verbosity) {
+ Serial.print(" DIR : ");
+ Serial.println(file.name());
+ }
+
if (levels) {
listDir(file.name(), levels - 1);
}
} else {
- Serial.print(" FILE: ");
- Serial.print(file.name());
- Serial.print(" SIZE: ");
- Serial.println(file.size());
+ if (ESPUI.verbosity) {
+ Serial.print(" FILE: ");
+ Serial.print(file.name());
+ Serial.print(" SIZE: ");
+ Serial.println(file.size());
+ }
}
file = root.openNextFile();
@@ -56,6 +73,7 @@ void listDir(const char *dirname, uint8_t levels) {
String str = "";
Dir dir = SPIFFS.openDir("/");
+
while (dir.next()) {
Serial.print(" FILE: ");
Serial.print(dir.fileName());
@@ -71,55 +89,90 @@ void ESPUIClass::list() {
Serial.println("SPIFFS Mount Failed");
return;
}
+
listDir("/", 1);
#if defined(ESP32)
+
Serial.println(SPIFFS.totalBytes());
Serial.println(SPIFFS.usedBytes());
+
#else
FSInfo fs_info;
SPIFFS.info(fs_info);
Serial.println(fs_info.totalBytes);
Serial.println(fs_info.usedBytes);
+
#endif
}
void deleteFile(const char *path) {
- if (DEBUG_ESPUI) Serial.print(SPIFFS.exists(path));
+ if (ESPUI.verbosity) {
+ Serial.print(SPIFFS.exists(path));
+ }
+
if (!SPIFFS.exists(path)) {
- Serial.printf("File: %s does not exist, not deleting\n", path);
+ if (ESPUI.verbosity) {
+ Serial.printf("File: %s does not exist, not deleting\n", path);
+ }
+
return;
}
- Serial.printf("Deleting file: %s\n", path);
+ if (ESPUI.verbosity) {
+ Serial.printf("Deleting file: %s\n", path);
+ }
if (SPIFFS.remove(path)) {
- Serial.println("File deleted");
+ if (ESPUI.verbosity) {
+ Serial.println("File deleted");
+ }
} else {
- Serial.println("Delete failed");
+ if (ESPUI.verbosity) {
+ Serial.println("Delete failed");
+ }
}
}
void writeFile(const char *path, const char *data) {
- Serial.printf("Writing file: %s\n", path);
+ if (ESPUI.verbosity) {
+ Serial.printf("Writing file: %s\n", path);
+ }
File file = SPIFFS.open(path, FILE_WRITE);
+
if (!file) {
- Serial.println("Failed to open file for writing");
+ if (ESPUI.verbosity) {
+ Serial.println("Failed to open file for writing");
+ }
+
return;
}
+
#if defined(ESP32)
+
if (file.print(data)) {
- Serial.println("File written");
+ if (ESPUI.verbosity) {
+ Serial.println("File written");
+ }
} else {
- Serial.println("Write failed");
+ if (ESPUI.verbosity) {
+ Serial.println("Write failed");
+ }
}
+
#else
+
if (file.print(FPSTR(data))) {
- Serial.println("File written");
+ if (ESPUI.verbosity) {
+ Serial.println("File written");
+ }
} else {
- Serial.println("Write failed");
+ if (ESPUI.verbosity) {
+ Serial.println("Write failed");
+ }
}
+
#endif
file.close();
}
@@ -129,20 +182,34 @@ void writeFile(const char *path, const char *data) {
void ESPUIClass::prepareFileSystem() {
// this function should only be used once
- Serial.println("About to prepare filesystem...");
+ if (this->verbosity) {
+ Serial.println("About to prepare filesystem...");
+ }
#if defined(ESP32)
SPIFFS.format();
+
if (!SPIFFS.begin(true)) {
- Serial.println("SPIFFS Mount Failed");
+ if (this->verbosity) {
+ Serial.println("SPIFFS Mount Failed");
+ }
+
return;
}
- listDir("/", 1);
- Serial.println("SPIFFS Mount ESP32 Done");
+
+ if (this->verbosity) {
+ listDir("/", 1);
+ Serial.println("SPIFFS Mount ESP32 Done");
+ }
+
#else
SPIFFS.format();
SPIFFS.begin();
- Serial.println("SPIFFS Mount ESP8266 Done");
+
+ if (this->verbosity) {
+ Serial.println("SPIFFS Mount ESP8266 Done");
+ }
+
#endif
deleteFile("/index.htm");
@@ -153,8 +220,12 @@ void ESPUIClass::prepareFileSystem() {
deleteFile("/js/zepto.min.js");
deleteFile("/js/controls.js");
deleteFile("/js/slider.js");
+ deleteFile("/js/graph.js");
+ deleteFile("/js/tabbedcontent.js");
- Serial.println("Cleanup done");
+ if (this->verbosity) {
+ Serial.println("Cleanup done");
+ }
// Now write
writeFile("/index.htm", HTML_INDEX);
@@ -165,454 +236,384 @@ void ESPUIClass::prepareFileSystem() {
writeFile("/js/zepto.min.js", JS_ZEPTO);
writeFile("/js/controls.js", JS_CONTROLS);
writeFile("/js/slider.js", JS_SLIDER);
+ writeFile("/js/graph.js", JS_GRAPH);
- Serial.println("Done Initializing filesystem :-)");
+ writeFile("/js/tabbedcontent.js", JS_TABBEDCONTENT);
+
+ if (this->verbosity) {
+ Serial.println("Done Initializing filesystem :-)");
+ }
#if defined(ESP32)
- if (DEBUG_ESPUI) listDir("/", 1);
+
+ if (this->verbosity) {
+ listDir("/", 1);
+ }
+
#endif
SPIFFS.end();
}
// Handle Websockets Communication
-void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
- AwsEventType type, void *arg, uint8_t *data, size_t len) {
+void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
switch (type) {
- case WS_EVT_DISCONNECT: {
- if (DEBUG_ESPUI) Serial.printf("Disconnected!\n");
- break;
+ case WS_EVT_DISCONNECT: {
+ if (ESPUI.verbosity) {
+ Serial.printf("Disconnected!\n");
}
- case WS_EVT_PONG: {
- if (DEBUG_ESPUI) Serial.printf("Received PONG!\n");
- break;
+
+ break;
+ }
+
+ case WS_EVT_PONG: {
+ if (ESPUI.verbosity) {
+ Serial.printf("Received PONG!\n");
}
- case WS_EVT_ERROR: {
- if (DEBUG_ESPUI) Serial.printf("WebSocket Error!\n");
- break;
+
+ break;
+ }
+
+ case WS_EVT_ERROR: {
+ if (ESPUI.verbosity) {
+ Serial.printf("WebSocket Error!\n");
}
- case WS_EVT_CONNECT: {
- if (DEBUG_ESPUI) {
- Serial.print("Connected: ");
- Serial.println(client->id());
+
+ break;
+ }
+
+ case WS_EVT_CONNECT: {
+ if (ESPUI.verbosity) {
+ Serial.print("Connected: ");
+ Serial.println(client->id());
+ }
+
+ ESPUI.jsonDom(client);
+
+ if (ESPUI.verbosity) {
+ Serial.println("JSON Data Sent to Client!");
+ }
+ } break;
+
+ case WS_EVT_DATA: {
+ String msg = "";
+ msg.reserve(len + 1);
+
+ for (size_t i = 0; i < len; i++) {
+ msg += (char)data[i];
+ }
+
+ uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
+
+ if (ESPUI.verbosity >= Verbosity::VerboseJSON) {
+ Serial.print("WS rec: ");
+ Serial.println(msg);
+ Serial.print("WS recognised ID: ");
+ Serial.println(id);
+ }
+
+ Control *c = ESPUI.getControl(id);
+
+ if (c == nullptr) {
+ if (ESPUI.verbosity) {
+ Serial.print("No control found for ID ");
+ Serial.println(id);
}
- ESPUI.jsonDom(client);
- if (DEBUG_ESPUI) {
- Serial.println("JSON Data Sent to Client!");
- }
- } break;
- case WS_EVT_DATA: {
- String msg = "";
- for (size_t i = 0; i < len; i++) {
- msg += (char)data[i];
+ return;
+ }
+
+ if (c->callback == nullptr) {
+ if (ESPUI.verbosity) {
+ Serial.print("No callback found for ID ");
+ Serial.println(id);
}
- int id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
- if (id >= ESPUI.cIndex) {
- if (DEBUG_ESPUI) Serial.println("Maleformated id in websocket message");
- return;
+ return;
+ }
+
+ if (msg.startsWith("bdown:")) {
+ c->callback(c, B_DOWN);
+ } else if (msg.startsWith("bup:")) {
+ c->callback(c, B_UP);
+ } else if (msg.startsWith("pfdown:")) {
+ c->callback(c, P_FOR_DOWN);
+ } else if (msg.startsWith("pfup:")) {
+ c->callback(c, P_FOR_UP);
+ } else if (msg.startsWith("pldown:")) {
+ c->callback(c, P_LEFT_DOWN);
+ } else if (msg.startsWith("plup:")) {
+ c->callback(c, P_LEFT_UP);
+ } else if (msg.startsWith("prdown:")) {
+ c->callback(c, P_RIGHT_DOWN);
+ } else if (msg.startsWith("prup:")) {
+ c->callback(c, P_RIGHT_UP);
+ } else if (msg.startsWith("pbdown:")) {
+ c->callback(c, P_BACK_DOWN);
+ } else if (msg.startsWith("pbup:")) {
+ c->callback(c, P_BACK_UP);
+ } else if (msg.startsWith("pcdown:")) {
+ c->callback(c, P_CENTER_DOWN);
+ } else if (msg.startsWith("pcup:")) {
+ c->callback(c, P_CENTER_UP);
+ } else if (msg.startsWith("sactive:")) {
+ c->value = "1";
+ ESPUI.updateControl(c, client->id());
+ c->callback(c, S_ACTIVE);
+ } else if (msg.startsWith("sinactive:")) {
+ c->value = "0";
+ ESPUI.updateControl(c, client->id());
+ c->callback(c, S_INACTIVE);
+ } else if (msg.startsWith("slvalue:")) {
+ c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
+ ESPUI.updateControl(c, client->id());
+ c->callback(c, SL_VALUE);
+ } else if (msg.startsWith("nvalue:")) {
+ c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
+ ESPUI.updateControl(c, client->id());
+ c->callback(c, N_VALUE);
+ } else if (msg.startsWith("tvalue:")) {
+ c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
+ ESPUI.updateControl(c, client->id());
+ c->callback(c, T_VALUE);
+ } else if (msg.startsWith("svalue:")) {
+ c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
+ ESPUI.updateControl(c, client->id());
+ c->callback(c, S_VALUE);
+ } else {
+ if (ESPUI.verbosity) {
+ Serial.println("Malformated message from the websocket");
}
+ }
+ } break;
- Control *c =
- ESPUI.controls[msg.substring(msg.lastIndexOf(':') + 1).toInt()];
-
- if (msg.startsWith("bdown:")) {
- c->callback(*c, B_DOWN);
- } else if (msg.startsWith("bup:")) {
- c->callback(*c, B_UP);
- } else if (msg.startsWith("pfdown:")) {
- c->callback(*c, P_FOR_DOWN);
- } else if (msg.startsWith("pfup:")) {
- c->callback(*c, P_FOR_UP);
- } else if (msg.startsWith("pldown:")) {
- c->callback(*c, P_LEFT_DOWN);
- } else if (msg.startsWith("plup:")) {
- c->callback(*c, P_LEFT_UP);
- } else if (msg.startsWith("prdown:")) {
- c->callback(*c, P_RIGHT_DOWN);
- } else if (msg.startsWith("prup:")) {
- c->callback(*c, P_RIGHT_UP);
- } else if (msg.startsWith("pbdown:")) {
- c->callback(*c, P_BACK_DOWN);
- } else if (msg.startsWith("pbup:")) {
- c->callback(*c, P_BACK_UP);
- } else if (msg.startsWith("pcdown:")) {
- c->callback(*c, P_CENTER_DOWN);
- } else if (msg.startsWith("pcup:")) {
- c->callback(*c, P_CENTER_UP);
- } else if (msg.startsWith("sactive:")) {
- ESPUI.updateSwitcher(c->id, true);
- c->callback(*c, S_ACTIVE);
- } else if (msg.startsWith("sinactive:")) {
- ESPUI.updateSwitcher(c->id, false);
- c->callback(*c, S_INACTIVE);
- } else if (msg.startsWith("slvalue:")) {
- int value =
- msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')).toInt();
- ESPUI.updateSlider(c->id, value, client->id());
- c->callback(*c, SL_VALUE);
- } else if (msg.startsWith("nvalue:")) {
- int value =
- msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')).toInt();
- ESPUI.updateNumber(c->id, value, client->id());
- c->callback(*c, N_VALUE);
- } else if (msg.startsWith("tvalue:")) {
- String value =
- msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
- ESPUI.updateText(c->id, value, client->id());
- c->callback(*c, T_VALUE);
- }
- } break;
- default:
- break;
+ default:
+ break;
}
}
-int ESPUIClass::label(const char *label, int color, String value) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
+uint16_t ESPUIClass::addControl(ControlType type, const char *label, String value, ControlColor color, uint16_t parentControl,
+ void (*callback)(Control *, int)) {
+ Control *control = new Control(type, label, callback, value, color, parentControl);
+
+ if (this->controls == nullptr) {
+ this->controls = control;
+ } else {
+ Control *iterator = this->controls;
+
+ while (iterator->next != nullptr) {
+ iterator = iterator->next;
+ }
+
+ iterator->next = control;
}
- Control *newL = new Control();
- newL->type = UI_LABEL;
- newL->label = label;
- newL->color = color;
- if (value != "")
- newL->value = value; // Init with labeltext
- else
- newL->value = String(label);
- newL->callback = NULL;
- newL->id = cIndex;
- controls[cIndex] = newL;
- cIndex++;
- return cIndex - 1;
+ return control->id;
}
-int ESPUIClass::graph(const char *label, int color) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
+uint16_t ESPUIClass::label(const char *label, ControlColor color, String value) { return addControl(ControlType::Label, label, value, color); }
+
+uint16_t ESPUIClass::graph(const char *label, ControlColor color) { return addControl(ControlType::Graph, label, "", color); }
+
+uint16_t ESPUIClass::slider(const char *label, void (*callback)(Control *, int), ControlColor color, int value, int min, int max) {
+ uint16_t sliderId = addControl(ControlType::Slider, label, String(value), color, Control::noParent, callback);
+ addControl(ControlType::Min, label, String(min), ControlColor::None, sliderId);
+ addControl(ControlType::Max, label, String(max), ControlColor::None, sliderId);
+
+ return sliderId;
+}
+
+uint16_t ESPUIClass::button(const char *label, void (*callback)(Control *, int), ControlColor color, String value) {
+ return addControl(ControlType::Button, label, value, color, Control::noParent, callback);
+}
+
+uint16_t ESPUIClass::switcher(const char *label, void (*callback)(Control *, int), ControlColor color, bool startState) {
+ return addControl(ControlType::Switcher, label, startState ? "1" : "0", color, Control::noParent, callback);
+}
+
+uint16_t ESPUIClass::pad(const char *label, void (*callback)(Control *, int), ControlColor color) {
+ return addControl(ControlType::Pad, label, "", color, Control::noParent, callback);
+}
+uint16_t ESPUIClass::padWithCenter(const char *label, void (*callback)(Control *, int), ControlColor color) {
+ return addControl(ControlType::PadWithCenter, label, "", color, Control::noParent, callback);
+}
+
+uint16_t ESPUIClass::number(const char *label, void (*callback)(Control *, int), ControlColor color, int number, int min, int max) {
+ uint16_t numberId = addControl(ControlType::Number, label, String(number), color, Control::noParent, callback);
+ addControl(ControlType::Min, label, String(min), ControlColor::None, numberId);
+ addControl(ControlType::Max, label, String(max), ControlColor::None, numberId);
+ return numberId;
+}
+
+uint16_t ESPUIClass::gauge(const char *label, ControlColor color, int number, int min, int max) {
+ uint16_t numberId = addControl(ControlType::Gauge, label, String(number), color, Control::noParent);
+ addControl(ControlType::Min, label, String(min), ControlColor::None, numberId);
+ addControl(ControlType::Max, label, String(max), ControlColor::None, numberId);
+ return numberId;
+}
+
+uint16_t ESPUIClass::accelerometer(const char *label, void (*callback)(Control *, int), ControlColor color) {
+ return addControl(ControlType::Accel, label, "", color, Control::noParent, callback);
+}
+
+uint16_t ESPUIClass::text(const char *label, void (*callback)(Control *, int), ControlColor color, String value) {
+ return addControl(ControlType::Text, label, value, color, Control::noParent, callback);
+}
+
+Control *ESPUIClass::getControl(uint16_t id) {
+ Control *control = this->controls;
+
+ while (control != nullptr) {
+ if (control->id == id) {
+ return control;
+ }
+
+ control = control->next;
}
- Control *newG = new Control();
- newG->type = UI_GRAPH;
- newG->label = label;
- newG->color = color;
- newG->id = cIndex;
- controls[cIndex] = newG;
- cIndex++;
- return cIndex - 1;
+ return nullptr;
}
-// TODO: this still needs a range setting
-int ESPUIClass::slider(const char *label, void (*callBack)(Control, int),
- int color, String value) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
+void ESPUIClass::updateControl(Control *control, int clientId) {
+ if (!control) {
+ return;
}
- Control *newSL = new Control();
- newSL->type = UI_SLIDER;
- newSL->label = label;
- newSL->color = color;
- if (value != "")
- newSL->value = value;
- else
- newSL->value = ""; // TODO: init with half value
- newSL->callback = callBack;
- newSL->id = cIndex;
- controls[cIndex] = newSL;
- cIndex++;
- return cIndex - 1;
-}
+ String json;
+ DynamicJsonDocument document(jsonUpdateDocumentSize);
+ JsonObject root = document.to();
-int ESPUIClass::button(const char *label, void (*callBack)(Control, int),
- int color, String value) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
+ root["type"] = (int)control->type + ControlType::UpdateOffset;
+ root["value"] = control->value;
+ root["id"] = control->id;
+ root["color"] = (int)control->color;
+ serializeJson(document, json);
+
+ if (this->verbosity >= Verbosity::VerboseJSON) {
+ Serial.println(json);
}
- Control *newB = new Control();
- newB->type = UI_BUTTON;
- newB->label = label;
- newB->color = color;
-
- if (value != "")
- newB->value = value; // Init with labeltext
- else
- newB->value = String(label);
-
- newB->callback = callBack;
- newB->id = cIndex;
- controls[cIndex] = newB;
- cIndex++;
- return cIndex - 1;
-}
-
-int ESPUIClass::switcher(const char *label, bool startState,
- void (*callBack)(Control, int), int color) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
- }
-
- Control *newS = new Control();
- newS->type = UI_SWITCHER;
- newS->label = label;
- newS->color = color;
- newS->value = String(startState);
- newS->callback = callBack;
- newS->id = cIndex;
- controls[cIndex] = newS;
- cIndex++;
- return cIndex - 1;
-}
-
-int ESPUIClass::pad(const char *label, bool center,
- void (*callBack)(Control, int), int color) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
- }
-
- Control *newP = new Control();
- if (center)
- newP->type = UI_CPAD;
- else
- newP->type = UI_PAD;
- newP->label = label;
- newP->color = color;
- newP->callback = callBack;
- newP->id = cIndex;
- controls[cIndex] = newP;
- cIndex++;
- return cIndex - 1;
-}
-
-// TODO: min and max need to be saved, they also need to be sent to the frontend
-int ESPUIClass::number(const char *label, void (*callBack)(Control, int),
- int color, int number, int min, int max) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
- }
-
- Control *newN = new Control();
- newN->type = UI_NUMBER;
- newN->label = label;
- newN->color = color;
- newN->value = String(number);
- newN->callback = callBack;
- newN->id = cIndex;
- controls[cIndex] = newN;
- cIndex++;
- return cIndex - 1;
-}
-
-int ESPUIClass::text(const char *label, void (*callBack)(Control, int),
- int color, String value) {
- if (labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element " + String(label) +
- " exists, skipping creating element!");
- return -1;
- }
-
- Control *newT = new Control();
- newT->type = UI_TEXT_INPUT;
- newT->label = label;
- newT->color = color;
- newT->value = value;
- newT->callback = callBack;
- newT->id = cIndex;
- controls[cIndex] = newT;
- cIndex++;
- return cIndex - 1;
-}
-
-void ESPUIClass::print(int id, String value) {
- if (id < cIndex && controls[id]->type == UI_LABEL) {
- controls[id]->value = value;
- String json;
- StaticJsonBuffer<200> jsonBuffer;
- JsonObject &root = jsonBuffer.createObject();
- root["type"] = UPDATE_LABEL;
- root["value"] = value;
- root["id"] = String(id);
- root.printTo(json);
+ if (clientId < 0) {
this->ws->textAll(json);
- } else {
- if (DEBUG_ESPUI)
- Serial.println(String("Error: ") + String(id) + String(" is no label"));
- }
-}
-
-void ESPUIClass::print(String label, String value) {
- if (!labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element does not " + String(label) +
- " exist, cannot update!");
return;
}
- print(getIdByLabel(label), value);
-}
-
-void ESPUIClass::updateSlider(int id, int nValue, int clientId) {
- if (id < cIndex && controls[id]->type == UI_SLIDER) {
- controls[id]->value = nValue;
- String json;
- StaticJsonBuffer<200> jsonBuffer;
- JsonObject &root = jsonBuffer.createObject();
- root["type"] = UPDATE_SLIDER;
- root["value"] = nValue;
- root["id"] = String(id);
- root.printTo(json);
- textThem(json, clientId);
- } else {
- if (DEBUG_ESPUI)
- Serial.println(String("Error: ") + String(id) + String(" is no slider"));
- }
-}
-
-void ESPUIClass::updateSlider(String label, int nValue, int clientId) {
- if (!labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element does not " + String(label) +
- " exist, cannot update!");
- return;
- }
- updateSlider(getIdByLabel(label), nValue, clientId);
-}
-
-void ESPUIClass::updateSwitcher(int id, bool nValue, int clientId) {
- if (id < cIndex && controls[id]->type == UI_SWITCHER) {
- controls[id]->value = nValue ? 1 : 0;
- String json;
- StaticJsonBuffer<200> jsonBuffer;
- JsonObject &root = jsonBuffer.createObject();
- root["type"] = UPDATE_SWITCHER;
- root["value"] = nValue ? 1 : 0;
- root["id"] = String(id);
- root.printTo(json);
- textThem(json, clientId);
- } else {
- if (DEBUG_ESPUI)
- Serial.println(String("Error: ") + String(id) +
- String(" is no switcher"));
- }
-}
-
-void ESPUIClass::updateSwitcher(String label, bool nValue, int clientId) {
- if (!labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element does not " + String(label) +
- " exist, cannot update!");
- return;
- }
- updateSwitcher(getIdByLabel(label), nValue, clientId);
-}
-
-void ESPUIClass::updateNumber(int id, int number, int clientId) {
- if (id < cIndex && controls[id]->type == UI_NUMBER) {
- controls[id]->value = number;
- String json;
- StaticJsonBuffer<200> jsonBuffer;
- JsonObject &root = jsonBuffer.createObject();
- root["type"] = UPDATE_NUMBER;
- root["value"] = String(number);
- root["id"] = String(id);
- root.printTo(json);
- textThem(json, clientId);
- } else {
- if (DEBUG_ESPUI)
- Serial.println(String("Error: ") + String(id) + String(" is no number"));
- }
-}
-
-void ESPUIClass::updateNumber(String label, int number, int clientId) {
- if (!labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element does not " + String(label) +
- " exist, cannot update!");
- return;
- }
- updateNumber(getIdByLabel(label), number, clientId);
-}
-
-void ESPUIClass::updateText(int id, String text, int clientId) {
- if (id < cIndex && controls[id]->type == UI_TEXT_INPUT) {
- controls[id]->value = text;
- String json;
- StaticJsonBuffer<200> jsonBuffer;
- JsonObject &root = jsonBuffer.createObject();
- root["type"] = UPDATE_TEXT_INPUT;
- root["value"] = String(text);
- root["id"] = String(id);
- root.printTo(json);
- textThem(json, clientId);
- } else {
- if (DEBUG_ESPUI)
- Serial.println(String("Error: ") + String(id) + String(" is no number"));
- }
-}
-
-void ESPUIClass::updateText(String label, String text, int clientId) {
- if (!labelExists(label)) {
- if (DEBUG_ESPUI)
- Serial.println("UI ERROR: Element does not " + String(label) +
- " exist, cannot update!");
- return;
- }
- updateText(getIdByLabel(label), text, clientId);
-}
-
-// This is a hacky workaround because ESPAsyncWebServer does not have a function
-// like this and it's clients array is private
-void ESPUIClass::textThem(String text, int clientId) {
+ // This is a hacky workaround because ESPAsyncWebServer does not have a
+ // function like this and it's clients array is private
int tryId = 0;
+
for (int count = 0; count < this->ws->count();) {
if (this->ws->hasClient(tryId)) {
if (clientId != tryId) {
- this->ws->client(tryId)->text(text);
+ this->ws->client(tryId)->text(json);
+
+ if (this->verbosity >= Verbosity::VerboseJSON) {
+ Serial.println(json);
+ }
}
+
count++;
}
+
tryId++;
}
}
-int ESPUIClass::getIdByLabel(String label) {
- for (int i = 0; i < cIndex; i++) {
- if (String(controls[i]->label) == label) return i;
+void ESPUIClass::updateControl(uint16_t id, int clientId) {
+ Control *control = getControl(id);
+
+ if (!control) {
+ if (this->verbosity) {
+ Serial.println(String("Error: There is no control with ID ") + String(id));
+ }
+ return;
}
- return -1; // failed, nonexistant
+
+ updateControl(control, clientId);
}
-bool ESPUIClass::labelExists(String label) {
- for (int i = 0; i < cIndex; i++) {
- if (String(controls[i]->label) == label) return true;
+void ESPUIClass::updateControlValue(Control *control, String value, int clientId) {
+ if (!control) {
+ return;
}
- return false;
+
+ control->value = value;
+ updateControl(control, clientId);
}
+void ESPUIClass::updateControlValue(uint16_t id, String value, int clientId) {
+ Control *control = getControl(id);
+
+ if (!control) {
+ if (this->verbosity) {
+ Serial.println(String("Error: There is no control with ID ") + String(id));
+ }
+ return;
+ }
+
+ updateControlValue(control, value, clientId);
+}
+
+void ESPUIClass::print(uint16_t id, String value) { updateControlValue(id, value); }
+
+void ESPUIClass::updateLabel(uint16_t id, String value) { updateControlValue(id, value); }
+
+void ESPUIClass::updateSlider(uint16_t id, int nValue, int clientId) { updateControlValue(id, String(nValue), clientId); }
+
+void ESPUIClass::updateSwitcher(uint16_t id, bool nValue, int clientId) { updateControlValue(id, String(nValue ? "1" : "0"), clientId); }
+
+void ESPUIClass::updateNumber(uint16_t id, int number, int clientId) { updateControlValue(id, String(number), clientId); }
+
+void ESPUIClass::updateText(uint16_t id, String text, int clientId) { updateControlValue(id, text, clientId); }
+
+void ESPUIClass::updateSelect(uint16_t id, String text, int clientId) { updateControlValue(id, text, clientId); }
+
+void ESPUIClass::updateGauge(uint16_t id, int number, int clientId) { updateControlValue(id, String(number), clientId); }
+
+void ESPUIClass::clearGraph(uint16_t id, int clientId) {}
+
+void ESPUIClass::addGraphPoint(uint16_t id, int nValue, int clientId) {
+ Control *control = getControl(id);
+ if (!control) {
+ return;
+ }
+
+ String json;
+ DynamicJsonDocument document(jsonUpdateDocumentSize);
+ JsonObject root = document.to();
+
+ root["type"] = (int)ControlType::GraphPoint;
+ root["value"] = nValue;
+ root["id"] = control->id;
+ serializeJson(document, json);
+
+ if (this->verbosity >= Verbosity::VerboseJSON) {
+ Serial.println(json);
+ }
+
+ if (clientId < 0) {
+ this->ws->textAll(json);
+ return;
+ }
+ // This is a hacky workaround because ESPAsyncWebServer does not have a
+ // function like this and it's clients array is private
+ int tryId = 0;
+
+ for (int count = 0; count < this->ws->count();) {
+ if (this->ws->hasClient(tryId)) {
+ if (clientId != tryId) {
+ this->ws->client(tryId)->text(json);
+
+ if (this->verbosity >= Verbosity::VerboseJSON) {
+ Serial.println(json);
+ }
+ }
+
+ count++;
+ }
+
+ tryId++;
+ }
+}
/*
Convert & Transfer Arduino elements to JSON elements
Initially this function used to send the control element data individually.
@@ -621,109 +622,126 @@ sent as one blob at the beginning. Therefore a new type is used as well
*/
void ESPUIClass::jsonDom(AsyncWebSocketClient *client) {
String json;
- DynamicJsonBuffer jsonBuffer(2000);
- JsonObject &root = jsonBuffer.createObject();
- root["type"] = UI_INITIAL_GUI;
- JsonArray &items = jsonBuffer.createArray();
+ DynamicJsonDocument document(jsonInitialDocumentSize);
+ document["type"] = (int)UI_INITIAL_GUI;
+ document["sliderContinuous"] = sliderContinuous;
+ JsonArray items = document.createNestedArray("controls");
- for (int i = -1; i < cIndex; i++) {
- JsonObject &item = jsonBuffer.createObject();
+ Control *control = this->controls;
- if (i == -1) {
- item["type"] = UI_TITEL;
- item["label"] = String(ui_title);
- } else {
- item["type"] = controls[i]->type;
- item["label"] = String(controls[i]->label);
- item["value"] = String(controls[i]->value);
- item["color"] = String(controls[i]->color);
- item["id"] = String(i);
+ JsonObject titleItem = items.createNestedObject();
+ titleItem["type"] = (int)UI_TITLE;
+ titleItem["label"] = ui_title;
+
+ while (control != nullptr) {
+ JsonObject item = items.createNestedObject();
+
+ item["id"] = String(control->id);
+ item["type"] = (int)control->type;
+ item["label"] = control->label;
+ item["value"] = String(control->value);
+ item["color"] = (int)control->color;
+
+ if (control->parentControl != Control::noParent) {
+ item["parentControl"] = String(control->parentControl);
}
- items.add(item);
+
+ // special case for selects: to preselect an option, you have to add
+ // "selected" to
+ if (control->type == ControlType::Option) {
+ if (ESPUI.getControl(control->parentControl)->value == control->value) {
+ item["selected"] = "selected";
+ } else {
+ item["selected"] = "";
+ }
+ }
+
+ control = control->next;
}
// Send as one big bunch
- root["controls"] = items;
- root.printTo(json);
+ serializeJson(document, json);
+
+ if (this->verbosity >= Verbosity::VerboseJSON) {
+ Serial.println(json);
+ }
+
client->text(json);
}
-void ESPUIClass::beginSPIFFS(const char *_title) {
- begin(_title, NULL, NULL);
- basicAuth = false;
-}
-
-void ESPUIClass::beginSPIFFS(const char *_title, const char *username,
- const char *password) {
+void ESPUIClass::beginSPIFFS(const char *_title, const char *username, const char *password) {
ui_title = _title;
+ this->basicAuthUsername = username;
+ this->basicAuthPassword = password;
+
+ if (username == nullptr && password == nullptr) {
+ basicAuth = false;
+ } else {
+ basicAuth = true;
+ }
+
server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/ws");
if (!SPIFFS.begin()) {
- Serial.println(
- "SPIFFS Mount Failed, PLEASE CHECK THE README ON HOW TO "
- "PREPARE YOUR ESP!!!!!!!");
+ if (ESPUI.verbosity) {
+ Serial.println("SPIFFS Mount Failed, PLEASE CHECK THE README ON HOW TO PREPARE YOUR ESP!!!!!!!");
+ }
+
return;
}
- listDir("/", 1);
+
+ if (ESPUI.verbosity) {
+ listDir("/", 1);
+ }
if (!SPIFFS.exists("/index.htm")) {
- Serial.println(
- "Please read the README!!!!!!!, Make sure to "
- "ESPUI.prepareFileSystem() once in an empty sketch");
+ if (ESPUI.verbosity) {
+ Serial.println("Please read the README!!!!!!!, Make sure to ESPUI.prepareFileSystem() once in an empty sketch");
+ }
+
return;
}
ws->onEvent(onWsEvent);
server->addHandler(ws);
- if (basicAuth && username != NULL && password != NULL) {
- basicAuthPassword = password;
- basicAuthUsername = username;
- basicAuth = true;
- if (WS_AUTHENTICATION)
- ws->setAuthentication(this->basicAuthUsername, this->basicAuthPassword);
- server->serveStatic("/", SPIFFS, "/")
- .setDefaultFile("index.htm")
- .setAuthentication(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword);
+ if (basicAuth) {
+ if (WS_AUTHENTICATION) {
+ ws->setAuthentication(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword);
+ }
- } else if (basicAuth) {
- Serial.println(
- "Could not enable BasicAuth: Username or password are not set");
+ server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(username, password);
} else {
server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
}
// Heap for general Servertest
server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- request->send(200, "text/plain",
- String(ESP.getFreeHeap()) + " In SPIFFSmode");
+ }
+
+ request->send(200, "text/plain", String(ESP.getFreeHeap()) + " In SPIFFSmode");
});
- server->onNotFound(
- [](AsyncWebServerRequest *request) { request->send(404); });
+ server->onNotFound([](AsyncWebServerRequest *request) { request->send(404); });
server->begin();
- if (DEBUG_ESPUI) Serial.println("UI Initialized");
+
+ if (this->verbosity) {
+ Serial.println("UI Initialized");
+ }
}
-void ESPUIClass::begin(const char *_title) {
- begin(_title, NULL, NULL);
- basicAuth = false;
-}
+void ESPUIClass::begin(const char *_title, const char *username, const char *password) {
+ basicAuthUsername = username;
+ basicAuthPassword = password;
-void ESPUIClass::begin(const char *_title, const char *username,
- const char *password) {
- if (basicAuth && username != NULL && password != NULL) {
- basicAuthPassword = password;
- basicAuthUsername = username;
+ if (username != nullptr && password != nullptr) {
basicAuth = true;
- } else if (basicAuth) {
- Serial.println(
- "Could not enable BasicAuth: Username or password are not set");
+ } else {
+ basicAuth = false;
}
ui_title = _title;
@@ -734,56 +752,66 @@ void ESPUIClass::begin(const char *_title, const char *username,
ws->onEvent(onWsEvent);
server->addHandler(ws);
- if (basicAuth && username != NULL && password != NULL) {
- basicAuthPassword = password;
- basicAuthUsername = username;
- basicAuth = true;
- if (WS_AUTHENTICATION)
- ws->setAuthentication(this->basicAuthUsername, this->basicAuthPassword);
-
- } else if (basicAuth) {
- Serial.println(
- "Could not enable BasicAuth: Username or password are not set");
- }
+ if (basicAuth && WS_AUTHENTICATION)
+ ws->setAuthentication(username, password);
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- AsyncWebServerResponse *response =
- request->beginResponse_P(200, "text/html", HTML_INDEX);
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML_INDEX);
request->send(response);
});
// Javascript files
server->on("/js/zepto.min.js", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- AsyncWebServerResponse *response = request->beginResponse_P(
- 200, "application/javascript", JS_ZEPTO_GZIP, sizeof(JS_ZEPTO_GZIP));
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", JS_ZEPTO_GZIP, sizeof(JS_ZEPTO_GZIP));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
server->on("/js/controls.js", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- AsyncWebServerResponse *response =
- request->beginResponse_P(200, "application/javascript",
- JS_CONTROLS_GZIP, sizeof(JS_CONTROLS_GZIP));
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", JS_CONTROLS_GZIP, sizeof(JS_CONTROLS_GZIP));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
server->on("/js/slider.js", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- AsyncWebServerResponse *response = request->beginResponse_P(
- 200, "application/javascript", JS_SLIDER_GZIP, sizeof(JS_SLIDER_GZIP));
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", JS_SLIDER_GZIP, sizeof(JS_SLIDER_GZIP));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ server->on("/js/graph.js", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
+ return request->requestAuthentication();
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", JS_GRAPH_GZIP, sizeof(JS_GRAPH_GZIP));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ server->on("/js/tabbedcontent.js", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
+ return request->requestAuthentication();
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", JS_TABBEDCONTENT_GZIP, sizeof(JS_TABBEDCONTENT_GZIP));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
@@ -791,40 +819,43 @@ void ESPUIClass::begin(const char *_title, const char *username,
// Stylesheets
server->on("/css/style.css", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- AsyncWebServerResponse *response = request->beginResponse_P(
- 200, "text/css", CSS_STYLE_GZIP, sizeof(CSS_STYLE_GZIP));
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", CSS_STYLE_GZIP, sizeof(CSS_STYLE_GZIP));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
- server->on(
- "/css/normalize.css", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
- return request->requestAuthentication();
- AsyncWebServerResponse *response = request->beginResponse_P(
- 200, "text/css", CSS_NORMALIZE_GZIP, sizeof(CSS_NORMALIZE_GZIP));
- response->addHeader("Content-Encoding", "gzip");
- request->send(response);
- });
+ server->on("/css/normalize.css", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
+ return request->requestAuthentication();
+ }
+
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", CSS_NORMALIZE_GZIP, sizeof(CSS_NORMALIZE_GZIP));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
// Heap for general Servertest
server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
- if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
- ESPUI.basicAuthPassword))
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword)) {
return request->requestAuthentication();
- request->send(200, "text/plain",
- String(ESP.getFreeHeap()) + " In Memorymode");
+ }
+
+ request->send(200, "text/plain", String(ESP.getFreeHeap()) + " In Memorymode");
});
- server->onNotFound(
- [](AsyncWebServerRequest *request) { request->send(404); });
+ server->onNotFound([](AsyncWebServerRequest *request) { request->send(404); });
server->begin();
- if (DEBUG_ESPUI) Serial.println("UI Initialized");
+
+ if (this->verbosity) {
+ Serial.println("UI Initialized");
+ }
}
-ESPUIClass ESPUI;
+void ESPUIClass::setVerbosity(Verbosity v) { this->verbosity = v; }
+
+ESPUIClass ESPUI;
\ No newline at end of file
diff --git a/src/ESPUI.h b/src/ESPUI.h
index cb459e7..a0b04ba 100644
--- a/src/ESPUI.h
+++ b/src/ESPUI.h
@@ -11,10 +11,10 @@
#if defined(ESP32)
-#include
-#include
#include "SPIFFS.h"
#include "WiFi.h"
+#include
+#include
#else
@@ -31,42 +31,113 @@
#endif
-typedef struct Control {
- unsigned int type;
- unsigned int id; // just mirroring the id here for practical reasons
- const char *label;
- void (*callback)(Control, int);
- String value;
- unsigned int color;
-} Control;
-
// Message Types (and control types)
-#define UI_INITIAL_GUI 100
-#define UI_TITEL 0
-#define UI_LABEL 1
-#define UPDATE_LABEL 6
+enum ControlType : uint8_t {
+ // fixed controls
+ Title = 0,
-#define UI_BUTTON 2
+ // updatable controls
+ Pad,
+ PadWithCenter,
+ Button,
+ Label,
+ Switcher,
+ Slider,
+ Number,
+ Text,
+ Graph,
+ GraphPoint,
+ Tab,
+ Select,
+ Option,
+ Min,
+ Max,
+ Step,
+ Gauge,
+ Accel,
-#define UI_SWITCHER 3
-#define UPDATE_SWITCHER 7
+ UpdateOffset = 100,
+ UpdatePad = 101,
+ UpdatePadWithCenter,
+ ButtonButton,
+ UpdateLabel,
+ UpdateSwitcher,
+ UpdateSlider,
+ UpdateNumber,
+ UpdateText,
+ ClearGraph,
+ UpdateTab,
+ UpdateSelection,
+ UpdateOption,
+ UpdateMin,
+ UpdateMax,
+ UpdateStep,
+ UpdateGauge,
+ UpdateAccel,
-#define UI_PAD 4
-#define UI_CPAD 5
+ InitialGui = 200
+};
-#define UI_SLIDER 8
-#define UPDATE_SLIDER 9
+#define UI_INITIAL_GUI ControlType::InitialGui
-#define UI_NUMBER 10
-#define UPDATE_NUMBER 11
+#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 UI_TEXT_INPUT 12
-#define UPDATE_TEXT_INPUT 13
+#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
-#define UI_GRAPH 14
-#define CLEAR_GRAPH 15
-#define ADD_GRAPH_POINT 16
+// 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;
+ uint16_t parentControl;
+ Control *next;
+
+ static constexpr uint16_t noParent = 0xffff;
+
+ Control(ControlType type, const char *label, void (*callback)(Control *, int), String value, ControlColor color,
+ uint16_t parentControl = Control::noParent)
+ : type(type), label(label), callback(callback), value(value), color(color), 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),
+ parentControl(control.parentControl), next(control.next) {}
+
+private:
+ static uint16_t idCounter;
+};
// Values
#define B_DOWN -1
@@ -89,87 +160,90 @@ typedef struct Control {
#define SL_VALUE 8
#define N_VALUE 9
#define T_VALUE 10
+#define S_VALUE 11
-// Colors
-#define COLOR_TURQUOISE 0
-#define COLOR_EMERALD 1
-#define COLOR_PETERRIVER 2
-#define COLOR_WETASPHALT 3
-#define COLOR_SUNFLOWER 4
-#define COLOR_CARROT 5
-#define COLOR_ALIZARIN 6
-#define COLOR_NONE 7
+enum Verbosity : uint8_t { Quiet = 0, Verbose, VerboseJSON };
class ESPUIClass {
- public:
- void begin(const char *_title); // Setup servers and page in Memorymode
- void begin(const char *_title, const char *username, const char *password);
+public:
+ ESPUIClass() {
+ verbosity = Verbosity::Quiet;
+ jsonUpdateDocumentSize = 2000;
+ jsonInitialDocumentSize = 8000;
+ sliderContinuous = false;
+ }
+ unsigned int jsonUpdateDocumentSize;
+ unsigned int jsonInitialDocumentSize;
+ bool sliderContinuous;
- void beginSPIFFS(const char *_title); // Setup servers and page in SPIFFSmode
- void beginSPIFFS(const char *_title, const char *username,
- const char *password);
+ void setVerbosity(Verbosity verbosity);
+ void begin(const char *_title, const char *username = nullptr, const char *password = nullptr); // Setup server and page in Memorymode
+ void beginSPIFFS(const char *_title, const char *username = nullptr, const char *password = nullptr); // Setup server and page in SPIFFSmode
- void prepareFileSystem(); // Initially preps the filesystem and loads a lot
- // of stuff into SPIFFS
- void list();
- // Creating Elements
+ void prepareFileSystem(); // Initially preps the filesystem and loads a lot of stuff into SPIFFS
+ void list(); // Lists SPIFFS directory
- int button(const char *label, void (*callBack)(Control, int), int color,
- String value = ""); // Create Event Button
- int switcher(const char *label, bool startState,
- void (*callBack)(Control, int),
- int color); // Create Toggle Button
- int pad(const char *label, bool centerButton, void (*callBack)(Control, int),
- int color); // Create Pad Control
- int slider(const char *label, void (*callBack)(Control, int), int color,
- String value); // Create Slider Control
- int number(const char *label, void (*callBack)(Control, int), int color,
- int number, int min, int max); // Create a Number Input Control
- int text(const char *label, void (*callBack)(Control, int), int color,
- String value = ""); // Create a Text Input Control
+ uint16_t addControl(ControlType type, const char *label, String value = String(""), ControlColor color = ControlColor::Turquoise,
+ uint16_t parentControl = Control::noParent, void (*callback)(Control *, int) = nullptr);
+
+ // create Elements
+ uint16_t button(const char *label, void (*callback)(Control *, int), ControlColor color, 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
+
+ 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, String value = ""); // Create a Text Input Control
// Output only
- int label(const char *label, int color, String value = ""); // Create Label
- int graph(const char *label, int color); // Create Graph display
+ uint16_t label(const char *label, ControlColor color, String value = ""); // Create Label
+ uint16_t graph(const char *label, ControlColor color); // Create Graph display
+ uint16_t gauge(const char *label, ControlColor color, int value, int min = 0,
+ int max = 100); // Create Gauge display
+
+ // Input only
+ uint16_t accelerometer(const char *label, void (*callback)(Control *, int), ControlColor color);
// Update Elements
- void print(int id, String value);
- void print(String label, String value);
- void updateSwitcher(int id, bool nValue, int clientId = -1);
- void updateSwitcher(String label, bool nValue, int clientId = -1);
+ Control *getControl(uint16_t id);
- void updateSlider(int id, int nValue, int clientId = -1);
- void updateSlider(String label, int nValue, int clientId = -1);
+ // Update Elements
+ void updateControlValue(uint16_t id, String value, int clientId = -1);
+ void updateControlValue(Control *control, String value, int clientId = -1);
- void updateNumber(int id, int nValue, int clientId = -1);
- void updateNumber(String label, int nValue, int clientId = -1);
+ void updateControl(uint16_t id, int clientId = -1);
+ void updateControl(Control *control, int clientId = -1);
- void updateText(int id, String nValue, int clientId = -1);
- void updateText(String label, String nValue, int clientId = -1);
+ void print(uint16_t id, String value);
+ void updateLabel(uint16_t id, String value);
+ void updateSwitcher(uint16_t id, bool nValue, int clientId = -1);
+ void updateSlider(uint16_t id, int nValue, int clientId = -1);
+ void updateNumber(uint16_t id, int nValue, int clientId = -1);
+ void updateText(uint16_t id, String nValue, int clientId = -1);
+ void updateSelect(uint16_t id, String nValue, int clientId = -1);
+ void updateGauge(uint16_t id, int number, int clientId);
- void clearGraph(int id, int clientId = -1);
- void clearGraph(String label, int clientId = -1);
+ void clearGraph(uint16_t id, int clientId = -1);
+ void addGraphPoint(uint16_t id, int nValue, int clientId = -1);
- void addGraphPoint(int id, int nValue, int clientId = -1);
- void addGraphPoint(String label, int nValue, int clientId = -1);
-
- void textThem(String text, int clientId);
-
- // Variables ---
- const char *ui_title = "ESPUI"; // Store UI Title and Header Name
- int cIndex = 0; // Control index
- Control *controls[25];
+ // Variables
+ const char *ui_title = "ESPUI"; // Store UI Title and Header Name
+ Control *controls = nullptr;
void jsonDom(AsyncWebSocketClient *client);
- int getIdByLabel(String label);
- bool labelExists(String label);
- private:
- const char *basicAuthUsername;
- const char *basicAuthPassword;
- bool basicAuth = true;
+ Verbosity verbosity;
+
AsyncWebServer *server;
AsyncWebSocket *ws;
+
+private:
+ const char *basicAuthUsername = nullptr;
+ const char *basicAuthPassword = nullptr;
+ bool basicAuth = true;
};
extern ESPUIClass ESPUI;
diff --git a/src/dataControlsJS.h b/src/dataControlsJS.h
index 7fc61b5..b9c33b6 100644
--- a/src/dataControlsJS.h
+++ b/src/dataControlsJS.h
@@ -1,118 +1,144 @@
const char JS_CONTROLS[] PROGMEM = R"=====(
-const UI_INITIAL_GUI=100;const UI_TITEL=0;const UI_LABEL=1;const UPDATE_LABEL=6;const UI_BUTTON=2;const UI_SWITCHER=3;const UPDATE_SWITCHER=7;const UI_PAD=4;const UI_CPAD=5;const UI_SLIDER=8;const UPDATE_SLIDER=9;const UI_NUMBER=10;const UPDATE_NUMBER=11;const UI_TEXT_INPUT=12;const UPDATE_TEXT_INPUT=13;const UI_GRAPH=14;const CLEAR_GRAPH=15;const ADD_GRAPH_POINT=16;const FOR=0;const BACK=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_NONE=7;const C_DARK=8;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_NONE:return"dark";default:return"";}}
-var websock;var websockConnected=false;function restart(){$(document).add("*").off();$("#row").html("");websock.close();start();}
+const UI_INITIAL_GUI=200;const UPDATE_OFFSET=100;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 UPTDATE_GAUGE=117;const UI_ACCEL=18;const UPTDATE_ACCEL=117;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_NONE:return"dark";default:return"";}}
+var websock;var websockConnected=false;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 handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
-function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);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){var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element)};handleEvent(fauxEvent);});break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:$("#row").append("
"+
+""+
data.label+
-" "+
data.value+
-" ");break;case UI_BUTTON:$("#row").append("
"+
-data.label+
-" "+
-data.value+
-" ");$("#"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);}});$("#"+data.id).on({touchend:function(e){e.preventDefault();buttonclick(data.id,false);}});break;case UI_SWITCHER:var label="";var input="
";if(data.value=="0"){label="";input="
";}
-$("#row").append("
"+
+"'>"+
+""+
data.label+
" "+
-label+
-input+
-""+
-" ");break;case UI_CPAD:center="OK ";case UI_PAD:$("#row").append(""+
+data.value+
+"
");$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);}});break;case UI_SWITCHER:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append("");switcher(data.id,data.value);break;case UI_CPAD:case UI_PAD:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append(""+
+"
"+
data.label+
" "+
"
"+
""+
-"▲ "+
"▲ "+
"▲ "+
-"▲ "+
" "+
-center+
+(data.type==UI_CPAD?"OK ":"")+
" "+
-"
");$("#pf"+data.id).on({touchstart:function(e){e.preventDefault();padclick(FOR,data.id,true);}});$("#pf"+data.id).on({touchend:function(e){e.preventDefault();padclick(FOR,data.id,false);}});$("#pl"+data.id).on({touchstart:function(e){e.preventDefault();padclick(LEFT,data.id,true);}});$("#pl"+data.id).on({touchend:function(e){e.preventDefault();padclick(LEFT,data.id,false);}});$("#pr"+data.id).on({touchstart:function(e){e.preventDefault();padclick(RIGHT,data.id,true);}});$("#pr"+data.id).on({touchend:function(e){e.preventDefault();padclick(RIGHT,data.id,false);}});$("#pb"+data.id).on({touchstart:function(e){e.preventDefault();padclick(BACK,data.id,true);}});$("#pb"+data.id).on({touchend:function(e){e.preventDefault();padclick(BACK,data.id,false);}});$("#pc"+data.id).on({touchstart:function(e){e.preventDefault();padclick(CENTER,data.id,true);}});$("#pc"+data.id).on({touchend:function(e){e.preventDefault();padclick(CENTER,data.id,false);}});break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);break;case UPDATE_SWITCHER:if(data.value=="0")switcher(data.id,0);else switcher(data.id,1);break;case UI_SLIDER:$("#row").append("");$("#row").append("");break;case UPDATE_SLIDER:slider_move($("#sl"+data.id),data.value,"100",false);break;case UI_NUMBER:$("#row").append(""+
-"
"+
+""+
data.label+
-" "+
+" "+
" "+
-"");break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);break;case UI_TEXT_INPUT:$("#row").append(""+
-"
"+
+""+
data.label+
-" "+
+" "+
" "+
-"");break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);break;default:console.error("Unknown type or event");break;}};websock.onmessage=handleEvent;}
-function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);console.log(val);}
-function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);console.log(val);}
+" ");break;case UI_TAB:$("#tabsnav").append(""+data.value+" ");$("#tabscontent").append("
");tabs=$(".tabscontent").tabbedContent({loop:true}).data("api");$("a").filter(function(){return $(this).attr("href")==="#click-to-switch";}).on("click",function(e){var tab=prompt("Tab to switch to (number or id)?");if(!tabs.switchTab(tab)){alert("That tab does not exist :\\");}
+e.preventDefault();});break;case UI_SELECT:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append(""+
+"
"+
+data.label+
+" "+
+" "+
+"");break;case UI_OPTION:if(data.parentControl){var parent=$("#select"+data.parentControl);parent.append(""+
+data.label+
+" ");}
+break;case UI_MIN:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("min",data.value);}}
+break;case UI_MAX:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("max",data.value);}}
+break;case UI_STEP:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("step",data.value);}}
+break;case UI_GRAPH:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append(""+
+"
"+
+data.label+
+" "+
+""+
+""+
+data.label+
+" "+
+" "+
+"");graphData[data.id]=restoreGraphData(data.id);renderGraphSvg(graphData[data.id],"graph"+data.id);break;case ADD_GRAPH_POINT:var ts=Math.round(new Date().getTime()/1000);graphData[data.id].push({x:ts,y:data.value});saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case CLEAR_GRAPH:graphData[data.id]=[];saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case UI_GAUGE:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+parent.append(""+
+"
"+
+data.label+
+" "+
+"WILL BE A GAUGE "+
+"");break;case UI_ACCEL:if(hasAccel)break;var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
+hasAccel=true;parent.append(""+
+"
"+
+data.label+
+" "+
+"ACCEL // Not implemented fully!
");requestOrientationPermission();break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);break;case UPDATE_SWITCHER:switcher(data.id,data.value=="0"?0:1);break;case UPDATE_SLIDER:slider_move($("#sl"+data.id),data.value,"100",false);break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);break;case UPDATE_SELECT:$("#select"+data.id).val(data.value);break;case UPDATE_BUTTON:case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);break;case UPDATE_ACCEL:break;default:console.error("Unknown type or event");break;}
+if(data.type>=UPDATE_OFFSET&&data.type
Control
+ Control
)=====";
-const uint8_t HTML_INDEX_GZIP[863] PROGMEM = { 31,139,8,0,56,43,252,91,2,255,133,84,109,115,170,56,20,254,43,172,159,118,231,222,22,17,107,219,123,197,153,160,96,171,34,2,130,226,183,0,169,4,195,75,73,16,245,215,111,34,189,179,187,179,51,187,204,36,231,237,57,207,57,132,112,198,191,205,236,233,54,220,24,82,202,114,50,25,223,119,105,156,34,152,76,198,57,98,80,138,83,88,83,196,180,134,125,60,188,76,198,12,51,130,38,211,178,96,117,73,198,114,103,118,200,2,230,72,59,99,212,86,101,205,164,152,67,80,193,180,94,139,19,150,106,9,58,227,24,61,220,141,239,18,46,48,195,144,60,208,24,18,164,41,189,201,152,224,226,36,213,136,104,61,154,242,244,184,97,18,230,20,61,41,173,209,135,150,64,6,127,224,28,30,145,92,21,199,159,17,164,104,52,252,142,3,221,118,219,254,114,126,44,1,127,214,158,159,26,254,145,107,186,48,129,51,5,150,144,229,202,121,221,10,69,159,39,250,214,55,0,88,205,55,83,249,146,234,14,119,78,245,204,51,23,107,30,29,45,120,238,241,157,59,55,130,111,10,70,124,79,68,166,93,9,214,81,206,55,83,157,198,13,49,94,4,223,198,212,173,192,240,239,88,37,216,185,3,229,211,25,16,236,170,230,167,88,158,26,52,174,26,112,189,147,246,64,201,189,129,210,120,247,24,95,92,95,12,230,75,135,231,113,60,199,152,162,174,46,171,243,230,89,240,27,133,235,109,137,5,244,18,168,167,141,222,222,94,240,211,242,134,203,203,113,184,14,98,95,109,140,197,219,253,77,119,139,149,219,119,0,6,126,226,221,29,164,245,76,114,19,103,162,180,0,204,146,88,119,48,142,208,61,150,244,61,95,49,245,111,134,105,164,177,97,125,123,171,194,86,28,132,30,122,190,171,7,122,182,77,67,121,49,163,224,56,53,60,54,36,213,94,62,191,102,43,231,176,24,125,154,175,76,61,165,224,154,41,151,189,191,140,156,161,30,148,237,172,253,180,195,163,113,57,157,118,67,28,142,200,6,154,253,145,251,105,189,132,239,204,110,220,200,130,183,209,37,139,104,67,103,96,39,39,117,69,244,227,240,57,219,140,94,71,241,222,120,137,54,64,65,254,12,45,7,162,187,197,222,53,119,111,238,41,220,187,196,206,215,215,195,206,236,31,28,112,181,102,134,186,218,2,101,181,53,134,193,236,253,102,103,160,111,103,254,101,121,3,87,174,183,23,227,233,21,181,226,83,4,125,55,72,251,135,57,207,219,86,44,26,184,213,161,56,1,43,3,151,245,181,223,174,189,254,197,54,157,171,117,43,219,245,172,84,44,143,182,86,86,182,214,202,223,94,226,88,180,112,72,140,32,76,204,245,249,80,184,106,184,95,16,240,150,168,201,245,169,138,114,118,11,7,102,123,240,158,206,113,142,162,231,172,133,247,35,53,136,185,61,121,141,147,79,167,127,187,201,148,93,9,162,41,66,172,187,196,114,76,169,92,148,117,14,9,190,161,71,110,253,31,248,238,236,128,52,174,113,197,36,90,199,154,156,81,249,134,42,86,62,230,184,120,204,120,80,238,162,255,66,81,130,19,84,255,39,36,238,254,100,250,79,144,124,31,0,210,56,42,147,171,84,22,164,132,137,150,193,51,236,226,63,40,131,53,251,253,143,159,28,145,224,179,152,23,195,137,208,36,156,104,57,196,197,27,207,70,245,95,83,162,3,209,10,22,2,193,75,122,12,178,134,74,49,129,148,106,4,70,136,76,236,143,15,126,24,136,247,192,97,162,3,78,249,149,152,214,93,161,47,188,104,153,23,65,191,188,156,178,46,219,175,96,79,168,205,195,71,67,72,55,108,122,28,245,197,243,75,136,183,18,178,27,118,127,2,173,245,31,70,3,5,0,0 };
+const uint8_t HTML_INDEX_GZIP[916] PROGMEM = { 31,139,8,0,193,22,6,94,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 };
diff --git a/src/dataNormalizeCSS.h b/src/dataNormalizeCSS.h
index 4e6223d..06d02e1 100644
--- a/src/dataNormalizeCSS.h
+++ b/src/dataNormalizeCSS.h
@@ -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,56,43,252,91,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,193,22,6,94,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 };
diff --git a/src/dataSliderJS.h b/src/dataSliderJS.h
index 810e18f..af8d98c 100644
--- a/src/dataSliderJS.h
+++ b/src/dataSliderJS.h
@@ -1,14 +1,15 @@
const char JS_SLIDER[] PROGMEM = R"=====(
-function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find('.slider');slider_fill=slider.find('.slider-fill');slider_handle=slider.find('.slider-handle');slider_label=slider.find('.slider-label');var range_val=parseInt(range.val());slider_fill.css('width',range_val+'%');slider_handle.css('left',range_val+'%');slider_label.find('span').text(range_val);});self.on('mousedown touchstart','.slider-handle',function(e){if(e.button===2){return false;}
-var parents=$(this).parents('.rkmd-slider');var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(':disabled');if(check_range===true){return false;}
-$(this).addClass('is-active');var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<'0')){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find('.is-active').removeClass('is-active');};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu};$(document).on(handlers);});self.on('mousedown touchstart','.slider',function(e){if(e.button===2){return false;}
-var parents=$(this).parents('.rkmd-slider');var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(':disabled');if(check_range===true){return false;}
-var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<'0')){slider_move(parents,slider_new_width,slider_width,true);}
-var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu};$(document).on(handlers);});};function sliderDiscrete_tmplt(){var tmplt=''+
+function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find(".slider");slider_fill=slider.find(".slider-fill");slider_handle=slider.find(".slider-handle");slider_label=slider.find(".slider-label");var range_val=parseInt(range.val());slider_fill.css("width",range_val+"%");slider_handle.css("left",range_val+"%");slider_label.find("span").text(range_val);});self.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2){return false;}
+var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
+$(this).addClass("is-active");var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find(".is-active").removeClass("is-active");};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu};$(document).on(handlers);});self.on("mousedown touchstart",".slider",function(e){if(e.button===2){return false;}
+var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
+var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}
+var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu};$(document).on(handlers);});}
+function sliderDiscrete_tmplt(){var tmplt='
';return tmplt;}
-function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round(newW/sliderW*100));var slider_fill=parents.find('.slider-fill');var slider_handle=parents.find('.slider-handle');var range=parents.find('input[type="range"]');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);}}
+"
";return tmplt;}
+function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round((newW/sliderW)*100));var slider_fill=parents.find(".slider-fill");var slider_handle=parents.find(".slider-handle");var range=parents.find('input[type="range"]');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[865] PROGMEM = { 31,139,8,0,56,43,252,91,2,255,237,86,207,111,155,48,20,190,247,175,72,163,174,134,149,184,89,143,33,238,101,211,164,29,118,218,164,77,170,170,200,1,83,172,16,131,176,73,182,209,252,239,123,254,1,1,66,170,110,167,29,118,194,246,251,252,252,222,247,62,243,156,84,34,82,60,23,147,114,179,141,87,37,21,79,236,75,198,99,86,122,146,101,44,82,121,233,215,59,90,78,96,150,4,210,88,86,123,30,171,180,153,228,73,34,153,10,162,170,20,202,173,125,224,50,42,153,98,129,113,231,22,67,237,129,92,29,221,134,93,111,68,91,177,25,122,173,197,186,182,38,59,246,124,156,177,68,133,253,115,12,98,176,134,25,141,82,47,113,217,121,60,216,249,181,137,17,66,80,41,151,126,104,102,152,22,5,19,177,215,223,188,82,219,34,131,195,252,208,100,64,44,52,225,0,68,92,20,149,122,80,63,11,70,166,198,58,125,68,77,196,61,32,182,107,173,113,149,240,44,35,118,220,135,204,180,229,136,75,169,136,51,54,142,180,182,35,54,163,107,118,198,169,49,1,82,87,207,4,186,218,209,140,20,180,148,236,147,80,158,89,194,176,164,211,236,68,136,35,41,61,100,42,129,130,118,223,13,122,51,12,208,2,117,57,206,225,76,4,46,42,89,80,129,124,172,216,15,119,180,70,251,225,193,15,109,121,133,135,182,121,37,89,156,239,197,68,229,85,148,74,69,75,112,61,76,61,104,107,202,252,154,39,30,195,235,74,169,92,16,66,238,252,26,170,7,37,152,36,52,147,44,60,92,232,220,33,99,38,148,108,234,142,221,28,136,210,146,159,181,85,50,42,239,74,210,1,91,85,118,0,78,153,13,162,47,78,141,139,82,22,109,236,117,106,81,47,232,7,115,136,103,17,115,73,215,25,139,33,24,72,172,235,130,16,85,86,236,36,189,38,37,26,199,239,51,170,203,193,229,140,2,59,59,230,18,218,230,59,246,177,34,93,206,44,39,79,236,59,97,216,124,159,159,161,150,169,62,39,254,170,137,103,242,97,254,104,77,221,156,5,219,183,196,128,105,214,163,66,7,60,196,45,73,151,206,235,235,203,83,4,154,35,223,175,221,178,142,213,115,100,5,67,104,255,223,99,216,8,15,7,19,95,85,12,50,108,104,129,208,60,171,154,18,238,123,191,12,184,195,20,46,153,62,123,132,66,123,64,227,131,212,70,162,26,187,176,196,6,70,169,221,5,131,168,138,133,14,202,90,225,247,98,102,135,240,202,139,243,168,218,66,20,190,22,252,49,180,215,223,130,255,242,31,164,119,34,78,39,234,127,65,158,23,127,160,206,81,165,253,133,142,14,97,115,214,100,188,167,153,251,111,198,4,45,99,190,155,68,90,246,100,106,209,211,123,116,115,49,178,110,90,212,244,126,121,11,150,115,16,27,7,128,78,77,166,19,128,69,119,129,251,249,242,214,124,173,179,142,75,59,10,93,149,77,140,64,226,32,159,126,29,160,0,223,28,247,240,5,138,220,107,229,88,158,94,211,251,76,85,138,203,188,2,37,234,157,183,110,231,219,119,243,185,223,83,184,233,213,131,63,70,191,89,119,192,174,97,143,195,219,142,221,246,225,215,220,136,179,29,185,159,217,185,182,92,219,190,188,56,69,7,72,193,33,146,107,70,209,2,137,92,64,75,69,179,61,91,111,184,154,141,218,182,249,175,17,195,193,61,142,204,19,162,127,142,185,191,47,177,49,233,190,6,252,75,50,216,94,191,122,239,240,92,205,177,168,182,107,120,138,53,62,168,82,37,144,12,127,21,44,171,181,84,37,23,79,222,157,9,209,232,5,50,151,121,180,193,122,2,207,148,12,252,84,108,129,110,134,212,193,146,245,172,251,206,111,101,146,232,206,54,11,0,0 };
+const uint8_t JS_SLIDER_GZIP[869] PROGMEM = { 31,139,8,0,226,85,7,94,2,255,237,86,77,143,155,48,16,189,231,87,100,173,237,6,186,196,155,238,49,196,185,180,170,212,67,79,173,212,74,171,85,228,128,89,172,16,131,176,73,218,102,243,223,59,254,128,0,33,171,109,79,61,244,4,246,60,143,103,222,60,123,156,84,34,82,60,23,227,114,179,141,87,37,21,79,236,75,198,99,86,122,146,101,44,82,121,233,31,118,180,28,195,40,9,164,177,172,246,60,86,105,61,200,147,68,50,21,68,85,41,148,155,251,192,101,84,50,197,2,227,206,77,134,218,3,185,62,185,13,219,222,136,182,98,243,235,53,22,235,218,154,236,191,231,227,140,37,42,236,238,99,16,189,57,204,104,148,122,137,203,206,227,193,206,63,152,24,33,4,149,114,233,135,102,132,105,81,48,17,123,221,197,43,181,45,50,216,204,15,77,6,196,66,19,14,192,9,23,69,165,30,212,207,130,17,100,172,232,113,82,71,220,6,34,108,231,80,147,78,194,179,140,216,255,46,100,170,45,39,92,74,69,156,177,97,164,181,157,176,25,93,179,11,78,141,9,144,186,122,38,208,213,142,102,164,160,165,100,159,132,242,204,20,134,41,157,102,43,66,28,73,233,33,83,9,20,52,235,110,209,155,126,128,22,168,203,113,9,103,34,112,81,201,130,10,228,99,197,126,184,173,53,218,15,143,126,104,203,43,60,180,205,43,201,226,124,47,198,42,175,162,84,42,90,130,235,126,234,65,83,83,230,31,120,226,49,188,174,148,202,5,33,228,222,63,64,245,160,4,227,132,102,146,133,199,145,206,29,50,102,66,201,186,238,216,141,129,40,45,249,105,83,37,163,242,182,36,29,176,81,101,11,224,148,89,35,186,226,212,184,40,101,209,198,30,167,6,245,130,126,48,135,120,230,49,151,116,157,177,24,130,129,196,218,46,8,81,101,197,206,210,171,83,162,113,252,62,163,186,28,92,78,41,176,179,99,46,161,109,190,99,31,43,210,230,204,114,242,196,190,19,134,205,247,249,25,106,153,234,125,226,175,154,120,38,31,102,143,214,212,206,89,176,125,67,12,152,166,29,42,116,192,125,220,130,180,233,188,185,185,58,71,160,25,242,253,131,155,214,177,122,142,172,160,15,237,222,61,134,141,240,120,52,241,85,69,47,195,154,22,8,205,179,170,41,225,188,119,202,128,112,139,41,92,50,189,247,0,133,118,131,218,7,57,24,137,106,236,220,18,27,24,165,182,39,12,162,42,230,58,40,107,133,235,197,140,142,225,181,23,231,81,181,133,40,124,45,248,83,104,175,63,5,255,229,223,75,239,76,156,78,212,255,130,60,71,127,160,206,65,165,253,133,142,142,163,122,175,241,112,79,51,231,223,252,147,201,34,230,187,113,164,101,79,144,19,194,114,114,59,26,152,183,45,106,185,184,3,203,37,136,187,160,151,3,38,219,139,150,11,221,5,150,179,197,157,249,90,103,141,75,100,255,80,232,170,108,98,60,207,167,91,7,40,192,55,199,61,124,129,34,247,90,57,149,167,211,244,62,83,149,226,50,175,64,137,158,94,122,231,150,250,111,223,205,102,126,71,227,166,91,247,238,140,110,187,110,129,93,203,30,134,55,61,187,233,196,175,57,19,23,123,114,55,183,75,141,249,160,207,226,252,28,11,218,164,66,114,77,232,28,137,92,64,67,69,211,61,91,111,184,154,158,76,232,100,219,230,191,6,12,71,247,52,50,15,136,238,46,230,244,190,196,196,184,253,22,240,175,72,111,249,225,213,107,251,251,106,126,69,181,93,195,67,172,246,65,149,42,225,74,135,59,5,203,106,45,85,201,197,147,119,111,66,52,106,129,204,101,30,109,176,30,192,35,37,3,63,21,155,163,219,62,113,48,101,61,235,174,243,27,27,117,74,231,52,11,0,0 };
diff --git a/src/dataStyleCSS.h b/src/dataStyleCSS.h
index 0167465..d0da6da 100644
--- a/src/dataStyleCSS.h
+++ b/src/dataStyleCSS.h
@@ -1,5 +1,5 @@
const char CSS_STYLE[] PROGMEM = R"=====(
-.container{position:relative;width:79%;margin:20px;box-sizing:border-box}.column,.columns{width:100%;float:left}.card{margin-top:2%;border-radius:6px;box-shadow:0 4px 4px rgba(204,197,185,0.5);padding-left:20px;padding-right:20px;margin-bottom:10px;min-width:150px;color:#fff}.card-slider{padding-bottom:10px}.turquoise{background:#1abc9c;border-bottom:#16a085 3px solid}.emerald{background:#2ecc71;border-bottom:#27ae60 3px solid}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.dark{background:#444857;border-bottom:#444857 3px solid}.label{box-sizing:border-box;white-space:nowrap;border-radius:.2em;padding:.12em .4em .14em;text-align:center;color:#fff;font-weight:700;line-height:1;margin-bottom:5px;display:inline-block;white-space:nowrap;vertical-align:baseline;position:relative;top:-.15em;background-color:#999;margin-bottom:10px}.label-wrap{width:90%;white-space:pre-wrap;word-wrap:break-word}.label.color-blue{background-color:#6f9ad1}.label.color-red{background-color:#d37c7c}.label.color-green{background-color:#9bc268}.label.color-orange{background-color:#dea154}.label.color-yellow{background-color:#e9d641}.label.color-purple{background-color:#9f83d1}@media(min-width:400px){.container{width:84%}}@media(min-width:630px){.container{width:98%}.column,.columns{margin-right:2%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.66666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.66666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}html{font-size:62.5%}body{margin:0;font-size:1.5em;line-height:1;font-weight:400;font-family:"Open Sans",sans-serif;color:#222;background-color:#ecf0f1}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media(min-width:630px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2rem}h6{font-size:1.5rem}}p{margin-top:0}a{color:#1eaedb}a:hover{color:#0fa0ce}button{display:inline-block;padding:10px;border-radius:3px;color:#fff;background-color:#999}#mainHeader{display:inline-block}#conStatus{position:inherit;font-size:.75em}button,.button{margin-bottom:1rem}.u-full-width{width:100%;box-sizing:border-box}.u-max-full-width{max-width:100%;box-sizing:border-box}.u-pull-right{float:right}.u-pull-left{float:left}.tcenter{text-align:center}hr{margin-top:.5rem;margin-bottom:1.2rem;border-width:0;border-top:1px solid #e1e1e1}.container:after,.row:after,.u-cf{content:"";display:table;clear:both}.control{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.1),hsla(0,0%,100%,0.1));border-radius:50%;box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.75),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.75);height:9em;margin:3em auto;position:relative;width:9em}.control ul{height:100%;padding:0;transform:rotate(45deg)}.control li{border-radius:100% 0 0 0;box-shadow:inset -1px -1px 1px hsla(0,0%,100%,0.5),0 0 1px hsla(0,0%,0%,0.75);display:inline-block;height:50%;overflow:hidden;width:50%}.control ul li:nth-child(2){transform:rotate(90deg)}.control ul li:nth-child(3){transform:rotate(-90deg)}.control ul li:nth-child(4){transform:rotate(180deg)}.control ul a{height:200%;position:relative;transform:rotate(-45deg);width:200%}.control a:hover,.control a:focus{background-color:hsla(0,0%,100%,0.25)}.control a{border-radius:50%;color:#333;display:block;font:bold 1em/3 sans-serif;text-align:center;text-decoration:none;text-shadow:0 1px 1px hsla(0,0%,100%,0.4);transition:.15s}.control .confirm{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.15),hsla(0,0%,100%,0.25));box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.25),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.85);left:50%;line-height:3;margin:-1.5em;position:absolute;top:50%;width:3em}.control .confirm:hover,.control .confirm:focus{background-color:#eee}.switch{display:inline-block !important;background-color:#bebebe;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);color:#fff;cursor:pointer;display:block;font-size:14px;height:26px;margin-bottom:12px;position:relative;width:60px;-webkit-transition:background-color .2s ease-in-out;-moz-transition:background-color .2s ease-in-out;-o-transition:background-color .2s ease-in-out;-ms-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.switch.checked{background-color:#76d21d}.switch input[type="checkbox"]{display:none;cursor:pointer;height:10px;left:12px;position:absolute;top:8px;width:10px}.in{position:absolute;top:8px;left:12px;-webkit-transition:left .08s ease-in-out;-moz-transition:left .08s ease-in-out;-o-transition:left .08s ease-in-out;-ms-transition:left .08s ease-in-out;transition:left .08s ease-in-out}.switch.checked div{left:38px}.switch .in:before{background:#fff;background:-moz-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#f0f0f0));background:-webkit-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-o-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-ms-linear-gradient(top,#fff 0,#f0f0f0 100%);background:linear-gradient(to bottom,#fff 0,#f0f0f0 100%);border:1px solid #fff;border-radius:2px;box-shadow:0 0 4px rgba(0,0,0,0.3);content:"";height:18px;position:absolute;top:-5px;left:-9px;width:26px}.switch .in:after{background:#f0f0f0;background:-moz-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f0f0f0),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-o-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-ms-linear-gradient(top,#f0f0f0 0,#fff 100%);background:linear-gradient(to bottom,#f0f0f0 0,#fff 100%);border-radius:10px;content:"";height:12px;margin:-1px 0 0 -1px;position:absolute;width:12px}.rkmd-slider{display:block;position:relative;font-size:16px;font-family:"Roboto",sans-serif}.rkmd-slider input[type="range"]{overflow:hidden;position:absolute;width:1px;height:1px;opacity:0}.rkmd-slider input[type="range"]+.slider{display:block;position:relative;width:100%;height:27px;border-radius:13px;background-color:#bebebe}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider{height:4px;border-radius:0}}.rkmd-slider input[type="range"]+.slider .slider-fill{display:block;position:absolute;width:0;height:100%;user-select:none;z-index:1}.rkmd-slider input[type="range"]+.slider .slider-handle{cursor:pointer;position:absolute;top:12px;left:0;width:15px;height:15px;margin-left:-8px;border-radius:50%;transition:all .2s ease;user-select:none;z-index:2}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider .slider-handle{top:-5.5px}}.rkmd-slider input[type="range"]:disabled+.slider{background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle{cursor:default !important;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle .slider-label{display:none;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active{top:-5.5px;width:15px;height:15px;margin-left:-8px}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active .slider-label{display:none;border-radius:50%;transform:none}.rkmd-slider input[type="range"]:disabled+.slider .slider-handle:active{box-shadow:none !important;transform:scale(1) !important}.rkmd-slider.slider-discrete .slider .slider-handle{position:relative;z-index:1}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{position:absolute;top:-17.5px;left:4px;width:30px;height:30px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;border-radius:50%;-webkit-transform:scale(1) rotate(-45deg);transform:scale(1) rotate(-45deg);-webkit-transition:all .2s ease;transition:all .2s ease}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{left:-2px;-webkit-transform:scale(0.5) rotate(-45deg);transform:scale(0.5) rotate(-45deg)}}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{position:absolute;top:7px;left:0;width:100%;color:#fff;font-size:16px;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{font-size:12px}}.rkmd-slider.slider-discrete .slider .slider-handle.is-active{top:0;margin-left:-2px;width:4px;height:4px}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label{top:-15px;left:-2px;border-radius:15px 15px 15px 0;-webkit-transform:rotate(-45deg) translate(23px,-25px);transform:rotate(-45deg) translate(23px,-25px)}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label span{opacity:1}.rkmd-slider.slider-discrete.slider-turquoise .slider-label{background-color:#16a085}.rkmd-slider.slider-discrete.slider-emerald .slider-label{background-color:#27ae60}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.rkmd-slider.slider-discrete.slider-peterriver .slider-label{background-color:#2980b9}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.rkmd-slider.slider-discrete.slider-wetasphalt .slider-label{background-color:#2c3e50}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.rkmd-slider.slider-discrete.slider-sunflower .slider-label{background-color:#e6bb0f}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.rkmd-slider.slider-discrete.slider-carrot .slider-label{background-color:#d35400}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.rkmd-slider.slider-discrete.slider-alizarin .slider-label{background-color:#c0392b}input{margin:0 auto 1.2rem auto;padding:2px 5px;width:100%;box-sizing:border-box;border:0;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background:rgba(255,255,255,0.8)}input[id^="num"]{max-width:6em;width:auto;text-align:right;font-weight:bold;font-size:115%}
+.container{position:relative;width:79%;margin:20px;box-sizing:border-box}.column,.columns{width:100%;float:left}.card{margin-top:2%;border-radius:6px;box-shadow:0 4px 4px rgba(204,197,185,0.5);padding-left:20px;padding-right:20px;margin-bottom:10px;min-width:500px;color:#fff}@media(max-width:630px){.card{min-width:98%}}.card-slider{padding-bottom:10px}.turquoise{background:#1abc9c;border-bottom:#16a085 3px solid}.emerald{background:#2ecc71;border-bottom:#27ae60 3px solid}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.dark{background:#444857;border-bottom:#444857 3px solid}.label{box-sizing:border-box;white-space:nowrap;border-radius:.2em;padding:.12em .4em .14em;text-align:center;color:#fff;font-weight:700;line-height:1;margin-bottom:5px;display:inline-block;white-space:nowrap;vertical-align:baseline;position:relative;top:-.15em;background-color:#999;margin-bottom:10px}.label-wrap{width:90%;white-space:pre-wrap;word-wrap:break-word}.label.color-blue{background-color:#6f9ad1}.label.color-red{background-color:#d37c7c}.label.color-green{background-color:#9bc268}.label.color-orange{background-color:#dea154}.label.color-yellow{background-color:#e9d641}.label.color-purple{background-color:#9f83d1}@media(min-width:400px){.container{width:84%}}@media(min-width:630px){.container{width:98%}.column,.columns{margin-right:2%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.66666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.66666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}html{font-size:62.5%}body{margin:0;font-size:1.5em;line-height:1;font-weight:400;font-family:"Open Sans",sans-serif;color:#222;background-color:#ecf0f1}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media(min-width:630px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2rem}h6{font-size:1.5rem}}p{margin-top:0}a{color:#1eaedb}a:hover{color:#0fa0ce}button{display:inline-block;padding:10px;border-radius:3px;color:#fff;background-color:#999}#mainHeader{display:inline-block}#conStatus{position:inherit;font-size:.75em}button,.button{margin-bottom:1rem}.u-full-width{width:100%;box-sizing:border-box}.u-max-full-width{max-width:100%;box-sizing:border-box}.u-pull-right{float:right}.u-pull-left{float:left}.tcenter{text-align:center}hr{margin-top:.5rem;margin-bottom:1.2rem;border-width:0;border-top:1px solid #e1e1e1}.container:after,.row:after,.u-cf{content:"";display:table;clear:both}.control{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.1),hsla(0,0%,100%,0.1));border-radius:50%;box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.75),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.75);height:9em;margin:3em auto;position:relative;width:9em}.control ul{height:100%;padding:0;transform:rotate(45deg)}.control li{border-radius:100% 0 0 0;box-shadow:inset -1px -1px 1px hsla(0,0%,100%,0.5),0 0 1px hsla(0,0%,0%,0.75);display:inline-block;height:50%;overflow:hidden;width:50%}.control ul li:nth-child(2){transform:rotate(90deg)}.control ul li:nth-child(3){transform:rotate(-90deg)}.control ul li:nth-child(4){transform:rotate(180deg)}.control ul a{height:200%;position:relative;transform:rotate(-45deg);width:200%}.control a:hover,.control a:focus{background-color:hsla(0,0%,100%,0.25)}.control a{border-radius:50%;color:#333;display:block;font:bold 1em/3 sans-serif;text-align:center;text-decoration:none;text-shadow:0 1px 1px hsla(0,0%,100%,0.4);transition:.15s}.control .confirm{background-color:#ddd;background-image:linear-gradient(hsla(0,0%,0%,0.15),hsla(0,0%,100%,0.25));box-shadow:inset 0 1px 1px 1px hsla(0,0%,100%,0.5),0 0 1px 1px hsla(0,0%,100%,0.25),0 0 1px 2px hsla(0,0%,100%,0.25),0 0 1px 3px hsla(0,0%,100%,0.25),0 0 1px 4px hsla(0,0%,100%,0.25),0 0 1px 6px hsla(0,0%,0%,0.85);left:50%;line-height:3;margin:-1.5em;position:absolute;top:50%;width:3em}.control .confirm:hover,.control .confirm:focus{background-color:#eee}.switch{display:inline-block !important;background-color:#bebebe;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);color:#fff;cursor:pointer;display:block;font-size:14px;height:26px;margin-bottom:12px;position:relative;width:60px;-webkit-transition:background-color .2s ease-in-out;-moz-transition:background-color .2s ease-in-out;-o-transition:background-color .2s ease-in-out;-ms-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.switch.checked{background-color:#76d21d}.switch input[type="checkbox"]{display:none;cursor:pointer;height:10px;left:12px;position:absolute;top:8px;width:10px}.in{position:absolute;top:8px;left:12px;-webkit-transition:left .08s ease-in-out;-moz-transition:left .08s ease-in-out;-o-transition:left .08s ease-in-out;-ms-transition:left .08s ease-in-out;transition:left .08s ease-in-out}.switch.checked div{left:38px}.switch .in:before{background:#fff;background:-moz-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#f0f0f0));background:-webkit-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-o-linear-gradient(top,#fff 0,#f0f0f0 100%);background:-ms-linear-gradient(top,#fff 0,#f0f0f0 100%);background:linear-gradient(to bottom,#fff 0,#f0f0f0 100%);border:1px solid #fff;border-radius:2px;box-shadow:0 0 4px rgba(0,0,0,0.3);content:"";height:18px;position:absolute;top:-5px;left:-9px;width:26px}.switch .in:after{background:#f0f0f0;background:-moz-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f0f0f0),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-o-linear-gradient(top,#f0f0f0 0,#fff 100%);background:-ms-linear-gradient(top,#f0f0f0 0,#fff 100%);background:linear-gradient(to bottom,#f0f0f0 0,#fff 100%);border-radius:10px;content:"";height:12px;margin:-1px 0 0 -1px;position:absolute;width:12px}.rkmd-slider{display:block;position:relative;font-size:16px;font-family:"Roboto",sans-serif}.rkmd-slider input[type="range"]{overflow:hidden;position:absolute;width:1px;height:1px;opacity:0}.rkmd-slider input[type="range"]+.slider{display:block;position:relative;width:100%;height:27px;border-radius:13px;background-color:#bebebe}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider{height:4px;border-radius:0}}.rkmd-slider input[type="range"]+.slider .slider-fill{display:block;position:absolute;width:0;height:100%;user-select:none;z-index:1}.rkmd-slider input[type="range"]+.slider .slider-handle{cursor:pointer;position:absolute;top:12px;left:0;width:15px;height:15px;margin-left:-8px;border-radius:50%;transition:all .2s ease;user-select:none;z-index:2}@media(pointer:fine){.rkmd-slider input[type="range"]+.slider .slider-handle{top:-5.5px}}.rkmd-slider input[type="range"]:disabled+.slider{background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle{cursor:default !important;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle .slider-label{display:none;background-color:#b0b0b0 !important}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active{top:-5.5px;width:15px;height:15px;margin-left:-8px}.rkmd-slider input[type="range"]:disabled+.slider .slider-fill.is-active .slider-label,.rkmd-slider input[type="range"]:disabled+.slider .slider-handle.is-active .slider-label{display:none;border-radius:50%;transform:none}.rkmd-slider input[type="range"]:disabled+.slider .slider-handle:active{box-shadow:none !important;transform:scale(1) !important}.rkmd-slider.slider-discrete .slider .slider-handle{position:relative;z-index:1}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{position:absolute;top:-17.5px;left:4px;width:30px;height:30px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;border-radius:50%;-webkit-transform:scale(1) rotate(-45deg);transform:scale(1) rotate(-45deg);-webkit-transition:all .2s ease;transition:all .2s ease}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label{left:-2px;-webkit-transform:scale(0.5) rotate(-45deg);transform:scale(0.5) rotate(-45deg)}}.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{position:absolute;top:7px;left:0;width:100%;color:#fff;font-size:16px;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media(pointer:fine){.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span{font-size:12px}}.rkmd-slider.slider-discrete .slider .slider-handle.is-active{top:0;margin-left:-2px;width:4px;height:4px}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label{top:-15px;left:-2px;border-radius:15px 15px 15px 0;-webkit-transform:rotate(-45deg) translate(23px,-25px);transform:rotate(-45deg) translate(23px,-25px)}.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label span{opacity:1}.rkmd-slider.slider-discrete.slider-turquoise .slider-label{background-color:#16a085}.rkmd-slider.slider-discrete.slider-emerald .slider-label{background-color:#27ae60}.peterriver{background:#3498db;border-bottom:#2980b9 3px solid}.rkmd-slider.slider-discrete.slider-peterriver .slider-label{background-color:#2980b9}.wetasphalt{background:#34495e;border-bottom:#2c3e50 3px solid}.rkmd-slider.slider-discrete.slider-wetasphalt .slider-label{background-color:#2c3e50}.sunflower{background:#f1c40f;border-bottom:#e6bb0f 3px solid}.rkmd-slider.slider-discrete.slider-sunflower .slider-label{background-color:#e6bb0f}.carrot{background:#e67e22;border-bottom:#d35400 3px solid}.rkmd-slider.slider-discrete.slider-carrot .slider-label{background-color:#d35400}.alizarin{background:#e74c3c;border-bottom:#c0392b 3px solid}.rkmd-slider.slider-discrete.slider-alizarin .slider-label{background-color:#c0392b}input{margin:0 auto 1.2rem auto;padding:2px 5px;width:100%;box-sizing:border-box;border:0;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background:rgba(255,255,255,0.8)}select{margin:0 auto 1.2rem auto;padding:2px 5px;width:100%;box-sizing:border-box;border:0;border-radius:4px;box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background:rgba(255,255,255,0.8)}input[id^="num"]{max-width:6em;width:auto;text-align:right;font-weight:bold;font-size:115%}body div>ul.navigation{margin:0;padding:0;border-bottom:3px solid #666;overflow:hidden}ul.navigation li{list-style:none;float:left;margin-right:4px}ul.navigation li.controls{float:right}ul.navigation li a{font-weight:bold;display:inline-block;padding:6px 12px;color:#888;outline:0;text-decoration:none;background:#f3f3f3;background:-webkit-gradient(linear,0 0,0 bottom,from(#eee),to(#e4e4e4));background:-moz-linear-gradient(#eee,#e4e4e4);background:linear-gradient(#eee,#e4e4e4);-pie-background:linear-gradient(#eee,#e4e4e4)}ul.navigation li.active a{pointer-events:none;color:white;background:#666;background:-webkit-gradient(linear,0 0,0 bottom,from(#888),to(#666));background:-moz-linear-gradient(#888,#666);background:linear-gradient(#888,#666);-pie-background:linear-gradient(#888,#666)}div.tabscontent>div{padding:0 15px}#tabsnav:empty{display:none}.range-slider{margin:0}.range-slider{width:100%}.range-slider__range{-webkit-appearance:none;width:calc(100% - (45px));height:10px;border-radius:5px;outline:0;padding:0;margin:0}.range-slider__range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:20px;height:20px;border-radius:50%;cursor:pointer;transition:background .15s ease-in-out}.range-slider__range::-webkit-slider-thumb:hover{background:#1abc9c}.range-slider__range:active::-webkit-slider-thumb{background:#1abc9c}.range-slider__range::-moz-range-thumb{width:20px;height:20px;border:0;border-radius:50%;cursor:pointer;transition:background .15s ease-in-out}.range-slider__range:focus::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 6px #1abc9c}.range-slider__value{display:inline-block;position:relative;width:30px;color:#fff;line-height:20px;text-align:center;border-radius:3px;padding:5px 5px;margin-left:2px}.range-slider__value:after{position:absolute;top:8px;left:-7px;width:0;height:0;content:""}::-moz-range-track{border:0}input::-moz-focus-inner,input::-moz-focus-outer{border:0}svg{display:block;width:100%;height:100%}.y-axis path,.x-axis path{stroke:gray;stroke-width:1;fill:none}.series{stroke:steelblue;stroke-width:3;fill:none}.data-points circle{stroke:steelblue;stroke-width:2;fill:white}.data-points text{display:none}.data-points circle:hover{fill:steelblue;stroke-width:6}.data-points circle:hover+text{display:inline-block}text{text-anchor:end}
)=====";
-const uint8_t CSS_STYLE_GZIP[2814] PROGMEM = { 31,139,8,0,126,24,117,92,2,255,197,26,89,111,179,72,242,175,120,19,69,138,181,192,54,167,13,104,164,125,220,183,149,118,30,71,51,82,3,77,64,193,224,229,24,39,159,197,127,223,62,56,250,34,118,190,100,180,113,226,64,83,85,93,119,87,87,99,165,77,221,195,178,70,237,245,220,116,101,95,54,117,212,162,10,246,229,159,40,190,148,89,95,68,135,240,41,62,193,246,165,172,35,7,156,223,226,164,121,51,187,242,71,89,191,68,73,211,102,168,53,241,200,104,165,77,53,156,106,99,250,223,93,25,178,13,192,83,156,87,13,236,163,10,229,61,6,131,109,118,101,228,204,190,57,71,206,83,60,81,105,97,86,14,93,20,204,83,20,48,107,46,17,216,121,231,55,250,215,190,36,240,217,1,158,97,135,7,195,62,250,6,176,252,125,124,134,89,134,89,49,9,117,198,223,60,210,150,47,197,52,52,205,151,52,125,223,156,48,79,100,8,223,79,44,250,228,30,179,221,180,209,99,158,231,140,71,179,171,202,140,104,101,162,198,225,142,86,63,180,255,29,154,178,67,215,4,166,175,47,109,51,212,89,244,104,195,36,13,211,120,81,10,69,120,180,3,8,142,254,206,197,18,116,13,166,57,90,232,132,90,88,101,2,174,131,210,244,96,203,184,206,1,162,0,240,184,103,212,163,182,197,198,105,5,116,215,11,143,89,162,160,135,71,144,132,60,250,5,245,176,59,23,176,234,37,116,47,244,145,130,158,186,200,23,102,239,134,26,219,242,34,77,158,219,169,7,114,25,27,5,73,2,114,30,27,171,181,109,196,137,81,112,64,142,35,163,102,174,239,1,97,98,88,149,63,96,91,214,34,242,193,75,93,69,223,41,112,67,39,225,145,51,216,190,10,136,158,231,29,253,131,140,200,70,121,196,10,38,168,186,106,253,61,190,20,101,143,204,238,12,83,20,213,205,165,133,103,201,143,45,7,157,102,95,140,44,27,223,237,44,143,124,217,248,59,238,209,91,111,98,169,94,234,40,69,53,182,41,231,128,113,142,99,210,188,32,234,191,7,0,226,10,199,167,89,176,123,91,114,102,31,251,110,86,118,231,10,190,71,101,77,33,147,170,73,95,117,12,98,175,233,203,20,86,211,196,9,236,16,65,136,213,200,39,145,105,90,182,143,57,93,21,103,78,44,134,97,168,137,168,73,93,38,153,105,10,254,16,199,62,207,198,185,69,244,113,124,193,154,162,87,81,210,34,248,106,146,251,9,223,162,147,96,25,6,62,184,230,169,131,60,132,153,45,130,182,40,211,64,102,238,33,61,164,34,228,75,139,80,173,129,13,147,212,9,142,34,108,211,194,250,69,199,66,134,160,237,123,34,240,59,170,112,88,104,128,81,152,5,158,196,239,121,104,207,149,142,114,152,31,93,44,220,63,79,40,43,225,243,154,160,112,44,156,223,246,87,107,77,213,108,252,232,61,141,42,116,224,234,161,195,227,147,154,164,39,51,78,169,114,1,136,242,178,237,122,51,45,202,42,91,128,249,193,25,145,102,93,48,90,77,141,22,218,235,245,188,8,120,86,176,252,28,240,36,253,165,145,151,9,215,114,151,31,2,81,96,83,73,48,14,225,47,111,134,86,26,119,129,37,80,207,177,3,203,32,161,72,190,43,223,100,22,137,118,58,244,39,170,165,7,126,32,82,167,81,40,193,4,190,72,190,46,21,21,28,60,34,149,66,253,232,72,212,43,13,11,161,45,41,231,130,42,69,68,186,208,170,86,49,251,162,108,179,9,118,67,97,216,28,12,172,19,225,100,177,8,57,188,116,228,34,20,213,92,147,231,29,234,205,228,221,20,60,65,51,218,9,174,115,148,60,99,197,88,125,196,208,142,138,116,236,131,196,234,138,193,121,146,177,49,46,210,114,2,129,0,231,113,134,126,88,68,119,61,107,67,36,206,49,13,253,176,72,201,115,183,132,90,253,215,208,142,138,116,124,71,68,230,92,204,216,24,23,9,4,96,75,36,62,26,140,141,113,137,86,184,37,20,23,53,134,126,88,164,116,16,29,175,215,10,213,111,136,116,12,54,69,170,54,244,35,197,38,79,45,244,183,132,146,67,208,248,224,217,189,126,164,196,171,241,209,195,123,213,47,69,183,177,253,72,245,174,177,232,79,213,149,150,45,184,82,66,81,224,88,254,211,152,52,217,251,4,26,129,120,125,106,91,164,174,16,139,26,190,228,193,235,29,187,207,225,169,172,222,163,135,127,159,81,189,251,21,214,221,131,209,225,111,236,169,109,153,207,5,147,67,234,71,117,225,77,115,144,219,99,97,27,133,99,20,174,81,120,70,225,27,69,192,111,61,128,84,197,88,126,139,249,226,57,113,1,192,36,56,193,188,86,230,220,114,226,10,245,184,128,163,53,14,169,245,112,221,132,161,198,194,225,240,92,43,208,96,250,91,168,46,143,170,34,186,91,120,30,135,231,88,26,102,93,205,148,224,72,113,253,43,111,161,163,138,171,67,245,41,106,32,160,250,42,106,32,163,130,205,186,69,80,183,175,40,210,179,28,85,67,84,185,162,244,174,34,147,179,193,234,56,158,5,167,24,225,117,242,33,27,65,148,37,35,140,138,134,236,183,166,81,144,67,144,162,49,25,176,207,212,87,109,233,61,87,253,54,219,46,243,187,2,87,216,106,234,139,235,241,241,132,11,183,127,33,72,246,159,186,9,198,71,92,220,253,218,195,126,232,214,93,123,89,23,56,44,122,46,206,172,3,142,179,137,81,195,154,24,150,10,119,162,0,107,48,243,161,170,152,29,248,114,98,99,167,63,152,39,248,198,163,144,219,123,208,206,4,133,214,154,87,214,18,160,215,203,19,146,78,174,124,175,160,103,91,163,171,178,89,26,139,150,183,25,115,57,73,50,234,40,179,246,25,119,96,190,37,72,246,188,207,219,61,34,155,124,198,181,100,142,96,142,103,49,172,182,185,204,151,131,153,230,87,2,128,25,136,30,30,150,61,87,15,147,10,197,105,133,96,139,165,237,11,70,165,109,42,221,214,33,203,120,139,151,39,248,130,34,98,86,72,246,38,216,65,48,237,231,162,171,224,51,48,192,19,253,181,236,189,177,142,16,245,210,177,189,228,86,254,172,118,214,52,41,107,156,185,119,96,71,100,156,255,20,42,254,222,0,28,140,242,252,192,1,56,58,0,135,3,112,111,1,120,183,0,2,1,96,230,32,158,114,72,184,24,56,114,241,38,26,14,125,19,111,53,172,66,226,211,147,21,118,67,117,157,211,16,241,205,57,52,65,220,227,13,94,151,55,237,41,106,27,28,74,232,217,243,51,244,178,95,49,171,242,42,42,153,16,216,17,118,129,170,107,147,136,96,222,163,107,141,140,218,36,50,113,77,44,75,210,15,233,187,68,69,153,101,168,158,196,196,79,120,49,49,191,81,221,23,108,127,246,236,236,175,138,128,33,16,5,148,113,92,13,142,121,11,201,211,32,217,71,21,9,206,102,112,168,25,212,142,131,50,51,179,199,36,44,193,90,233,77,41,217,224,6,242,38,197,217,80,137,57,157,203,113,116,174,106,24,77,177,138,203,163,197,46,204,32,36,177,226,16,175,178,157,141,78,255,112,119,92,41,162,118,115,232,72,134,210,166,133,84,206,26,215,80,108,112,233,107,110,58,139,183,103,218,96,26,178,108,191,91,57,38,23,120,31,126,250,174,236,226,107,210,11,86,209,254,219,179,137,243,127,207,38,71,28,105,172,98,197,86,230,235,19,119,206,44,38,171,76,23,231,132,9,94,32,134,158,181,195,8,214,180,127,230,243,203,108,16,217,35,151,241,13,199,124,68,8,141,86,119,41,251,180,208,46,242,187,191,149,167,115,211,246,176,238,53,101,66,130,200,71,90,5,60,177,117,62,219,13,80,109,208,246,57,214,6,249,88,238,158,175,66,210,161,237,240,245,185,41,169,239,170,110,63,213,75,132,254,28,197,129,218,86,119,72,243,125,35,39,7,164,20,194,101,117,242,90,246,38,231,221,178,100,59,203,233,118,8,118,200,196,132,155,161,143,205,83,243,227,115,8,205,39,233,119,159,130,255,4,236,108,94,43,45,80,250,170,237,82,30,130,204,177,179,25,112,87,214,231,161,255,173,127,63,163,95,30,40,18,182,230,195,239,139,123,208,36,34,25,107,89,220,176,126,89,43,68,48,131,224,194,71,252,100,174,211,72,195,182,172,175,219,128,43,49,141,217,200,195,29,222,57,124,108,171,13,168,230,30,74,221,109,160,91,0,178,254,119,89,249,231,149,237,173,143,68,252,73,233,88,13,81,130,240,210,35,158,230,136,229,121,68,101,147,179,41,214,149,65,0,119,0,255,3,228,179,35,185,104,47,32,78,218,91,144,24,17,131,178,76,8,208,11,22,67,6,235,16,119,120,248,25,80,202,123,126,136,230,185,105,162,189,118,146,159,98,176,249,57,52,108,162,159,193,83,113,102,225,245,152,52,195,241,213,58,53,140,144,247,28,249,200,16,172,7,134,66,198,91,170,247,57,106,142,155,177,98,250,115,12,152,225,18,54,36,235,9,126,67,55,7,162,219,80,230,239,244,28,38,40,179,244,247,122,14,243,17,141,243,96,151,250,132,231,220,96,176,249,57,180,77,207,249,24,239,35,207,209,97,74,197,59,221,121,43,30,224,44,235,88,68,203,119,226,60,228,66,227,22,83,234,116,136,15,180,175,167,229,136,88,92,47,213,21,144,91,65,201,170,41,180,182,254,211,96,17,26,190,175,37,208,22,86,4,122,34,133,151,3,121,59,176,201,233,186,92,147,203,134,244,94,250,119,114,48,112,99,134,191,91,119,74,198,109,250,231,186,224,160,180,60,108,210,243,216,170,96,230,62,208,180,156,69,57,182,241,254,122,55,131,211,172,158,50,41,24,239,150,114,55,253,55,243,178,170,182,68,150,84,11,98,126,83,57,116,164,181,133,42,148,246,108,141,254,129,87,160,12,189,69,246,231,153,40,96,157,85,232,42,173,242,250,28,69,157,151,29,246,204,166,240,57,155,251,107,137,198,18,217,81,81,19,41,106,185,101,20,86,213,82,196,108,75,229,124,205,104,178,168,44,219,90,152,219,219,54,139,176,121,72,195,37,91,60,64,227,88,128,124,184,242,249,243,84,5,151,48,126,30,93,52,102,134,114,56,84,253,141,194,254,155,185,95,110,232,17,244,151,101,17,201,137,149,233,95,47,140,85,118,38,76,73,234,249,178,32,43,41,206,1,239,141,162,111,19,227,155,173,179,69,88,178,147,62,5,208,230,11,1,24,191,202,70,52,41,150,171,201,8,93,222,241,215,25,187,20,86,232,217,222,111,121,201,76,26,207,153,182,168,95,36,147,131,76,93,157,244,89,248,78,122,146,2,55,138,68,251,96,45,117,162,183,248,15,57,201,152,253,199,85,54,190,68,106,179,105,75,82,114,96,229,211,106,37,222,126,162,90,75,161,182,234,80,234,157,221,134,208,236,237,132,69,96,99,252,246,10,240,115,106,102,17,230,232,84,54,137,64,58,77,183,196,212,192,140,95,247,129,93,119,134,91,27,230,131,178,16,131,181,149,184,188,210,181,214,128,106,195,80,149,87,104,75,111,181,171,215,170,110,161,160,172,219,116,3,162,25,237,212,65,121,224,47,178,51,83,37,167,19,71,94,252,239,36,43,101,113,32,102,106,103,137,72,174,115,229,73,233,251,211,51,73,46,203,242,192,186,91,116,212,2,216,39,77,209,229,11,108,219,122,114,214,29,125,80,145,17,7,23,207,134,233,96,188,125,252,57,240,239,20,146,217,107,118,181,143,243,233,124,191,188,35,43,233,75,173,18,216,27,178,119,17,157,94,157,189,73,146,189,56,251,229,215,101,239,224,104,157,225,54,83,148,254,151,95,194,189,131,169,117,134,219,76,81,250,95,125,181,247,14,158,150,9,110,178,196,200,127,233,125,225,59,248,97,212,111,50,195,104,127,241,13,228,59,216,153,233,223,100,136,81,31,105,89,182,188,93,67,15,68,119,236,204,123,58,28,157,14,58,201,89,11,87,217,110,158,207,207,141,54,240,149,51,5,78,55,236,109,125,223,55,230,63,96,29,247,140,235,223,202,236,143,95,30,234,225,244,240,59,247,234,64,128,78,19,147,148,127,110,133,164,111,9,8,239,229,144,83,56,126,73,181,253,167,241,127,75,92,82,69,202,48,0,0 };
+const uint8_t CSS_STYLE_GZIP[3402] PROGMEM = { 31,139,8,0,193,22,6,94,2,255,213,27,219,110,171,72,242,87,216,68,145,98,13,176,220,109,131,102,180,143,251,182,210,206,227,106,118,212,134,38,160,96,240,66,251,36,25,139,127,223,190,1,125,35,118,46,163,213,30,159,147,131,155,170,234,186,117,85,117,117,199,205,187,22,129,186,133,253,229,212,13,53,170,187,54,237,97,3,80,253,3,102,47,117,129,170,116,187,127,200,142,160,127,170,219,52,240,78,175,217,161,123,117,134,250,143,186,125,74,15,93,95,192,222,193,35,163,155,119,205,249,216,218,252,255,225,194,144,125,207,123,200,202,166,3,40,109,96,137,48,24,232,139,11,35,231,160,238,148,6,15,25,167,210,131,162,62,15,105,50,77,81,129,162,123,73,61,43,58,189,210,127,253,211,1,60,6,94,100,251,251,173,237,239,98,219,115,227,77,118,2,69,129,89,113,8,117,198,223,52,210,215,79,21,31,226,243,29,58,132,186,35,230,137,12,225,239,140,197,216,35,223,49,219,93,159,222,151,101,57,254,237,8,139,26,60,30,193,43,135,72,66,12,177,185,112,222,103,196,253,238,97,100,2,57,67,83,23,68,133,124,106,97,162,209,69,231,254,63,231,174,30,224,229,0,242,231,167,190,59,183,69,122,239,131,67,190,207,179,89,131,20,225,222,79,128,183,139,173,16,139,59,116,152,230,232,194,35,236,65,83,72,184,1,204,243,173,175,226,6,91,0,19,79,196,61,65,4,251,30,91,178,151,208,195,104,191,43,14,26,250,126,231,29,246,34,250,11,68,96,56,85,160,65,10,122,180,143,161,134,158,135,48,150,102,31,206,45,54,252,139,50,121,233,231,145,87,170,216,48,57,28,188,82,196,198,106,237,59,121,98,152,108,97,16,168,168,69,24,71,158,52,49,104,234,63,64,95,183,50,242,54,202,67,77,223,185,23,238,131,131,136,92,128,254,89,66,140,162,104,23,111,85,68,54,42,34,54,224,0,155,139,113,113,100,47,85,141,160,51,156,64,14,211,182,123,233,193,73,113,122,55,128,199,201,113,83,215,199,223,44,55,34,63,124,252,51,67,240,21,57,88,170,167,54,205,97,139,109,42,120,107,86,226,5,236,188,64,234,236,91,207,203,26,188,152,157,138,125,247,21,207,143,177,163,23,245,112,106,192,91,90,183,20,242,208,116,249,179,137,65,236,53,168,206,65,195,39,62,128,1,18,132,76,15,19,100,25,59,174,31,99,78,23,197,57,156,197,253,126,111,88,126,92,93,14,153,137,71,138,61,14,20,34,27,167,30,210,215,217,11,214,20,125,74,15,61,4,207,14,249,206,241,93,58,9,150,225,44,46,174,105,234,164,220,131,194,151,65,123,88,24,32,139,112,155,111,115,25,242,169,135,176,53,192,238,15,121,144,236,100,216,174,7,237,147,137,133,2,2,63,142,100,224,55,216,224,101,97,0,134,251,34,137,20,126,79,231,254,212,152,40,239,203,93,136,133,155,98,213,28,148,34,143,199,170,57,174,179,241,93,132,131,149,6,61,71,54,5,154,132,54,45,162,115,51,242,184,58,3,164,101,221,15,200,201,171,186,41,102,96,113,112,66,164,33,218,27,221,174,133,51,237,229,121,202,24,145,155,204,127,182,120,18,244,210,169,57,37,116,195,249,15,129,168,176,169,20,152,128,240,87,118,231,94,25,15,61,87,162,94,98,7,86,65,246,50,249,161,126,85,89,36,218,25,224,15,216,42,47,226,68,166,78,87,161,2,147,196,50,249,182,214,84,176,141,136,84,26,245,93,160,80,111,12,44,236,125,69,57,47,176,209,68,164,89,89,183,138,131,170,186,47,56,236,138,194,176,57,24,216,32,195,169,98,17,114,56,117,148,50,20,213,92,87,150,3,68,206,225,205,145,60,193,48,58,72,174,179,83,60,99,193,88,124,196,54,142,202,116,252,173,194,234,130,33,120,146,189,50,46,211,10,18,137,128,224,113,182,121,88,70,15,35,119,69,36,193,49,109,243,176,76,41,10,215,132,90,252,215,54,142,202,116,226,64,70,22,92,204,94,25,151,9,36,222,154,72,226,106,176,87,198,21,90,251,53,161,132,85,99,155,135,101,74,91,217,241,144,81,40,180,34,210,46,89,21,169,89,209,143,178,54,69,106,251,120,77,40,117,9,218,239,188,187,213,143,180,245,106,191,247,242,86,245,43,171,219,94,127,165,123,215,88,161,99,115,161,101,11,174,148,96,154,4,110,252,48,30,186,226,141,131,166,94,182,188,245,93,82,87,200,69,141,88,242,224,124,199,190,151,224,88,55,111,233,221,63,78,176,181,126,5,237,112,103,15,248,39,246,212,190,46,167,130,41,32,245,163,158,120,243,210,43,253,177,242,237,42,176,171,208,174,34,187,138,237,42,17,247,41,158,82,197,184,113,143,249,18,57,9,61,15,147,16,4,139,122,149,115,55,200,26,136,112,1,71,107,28,82,235,225,186,9,67,141,85,32,224,133,110,98,192,140,215,80,67,17,85,71,12,215,240,34,1,47,112,13,204,134,134,41,189,29,197,141,47,162,133,118,58,174,9,53,166,168,137,132,26,235,168,137,138,234,173,214,45,146,186,99,77,145,145,27,232,26,162,202,149,165,15,53,153,130,21,86,199,241,36,57,197,8,46,220,135,124,8,96,113,24,65,90,117,100,191,197,71,189,18,120,57,28,15,103,236,51,237,197,88,122,79,85,191,207,246,214,226,174,32,148,246,165,230,226,122,188,63,226,194,237,239,16,144,253,167,105,130,241,30,23,119,191,34,128,206,195,178,197,175,219,10,47,11,36,172,51,119,139,215,25,103,212,118,57,195,74,225,78,20,224,158,157,242,220,52,204,14,98,57,177,210,22,56,59,100,31,45,160,44,219,234,247,209,78,4,133,214,154,23,214,63,160,207,243,27,18,78,46,98,99,1,177,173,209,69,219,44,141,85,47,218,140,185,156,34,25,117,148,73,251,140,59,111,250,74,144,252,105,159,103,221,67,159,124,198,165,100,78,65,137,103,177,221,190,123,153,30,207,78,94,94,8,0,102,32,189,187,155,247,92,8,28,26,152,229,13,4,61,150,22,85,140,74,223,53,166,173,67,81,136,22,175,143,224,9,166,196,172,128,236,77,176,131,96,218,143,213,208,128,71,207,246,30,232,95,215,223,216,203,8,81,47,29,219,40,110,21,79,106,103,29,150,186,197,145,219,242,44,34,227,244,79,163,18,111,108,79,128,209,222,111,5,128,192,4,16,8,0,225,53,128,232,26,64,34,1,76,28,100,60,134,236,103,3,167,33,222,68,131,51,234,178,181,238,214,158,248,52,183,130,117,110,46,83,24,34,190,57,45,77,47,67,120,131,55,148,93,127,76,251,14,47,37,248,24,197,5,124,218,44,152,77,125,145,149,76,8,88,132,93,79,215,181,67,68,112,110,209,181,65,70,99,16,225,92,19,203,146,240,67,250,46,105,85,23,5,108,179,169,201,245,32,138,137,249,77,91,84,177,253,217,99,176,185,104,2,238,61,89,64,21,39,52,224,56,215,144,34,3,146,191,211,145,192,100,134,128,154,65,239,56,104,51,51,123,112,97,9,214,66,143,135,100,91,24,40,187,28,71,67,109,205,153,92,78,160,115,209,151,17,95,171,184,60,154,237,194,12,66,2,43,94,226,77,97,249,240,248,215,208,18,74,17,189,155,67,71,10,152,119,61,160,114,182,184,134,98,131,115,19,116,213,89,162,13,211,6,211,144,235,199,195,194,49,121,192,251,240,227,119,69,151,216,16,94,176,138,54,223,30,77,130,255,121,52,217,225,149,198,42,86,108,101,177,62,9,167,200,226,176,202,116,118,78,112,192,9,226,140,88,59,140,96,241,253,179,24,95,38,131,168,30,57,143,175,56,230,61,132,112,116,135,151,26,229,149,49,201,91,127,169,143,167,174,71,160,69,134,50,225,0,201,71,201,2,145,220,103,159,236,230,81,109,208,94,59,214,6,249,184,225,70,172,66,242,115,63,224,231,83,87,83,223,213,221,158,215,75,132,254,180,138,19,189,7,31,144,78,253,74,76,78,72,41,132,203,234,195,115,141,28,193,187,85,201,44,55,24,44,8,6,232,96,194,221,25,101,206,177,251,227,99,8,221,7,233,15,31,130,255,0,236,100,94,55,175,96,254,108,236,82,110,147,34,240,139,9,208,170,219,211,25,253,11,189,157,224,207,119,20,9,91,243,238,183,217,61,104,16,81,140,53,39,55,172,95,214,10,145,204,32,185,240,14,191,153,234,52,210,176,173,219,203,58,224,66,204,96,54,242,210,194,59,135,247,109,181,2,213,221,66,105,184,14,116,13,64,213,191,85,212,63,46,108,111,189,35,226,115,165,99,53,164,7,136,83,143,124,154,35,151,231,41,149,77,141,166,88,87,54,1,180,60,252,159,71,62,22,137,69,27,9,145,107,111,70,98,68,108,202,50,33,64,31,216,26,178,89,135,120,192,195,143,30,165,188,17,135,104,156,227,19,109,140,147,124,138,193,238,115,104,216,68,159,193,211,113,38,225,205,152,52,194,137,213,58,53,140,20,247,2,245,124,209,91,78,23,165,136,55,87,239,211,170,217,173,174,21,39,158,214,128,179,159,151,13,137,122,146,223,208,205,129,236,54,148,249,27,61,135,9,202,44,253,189,158,195,124,196,224,60,216,165,62,224,57,87,24,236,62,135,182,234,57,239,227,189,231,57,38,76,165,120,167,59,111,205,3,130,57,143,165,180,124,39,206,67,30,12,110,193,67,103,64,124,160,127,62,206,71,196,114,190,212,51,160,144,65,73,214,148,90,91,255,236,176,8,157,216,215,146,104,75,25,129,158,72,225,116,160,110,7,86,57,93,210,53,121,236,72,239,5,189,145,131,129,43,51,252,228,222,40,153,176,233,159,234,130,173,214,242,240,73,207,99,173,130,153,250,64,60,157,165,37,182,241,230,114,51,131,124,214,72,155,212,27,111,150,210,226,255,59,101,221,52,107,34,43,170,245,50,113,83,121,30,72,107,11,54,48,71,44,71,255,129,51,80,1,95,83,255,227,76,84,160,45,26,120,81,178,188,57,70,81,231,101,135,61,147,41,98,193,230,241,82,162,177,64,182,211,212,68,138,90,33,141,130,166,153,139,152,117,169,130,175,25,77,21,149,69,91,23,115,123,221,102,41,54,15,105,184,20,179,7,24,28,203,35,31,161,124,254,56,85,201,37,236,207,163,203,198,44,96,9,206,13,186,82,216,127,51,247,243,23,122,4,253,101,89,100,114,114,101,250,231,11,227,214,131,3,114,18,122,190,44,200,66,74,112,192,91,87,209,183,137,241,205,214,89,35,172,216,201,28,2,104,243,133,0,140,95,101,35,229,138,21,106,50,66,87,116,252,101,198,33,7,13,124,244,55,107,94,50,145,198,115,230,61,68,179,100,234,34,211,179,147,57,10,223,72,79,81,224,74,145,232,111,221,185,78,140,102,255,33,39,25,147,255,132,218,198,151,72,237,116,125,77,74,14,172,124,90,173,100,235,111,116,107,105,212,22,29,42,189,179,235,16,134,189,157,148,4,86,198,175,103,128,207,169,153,173,176,192,164,50,46,2,233,52,93,19,211,0,51,126,221,7,172,225,4,214,54,204,91,45,17,123,75,43,113,190,210,181,212,128,122,195,80,151,87,106,75,175,181,171,151,170,110,166,160,229,109,186,1,49,140,14,250,160,58,240,39,217,153,169,82,208,73,160,38,255,27,201,42,81,220,147,35,117,48,175,72,161,115,21,41,225,251,195,51,41,46,203,226,192,178,91,12,244,2,56,38,77,209,249,135,183,110,107,238,172,22,125,209,144,145,0,23,207,182,19,96,188,77,246,49,240,239,20,146,217,107,114,181,247,227,233,244,125,190,35,171,232,75,175,18,216,13,217,155,136,242,171,179,87,73,178,139,179,95,190,46,123,3,71,203,12,215,153,162,244,191,124,9,247,6,166,150,25,174,51,69,233,127,245,106,239,13,60,205,19,92,101,137,145,255,210,125,225,27,248,97,212,175,50,195,104,127,241,6,242,13,236,76,244,175,50,196,168,143,180,44,155,111,215,208,3,81,139,157,121,243,195,81,126,208,73,206,90,132,202,118,245,124,126,106,180,121,95,57,83,16,116,195,174,246,199,177,61,253,243,220,221,102,100,169,229,255,142,109,86,3,215,197,191,127,190,107,207,199,187,223,132,27,15,9,60,114,38,41,255,66,98,167,151,27,164,235,68,228,240,80,172,4,124,126,73,138,52,166,127,57,55,110,11,126,212,79,244,188,112,185,54,181,28,88,203,94,22,46,13,209,36,73,212,35,226,81,162,70,142,178,155,122,192,211,162,183,6,178,76,191,220,179,200,164,75,192,36,61,170,200,211,161,214,32,93,219,80,161,44,112,209,132,125,247,122,12,49,5,109,97,112,207,222,237,118,89,119,70,4,146,156,207,155,78,80,165,216,20,146,207,45,13,83,210,24,244,166,94,97,217,119,199,71,114,250,182,177,81,135,31,34,242,81,122,162,166,142,45,65,177,39,240,247,218,146,50,160,115,170,177,220,55,66,235,154,231,217,24,92,120,5,230,144,59,136,104,224,71,65,84,111,244,134,189,164,25,226,16,159,83,11,54,1,83,11,38,113,139,78,48,188,77,97,223,85,200,2,117,85,27,51,232,136,215,132,139,112,145,205,123,182,191,144,195,155,121,49,208,74,106,188,39,239,177,190,82,120,60,161,55,105,147,139,99,46,217,171,78,45,218,105,57,41,195,75,116,145,95,252,254,59,187,253,63,105,14,156,78,152,77,208,230,124,241,48,60,188,215,200,105,91,221,114,44,92,142,227,154,107,35,29,199,41,59,55,210,130,157,189,123,89,214,102,214,56,7,233,108,188,169,170,170,206,199,195,42,95,102,62,3,97,59,26,120,198,30,160,210,110,52,30,114,90,228,38,130,124,190,118,51,199,252,10,157,254,219,82,102,26,204,235,87,132,191,149,8,115,89,246,134,97,190,171,14,45,133,124,179,94,232,61,128,53,145,228,195,43,86,202,144,125,163,237,205,73,107,69,214,31,128,252,166,140,57,206,174,244,235,67,249,151,227,164,187,16,84,33,250,238,84,191,196,56,57,112,204,19,181,116,131,158,110,177,116,54,249,113,217,149,51,103,103,59,231,253,185,205,238,9,71,55,163,108,217,30,27,226,50,153,144,101,106,14,64,53,142,109,210,194,222,214,199,177,161,136,71,78,136,195,143,39,165,247,175,31,110,176,64,241,230,128,215,122,176,78,0,85,182,251,186,124,185,12,56,71,62,195,20,7,179,183,140,61,79,183,34,51,210,245,227,113,137,28,242,192,97,2,30,16,132,13,249,101,39,25,35,20,49,10,128,128,67,93,112,176,242,186,207,27,120,5,59,96,216,52,49,200,232,196,178,74,156,212,169,243,197,74,105,172,204,144,172,227,253,36,205,33,93,91,165,111,152,115,181,121,133,221,15,182,197,248,95,172,216,174,129,175,58,0,0 };
diff --git a/src/dataTabbedcontentJS.h b/src/dataTabbedcontentJS.h
new file mode 100644
index 0000000..bbcca44
--- /dev/null
+++ b/src/dataTabbedcontentJS.h
@@ -0,0 +1,39 @@
+const char JS_TABBEDCONTENT[] PROGMEM = R"=====(
+;(function($,document,window,undefined){"use strict";var Tabbedcontent=function(tabcontent,options){var defaults={links:tabcontent.prev().find('a').length?tabcontent.prev().find('a'):'.tabs a',errorSelector:'.error-message',speed:false,onSwitch:false,onInit:false,currentClass:'active',tabErrorClass:'has-errors',history:true,historyOnInit:true,loop:false},firstTime=false,children=tabcontent.children(),history=window.history,loc=document.location,current=null;options=$.extend(defaults,options);if(!(options.links instanceof $)){options.links=$(options.links);}
+function tabExists(tab){return Boolean(children.filter(tab).length);}
+function isFirst(){return current===0;}
+function isInt(num){return num%1===0;}
+function isLast(){return current===children.length-1;}
+function filterTab(tab){return $(this).attr('href').match(new RegExp(tab+'$'));}
+function getTab(tab){if(tab instanceof $){return{tab:tab,link:options.links.eq(tab.index())};}
+if(isInt(tab)){return{tab:children.eq(tab),link:options.links.eq(tab)};}
+if(children.filter(tab).length){return{tab:children.filter(tab),link:options.links.filter(function(){return filterTab.apply(this,[tab]);})};}
+return{tab:children.filter('#'+tab),link:options.links.filter(function(){return filterTab.apply(this,['#'+tab]);})};}
+function getCurrent(){return options.links.parent().filter('.'+options.currentClass).index();}
+function next(loop){++current;if(loop===undefined)loop=options.loop;if(current=children.length){return switchTab(0,true);}
+return false;}
+function prev(loop){--current;if(loop===undefined)loop=options.loop;if(current>=0){return switchTab(current,true);}else if(loop&¤t<0){return switchTab(children.length-1,true);}
+return false;}
+function onSwitch(tab){if(options.history&&options.historyOnInit&&firstTime&&history!==undefined&&('pushState'in history)){firstTime=false;window.setTimeout(function(){history.replaceState(null,'',tab);},100);}
+current=getCurrent();if(options.onSwitch&&typeof options.onSwitch==='function'){options.onSwitch(tab,api());}
+tabcontent.trigger('tabcontent.switch',[tab,api()]);}
+function switchTab(tab,api){if(!tab.toString().match(/^#/)){tab='#'+getTab(tab).tab.attr('id');}
+if(!tabExists(tab)){return false;}
+options.links.attr('aria-selected','false').parent().removeClass(options.currentClass);options.links.filter(function(){return filterTab.apply(this,[tab]);}).attr('aria-selected','true').parent().addClass(options.currentClass);children.hide();if(options.history&&api){if(history!==undefined&&('pushState'in history)){history.pushState(null,'',tab);}else{window.location.hash=tab;}}
+children.attr('aria-hidden','true').filter(tab).show(options.speed,function(){if(options.speed){onSwitch(tab);}}).attr('aria-hidden','false');if(!options.speed){onSwitch(tab);}
+return true;}
+function apiSwitch(tab){return switchTab(tab,true);}
+function hashSwitch(e){switchTab(loc.hash);}
+function init(){if(tabExists(loc.hash)){switchTab(loc.hash);}
+else if(options.links.parent().filter('.'+options.currentClass).length){switchTab(options.links.parent().filter('.'+options.currentClass).index());}
+else if(options.errorSelector&&children.find(options.errorSelector).length){children.each(function(){if($(this).find(options.errorSelector).length){switchTab("#"+$(this).attr("id"));return false;}});}
+else{switchTab("#"+children.filter(":first-child").attr("id"));}
+if(options.errorSelector){children.find(options.errorSelector).each(function(){var tab=getTab($(this).parent());tab.link.parent().addClass(options.tabErrorClass);});}
+if('onhashchange'in window){$(window).bind('hashchange',hashSwitch);}else{var current_href=loc.href;window.setInterval(function(){if(current_href!==loc.href){hashSwitch.call(window.event);current_href=loc.href;}},100);}
+$(options.links).on('click',function(e){switchTab($(this).attr('href').replace(/^[^#]+/,''),options.history);e.preventDefault();});if(options.onInit&&typeof options.onInit==='function'){options.onInit(api());}
+tabcontent.trigger('tabcontent.init',[api()]);}
+function api(){return{'switch':apiSwitch,'switchTab':apiSwitch,'getCurrent':getCurrent,'getTab':getTab,'next':next,'prev':prev,'isFirst':isFirst,'isLast':isLast};}
+init();return api();};$.fn.tabbedContent=function(options){return this.each(function(){var tabs=new Tabbedcontent($(this),options);$(this).data('api',tabs);});};})(window.jQuery||window.Zepto||window.$,document,window);
+)=====";
+
+const uint8_t JS_TABBEDCONTENT_GZIP[1412] PROGMEM = { 31,139,8,0,193,22,6,94,2,255,173,88,221,111,219,54,16,127,239,95,225,56,158,73,193,178,146,188,90,85,7,44,235,128,2,3,134,45,125,90,145,14,140,68,219,90,21,74,19,169,124,192,245,255,190,59,138,164,72,197,113,179,172,47,182,73,30,239,251,126,119,116,74,215,157,200,85,89,11,58,139,139,58,239,110,185,80,241,125,41,138,250,62,238,68,193,215,165,224,69,180,155,118,146,79,164,106,203,92,77,211,59,214,78,62,178,155,27,94,228,181,80,112,33,115,76,20,187,49,123,113,221,224,142,140,118,72,14,140,88,87,41,153,237,170,82,124,145,171,129,46,105,90,126,71,163,4,4,21,148,48,18,37,21,23,27,181,253,241,8,201,138,36,112,42,39,140,196,188,109,235,246,138,87,60,87,117,11,251,122,189,188,229,82,178,13,39,177,108,56,47,86,107,86,73,30,215,226,234,190,84,249,214,45,63,136,82,153,69,222,181,45,72,186,172,152,148,43,194,192,152,59,184,13,66,222,35,63,179,189,101,114,169,249,75,18,111,75,9,2,31,87,170,237,184,93,252,214,115,212,91,85,93,55,61,239,125,188,46,91,169,62,150,183,60,51,194,182,101,85,128,184,204,51,209,238,209,200,114,203,250,40,36,102,9,28,243,204,70,40,129,5,67,247,90,197,51,209,85,85,106,92,158,205,18,254,0,92,11,106,221,238,130,145,150,107,122,66,205,42,209,177,152,148,66,42,38,114,94,175,39,179,40,218,5,135,217,44,36,142,210,253,27,27,236,9,186,231,1,180,147,24,246,104,215,114,213,181,98,242,83,93,87,156,9,106,45,130,176,85,138,183,154,198,196,54,224,82,202,95,208,63,212,49,176,38,101,217,121,72,247,65,40,42,186,91,71,8,191,127,184,120,74,246,43,59,204,205,41,212,43,177,188,240,175,245,74,66,86,7,182,204,168,2,247,71,9,83,170,165,100,219,242,53,228,231,45,131,36,162,130,223,79,254,224,155,247,15,13,222,88,144,25,137,2,179,54,92,57,110,224,116,248,14,29,109,68,236,224,0,171,33,70,247,174,2,95,39,252,31,188,150,64,26,240,7,26,69,123,96,15,156,122,63,32,223,128,135,179,174,191,21,61,207,208,50,58,22,160,131,156,61,186,67,220,205,177,195,2,231,69,231,219,132,53,77,245,168,125,26,127,2,54,215,224,49,173,205,17,105,228,148,44,190,147,68,195,202,73,245,99,117,217,167,201,192,34,148,212,176,254,212,105,149,144,133,165,240,177,35,178,209,242,217,11,168,70,138,128,16,237,22,11,67,141,133,136,91,144,151,3,204,234,13,39,24,22,72,101,46,188,29,165,175,83,84,106,80,195,92,51,148,49,2,16,40,192,1,107,38,70,204,124,110,14,223,101,223,230,115,110,57,188,177,238,68,216,242,45,210,128,220,91,180,92,190,214,162,119,217,249,43,141,120,123,240,226,184,188,191,105,133,109,8,174,72,173,162,6,114,231,243,209,70,15,240,243,185,3,244,249,220,156,156,120,70,207,231,148,52,157,220,94,41,166,56,41,197,196,208,64,189,142,58,65,106,32,94,114,189,89,119,202,207,101,115,45,105,121,83,177,156,107,118,20,113,62,38,186,53,129,105,241,197,249,57,90,104,65,206,79,228,212,51,200,90,58,159,171,199,6,241,103,124,0,65,35,86,52,25,122,128,239,161,152,53,37,213,24,231,181,45,152,8,54,27,172,7,111,175,15,9,209,21,222,95,186,14,202,97,8,153,33,208,190,63,65,168,83,245,21,112,20,27,106,81,246,236,243,233,25,248,13,206,50,172,94,15,84,113,2,48,184,92,22,36,234,33,237,36,108,73,3,30,152,208,135,69,221,223,102,109,201,150,82,79,16,188,32,49,209,180,0,243,174,228,91,126,91,223,113,93,222,244,96,205,167,223,5,7,159,81,7,147,216,215,134,21,197,49,85,92,21,108,203,130,135,73,224,178,218,186,252,191,229,174,77,71,119,60,202,69,172,212,157,73,104,59,158,36,48,50,109,113,204,73,247,144,164,86,53,207,80,208,178,224,98,48,211,111,69,114,91,223,59,229,245,32,23,123,62,245,12,211,103,144,180,126,57,131,192,232,160,32,19,94,61,8,29,103,96,113,3,85,243,243,23,220,231,227,198,19,32,194,172,182,208,227,46,161,35,204,45,30,237,6,98,240,148,118,82,56,18,1,202,80,59,54,152,124,118,132,207,221,182,72,249,218,198,101,219,193,192,253,127,182,192,67,74,5,227,58,224,249,208,236,97,90,61,72,52,40,54,76,56,12,188,24,166,130,29,212,94,194,103,48,112,122,58,93,4,35,222,180,44,166,160,119,136,26,123,107,200,232,230,120,82,153,174,52,188,47,245,254,52,228,168,209,233,176,94,187,23,57,97,108,51,62,170,16,21,13,34,90,43,108,144,162,20,225,17,3,119,4,57,130,231,13,66,80,175,38,169,5,230,84,190,101,98,163,49,160,47,234,104,55,163,230,87,114,163,95,98,30,85,60,228,183,133,2,212,208,228,197,95,56,57,103,58,89,225,135,215,245,96,146,229,237,29,171,70,209,244,175,1,58,217,139,128,65,78,74,146,179,170,50,250,36,252,14,168,1,251,14,74,219,187,46,57,126,203,64,123,163,36,175,202,252,11,25,144,37,168,206,131,243,191,233,199,208,155,62,125,62,189,94,156,1,4,70,241,8,100,163,148,235,87,43,168,243,115,255,6,163,218,193,65,79,238,135,137,39,29,25,183,159,237,199,120,72,95,218,137,17,71,160,15,31,232,193,122,203,14,249,196,116,236,149,67,182,152,56,31,4,187,195,124,65,86,195,111,189,175,73,251,239,152,224,196,75,86,248,25,19,116,3,89,225,103,76,204,91,143,172,204,15,220,193,231,26,110,224,183,126,153,104,240,179,53,168,21,77,247,233,44,89,11,76,216,27,94,92,142,255,116,112,255,52,88,192,134,32,60,87,48,50,195,135,91,240,239,133,141,242,240,72,182,97,47,152,98,208,63,154,82,247,56,83,34,240,97,243,238,239,223,59,222,62,126,253,106,150,127,242,70,213,110,245,228,255,148,40,253,23,8,208,63,203,110,17,0,0 };
diff --git a/src/dataZeptoJS.h b/src/dataZeptoJS.h
index d0585e1..de6217e 100644
--- a/src/dataZeptoJS.h
+++ b/src/dataZeptoJS.h
@@ -2,4 +2,4 @@ const char JS_ZEPTO[] PROGMEM = R"=====(
!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t)}):e(t)}(this,function(t){var e=function(){function $(t){return null==t?String(t):S[C.call(t)]||"object"}function F(t){return"function"==$(t)}function k(t){return null!=t&&t==t.window}function M(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function R(t){return"object"==$(t)}function Z(t){return R(t)&&!k(t)&&Object.getPrototypeOf(t)==Object.prototype}function z(t){var e=!!t&&"length"in t&&t.length,n=r.type(t);return"function"!=n&&!k(t)&&("array"==n||0===e||"number"==typeof e&&e>0&&e-1 in t)}function q(t){return a.call(t,function(t){return null!=t})}function H(t){return t.length>0?r.fn.concat.apply([],t):t}function I(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function V(t){return t in l?l[t]:l[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function _(t,e){return"number"!=typeof e||h[I(t)]?e:e+"px"}function B(t){var e,n;return c[t]||(e=f.createElement(t),f.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),c[t]=n),c[t]}function U(t){return"children"in t?u.call(t.children):r.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function X(t,e){var n,r=t?t.length:0;for(n=0;r>n;n++)this[n]=t[n];this.length=r,this.selector=e||""}function J(t,r,i){for(n in r)i&&(Z(r[n])||L(r[n]))?(Z(r[n])&&!Z(t[n])&&(t[n]={}),L(r[n])&&!L(t[n])&&(t[n]=[]),J(t[n],r[n],i)):r[n]!==e&&(t[n]=r[n])}function W(t,e){return null==e?r(t):r(t).filter(e)}function Y(t,e,n,r){return F(e)?e.call(t,n,r):e}function G(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function K(t,n){var r=t.className||"",i=r&&r.baseVal!==e;return n===e?i?r.baseVal:r:void(i?r.baseVal=n:t.className=n)}function Q(t){try{return t?"true"==t||("false"==t?!1:"null"==t?null:+t+""==t?+t:/^[\[\{]/.test(t)?r.parseJSON(t):t):t}catch(e){return t}}function tt(t,e){e(t);for(var n=0,r=t.childNodes.length;r>n;n++)tt(t.childNodes[n],e)}var e,n,r,i,O,P,o=[],s=o.concat,a=o.filter,u=o.slice,f=t.document,c={},l={},h={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},p=/^\s*<(\w+|!)[^>]*>/,d=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,m=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,g=/^(?:body|html)$/i,v=/([A-Z])/g,y=["val","css","html","text","data","width","height","offset"],x=["after","prepend","before","append"],b=f.createElement("table"),E=f.createElement("tr"),j={tr:f.createElement("tbody"),tbody:b,thead:b,tfoot:b,td:E,th:E,"*":f.createElement("div")},w=/complete|loaded|interactive/,T=/^[\w-]*$/,S={},C=S.toString,N={},A=f.createElement("div"),D={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},L=Array.isArray||function(t){return t instanceof Array};return N.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=A).appendChild(t),r=~N.qsa(i,e).indexOf(t),o&&A.removeChild(t),r},O=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},P=function(t){return a.call(t,function(e,n){return t.indexOf(e)==n})},N.fragment=function(t,n,i){var o,s,a;return d.test(t)&&(o=r(f.createElement(RegExp.$1))),o||(t.replace&&(t=t.replace(m,"<$1>$2>")),n===e&&(n=p.test(t)&&RegExp.$1),n in j||(n="*"),a=j[n],a.innerHTML=""+t,o=r.each(u.call(a.childNodes),function(){a.removeChild(this)})),Z(i)&&(s=r(o),r.each(i,function(t,e){y.indexOf(t)>-1?s[t](e):s.attr(t,e)})),o},N.Z=function(t,e){return new X(t,e)},N.isZ=function(t){return t instanceof N.Z},N.init=function(t,n){var i;if(!t)return N.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&p.test(t))i=N.fragment(t,RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}else{if(F(t))return r(f).ready(t);if(N.isZ(t))return t;if(L(t))i=q(t);else if(R(t))i=[t],t=null;else if(p.test(t))i=N.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}}return N.Z(i,t)},r=function(t,e){return N.init(t,e)},r.extend=function(t){var e,n=u.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){J(t,n,e)}),t},N.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,s=T.test(o);return t.getElementById&&s&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:u.call(s&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},r.contains=f.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},r.type=$,r.isFunction=F,r.isWindow=k,r.isArray=L,r.isPlainObject=Z,r.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},r.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},r.inArray=function(t,e,n){return o.indexOf.call(e,t,n)},r.camelCase=O,r.trim=function(t){return null==t?"":String.prototype.trim.call(t)},r.uuid=0,r.support={},r.expr={},r.noop=function(){},r.map=function(t,e){var n,i,o,r=[];if(z(t))for(i=0;i=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return o.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return F(t)?this.not(this.not(t)):r(a.call(this,function(e){return N.matches(e,t)}))},add:function(t,e){return r(P(this.concat(r(t,e))))},is:function(t){return this.length>0&&N.matches(this[0],t)},not:function(t){var n=[];if(F(t)&&t.call!==e)this.each(function(e){t.call(this,e)||n.push(this)});else{var i="string"==typeof t?this.filter(t):z(t)&&F(t.item)?u.call(t):r(t);this.forEach(function(t){i.indexOf(t)<0&&n.push(t)})}return r(n)},has:function(t){return this.filter(function(){return R(t)?r.contains(this,t):r(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!R(t)?t:r(t)},last:function(){var t=this[this.length-1];return t&&!R(t)?t:r(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?r(t).filter(function(){var t=this;return o.some.call(n,function(e){return r.contains(e,t)})}):1==this.length?r(N.qsa(this[0],t)):this.map(function(){return N.qsa(this,t)}):r()},closest:function(t,e){var n=[],i="object"==typeof t&&r(t);return this.each(function(r,o){for(;o&&!(i?i.indexOf(o)>=0:N.matches(o,t));)o=o!==e&&!M(o)&&o.parentNode;o&&n.indexOf(o)<0&&n.push(o)}),r(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=r.map(n,function(t){return(t=t.parentNode)&&!M(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return W(e,t)},parent:function(t){return W(P(this.pluck("parentNode")),t)},children:function(t){return W(this.map(function(){return U(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||u.call(this.childNodes)})},siblings:function(t){return W(this.map(function(t,e){return a.call(U(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return r.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=B(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=F(t);if(this[0]&&!e)var n=r(t).get(0),i=n.parentNode||this.length>1;return this.each(function(o){r(this).wrapAll(e?t.call(this,o):i?n.cloneNode(!0):n)})},wrapAll:function(t){if(this[0]){r(this[0]).before(t=r(t));for(var e;(e=t.children()).length;)t=e.first();r(t).append(this)}return this},wrapInner:function(t){var e=F(t);return this.each(function(n){var i=r(this),o=i.contents(),s=e?t.call(this,n):t;o.length?o.wrapAll(s):i.append(s)})},unwrap:function(){return this.parent().each(function(){r(this).replaceWith(r(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var n=r(this);(t===e?"none"==n.css("display"):t)?n.show():n.hide()})},prev:function(t){return r(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return r(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;r(this).empty().append(Y(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=Y(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,r){var i;return"string"!=typeof t||1 in arguments?this.each(function(e){if(1===this.nodeType)if(R(t))for(n in t)G(this,n,t[n]);else G(this,t,Y(this,r,e,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(i=this[0].getAttribute(t))?i:e},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){G(this,t)},this)})},prop:function(t,e){return t=D[t]||t,1 in arguments?this.each(function(n){this[t]=Y(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=D[t]||t,this.each(function(){delete this[t]})},data:function(t,n){var r="data-"+t.replace(v,"-$1").toLowerCase(),i=1 in arguments?this.attr(r,n):this.attr(r);return null!==i?Q(i):e},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=Y(this,t,e,this.value)})):this[0]&&(this[0].multiple?r(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(e){if(e)return this.each(function(t){var n=r(this),i=Y(this,e,t,n.offset()),o=n.offsetParent().offset(),s={top:i.top-o.top,left:i.left-o.left};"static"==n.css("position")&&(s.position="relative"),n.css(s)});if(!this.length)return null;if(f.documentElement!==this[0]&&!r.contains(f.documentElement,this[0]))return{top:0,left:0};var n=this[0].getBoundingClientRect();return{left:n.left+t.pageXOffset,top:n.top+t.pageYOffset,width:Math.round(n.width),height:Math.round(n.height)}},css:function(t,e){if(arguments.length<2){var i=this[0];if("string"==typeof t){if(!i)return;return i.style[O(t)]||getComputedStyle(i,"").getPropertyValue(t)}if(L(t)){if(!i)return;var o={},s=getComputedStyle(i,"");return r.each(t,function(t,e){o[e]=i.style[O(e)]||s.getPropertyValue(e)}),o}}var a="";if("string"==$(t))e||0===e?a=I(t)+":"+_(t,e):this.each(function(){this.style.removeProperty(I(t))});else for(n in t)t[n]||0===t[n]?a+=I(n)+":"+_(n,t[n])+";":this.each(function(){this.style.removeProperty(I(n))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(r(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?o.some.call(this,function(t){return this.test(K(t))},V(t)):!1},addClass:function(t){return t?this.each(function(e){if("className"in this){i=[];var n=K(this),o=Y(this,t,e,n);o.split(/\s+/g).forEach(function(t){r(this).hasClass(t)||i.push(t)},this),i.length&&K(this,n+(n?" ":"")+i.join(" "))}}):this},removeClass:function(t){return this.each(function(n){if("className"in this){if(t===e)return K(this,"");i=K(this),Y(this,t,n,i).split(/\s+/g).forEach(function(t){i=i.replace(V(t)," ")}),K(this,i.trim())}})},toggleClass:function(t,n){return t?this.each(function(i){var o=r(this),s=Y(this,t,i,K(this));s.split(/\s+/g).forEach(function(t){(n===e?!o.hasClass(t):n)?o.addClass(t):o.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var n="scrollTop"in this[0];return t===e?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var n="scrollLeft"in this[0];return t===e?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),i=g.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(r(t).css("margin-top"))||0,n.left-=parseFloat(r(t).css("margin-left"))||0,i.top+=parseFloat(r(e[0]).css("border-top-width"))||0,i.left+=parseFloat(r(e[0]).css("border-left-width"))||0,{top:n.top-i.top,left:n.left-i.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||f.body;t&&!g.test(t.nodeName)&&"static"==r(t).css("position");)t=t.offsetParent;return t})}},r.fn.detach=r.fn.remove,["width","height"].forEach(function(t){var n=t.replace(/./,function(t){return t[0].toUpperCase()});r.fn[t]=function(i){var o,s=this[0];return i===e?k(s)?s["inner"+n]:M(s)?s.documentElement["scroll"+n]:(o=this.offset())&&o[t]:this.each(function(e){s=r(this),s.css(t,Y(this,i,e,s[t]()))})}}),x.forEach(function(n,i){var o=i%2;r.fn[n]=function(){var n,a,s=r.map(arguments,function(t){var i=[];return n=$(t),"array"==n?(t.forEach(function(t){return t.nodeType!==e?i.push(t):r.zepto.isZ(t)?i=i.concat(t.get()):void(i=i.concat(N.fragment(t)))}),i):"object"==n||null==t?t:N.fragment(t)}),u=this.length>1;return s.length<1?this:this.each(function(e,n){a=o?n:n.parentNode,n=0==i?n.nextSibling:1==i?n.firstChild:2==i?n:null;var c=r.contains(f.documentElement,a);s.forEach(function(e){if(u)e=e.cloneNode(!0);else if(!a)return r(e).remove();a.insertBefore(e,n),c&&tt(e,function(e){if(!(null==e.nodeName||"SCRIPT"!==e.nodeName.toUpperCase()||e.type&&"text/javascript"!==e.type||e.src)){var n=e.ownerDocument?e.ownerDocument.defaultView:t;n.eval.call(n,e.innerHTML)}})})})},r.fn[o?n+"To":"insert"+(i?"Before":"After")]=function(t){return r(t)[n](this),this}}),N.Z.prototype=X.prototype=r.fn,N.uniq=P,N.deserializeValue=Q,r.zepto=N,r}();return t.Zepto=e,void 0===t.$&&(t.$=e),function(e){function h(t){return t._zid||(t._zid=n++)}function p(t,e,n,r){if(e=d(e),e.ns)var i=m(e.ns);return(a[h(t)]||[]).filter(function(t){return t&&(!e.e||t.e==e.e)&&(!e.ns||i.test(t.ns))&&(!n||h(t.fn)===h(n))&&(!r||t.sel==r)})}function d(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function m(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function g(t,e){return t.del&&!f&&t.e in c||!!e}function v(t){return l[t]||f&&c[t]||t}function y(t,n,i,o,s,u,f){var c=h(t),p=a[c]||(a[c]=[]);n.split(/\s/).forEach(function(n){if("ready"==n)return e(document).ready(i);var a=d(n);a.fn=i,a.sel=s,a.e in l&&(i=function(t){var n=t.relatedTarget;return!n||n!==this&&!e.contains(this,n)?a.fn.apply(this,arguments):void 0}),a.del=u;var c=u||i;a.proxy=function(e){if(e=T(e),!e.isImmediatePropagationStopped()){e.data=o;var n=c.apply(t,e._args==r?[e]:[e].concat(e._args));return n===!1&&(e.preventDefault(),e.stopPropagation()),n}},a.i=p.length,p.push(a),"addEventListener"in t&&t.addEventListener(v(a.e),a.proxy,g(a,f))})}function x(t,e,n,r,i){var o=h(t);(e||"").split(/\s/).forEach(function(e){p(t,e,n,r).forEach(function(e){delete a[o][e.i],"removeEventListener"in t&&t.removeEventListener(v(e.e),e.proxy,g(e,i))})})}function T(t,n){return(n||!t.isDefaultPrevented)&&(n||(n=t),e.each(w,function(e,r){var i=n[e];t[e]=function(){return this[r]=b,i&&i.apply(n,arguments)},t[r]=E}),t.timeStamp||(t.timeStamp=Date.now()),(n.defaultPrevented!==r?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(t.isDefaultPrevented=b)),t}function S(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===r||(n[e]=t[e]);return T(n,t)}var r,n=1,i=Array.prototype.slice,o=e.isFunction,s=function(t){return"string"==typeof t},a={},u={},f="onfocusin"in t,c={focus:"focusin",blur:"focusout"},l={mouseenter:"mouseover",mouseleave:"mouseout"};u.click=u.mousedown=u.mouseup=u.mousemove="MouseEvents",e.event={add:y,remove:x},e.proxy=function(t,n){var r=2 in arguments&&i.call(arguments,2);if(o(t)){var a=function(){return t.apply(n,r?r.concat(i.call(arguments)):arguments)};return a._zid=h(t),a}if(s(n))return r?(r.unshift(t[n],t),e.proxy.apply(null,r)):e.proxy(t[n],t);throw new TypeError("expected function")},e.fn.bind=function(t,e,n){return this.on(t,e,n)},e.fn.unbind=function(t,e){return this.off(t,e)},e.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var b=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,w={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};e.fn.delegate=function(t,e,n){return this.on(e,t,n)},e.fn.undelegate=function(t,e,n){return this.off(e,t,n)},e.fn.live=function(t,n){return e(document.body).delegate(this.selector,t,n),this},e.fn.die=function(t,n){return e(document.body).undelegate(this.selector,t,n),this},e.fn.on=function(t,n,a,u,f){var c,l,h=this;return t&&!s(t)?(e.each(t,function(t,e){h.on(t,n,a,e,f)}),h):(s(n)||o(u)||u===!1||(u=a,a=n,n=r),(u===r||a===!1)&&(u=a,a=r),u===!1&&(u=E),h.each(function(r,o){f&&(c=function(t){return x(o,t.type,u),u.apply(this,arguments)}),n&&(l=function(t){var r,s=e(t.target).closest(n,o).get(0);return s&&s!==o?(r=e.extend(S(t),{currentTarget:s,liveFired:o}),(c||u).apply(s,[r].concat(i.call(arguments,1)))):void 0}),y(o,t,u,a,n,l||c)}))},e.fn.off=function(t,n,i){var a=this;return t&&!s(t)?(e.each(t,function(t,e){a.off(t,n,e)}),a):(s(n)||o(i)||i===!1||(i=n,n=r),i===!1&&(i=E),a.each(function(){x(this,t,i,n)}))},e.fn.trigger=function(t,n){return t=s(t)||e.isPlainObject(t)?e.Event(t):T(t),t._args=n,this.each(function(){t.type in c&&"function"==typeof this[t.type]?this[t.type]():"dispatchEvent"in this?this.dispatchEvent(t):e(this).triggerHandler(t,n)})},e.fn.triggerHandler=function(t,n){var r,i;return this.each(function(o,a){r=S(s(t)?e.Event(t):t),r._args=n,r.target=a,e.each(p(a,t.type||t),function(t,e){return i=e.proxy(r),r.isImmediatePropagationStopped()?!1:void 0})}),i},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(t){e.fn[t]=function(e){return 0 in arguments?this.bind(t,e):this.trigger(t)}}),e.Event=function(t,e){s(t)||(e=t,t=e.type);var n=document.createEvent(u[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),T(n)}}(e),function(e){function p(t,n,r){var i=e.Event(n);return e(t).trigger(i,r),!i.isDefaultPrevented()}function d(t,e,n,i){return t.global?p(e||r,n,i):void 0}function m(t){t.global&&0===e.active++&&d(t,null,"ajaxStart")}function g(t){t.global&&!--e.active&&d(t,null,"ajaxStop")}function v(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||d(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void d(e,n,"ajaxSend",[t,e])}function y(t,e,n,r){var i=n.context,o="success";n.success.call(i,t,o,e),r&&r.resolveWith(i,[t,o,e]),d(n,i,"ajaxSuccess",[e,n,t]),b(o,e,n)}function x(t,e,n,r,i){var o=r.context;r.error.call(o,n,e,t),i&&i.rejectWith(o,[n,e,t]),d(r,o,"ajaxError",[n,r,t||e]),b(e,n,r)}function b(t,e,n){var r=n.context;n.complete.call(r,e,t),d(n,r,"ajaxComplete",[e,n]),g(n)}function E(t,e,n){if(n.dataFilter==j)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function j(){}function w(t){return t&&(t=t.split(";",2)[0]),t&&(t==c?"html":t==f?"json":a.test(t)?"script":u.test(t)&&"xml")||"text"}function T(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function S(t){t.processData&&t.data&&"string"!=e.type(t.data)&&(t.data=e.param(t.data,t.traditional)),!t.data||t.type&&"GET"!=t.type.toUpperCase()&&"jsonp"!=t.dataType||(t.url=T(t.url,t.data),t.data=void 0)}function C(t,n,r,i){return e.isFunction(n)&&(i=r,r=n,n=void 0),e.isFunction(r)||(i=r,r=void 0),{url:t,data:n,success:r,dataType:i}}function O(t,n,r,i){var o,s=e.isArray(n),a=e.isPlainObject(n);e.each(n,function(n,u){o=e.type(u),i&&(n=r?i:i+"["+(a||"object"==o||"array"==o?n:"")+"]"),!i&&s?t.add(u.name,u.value):"array"==o||!r&&"object"==o?O(t,u,r,n):t.add(n,u)})}var i,o,n=+new Date,r=t.document,s=/