1
0
mirror of https://github.com/s00500/ESPUI.git synced 2025-07-04 01:00:19 +00:00

113 Commits
2.1.0 ... 2.2.2

Author SHA1 Message Date
fd1cc14e87 Bump Version
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2023-08-01 13:01:49 +02:00
372157b980 Merge pull request #241 from d-a-v/membreathe
reduce memory footprint
2023-08-01 13:00:52 +02:00
f08cd5c51a Icon changes, thanks to Mark Kinsley
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2023-07-20 11:39:20 +02:00
b45f4f7356 update examples with esp8266 specifics 2023-07-19 12:07:25 +02:00
c3bf9c5d00 reduce memory footprint 2023-07-18 19:49:58 +02:00
678a2ccf53 Merge pull request #235 from MartinMueller2003/master
Exposed  WebServer, Added password support.
2023-05-13 15:55:39 +02:00
270f0dd16b Merge pull request #218 from d-a-v/ptr2long
pointers can always be casted to long
2023-05-13 15:53:59 +02:00
be8cf73d55 Exposed WebServer instance 2023-05-12 15:02:32 -04:00
cc69f8a469 pointers can always be be casted to long 2023-01-17 14:53:02 +01:00
48e947287a Added variant of the text type to support hidden password fields 2022-11-27 16:59:28 -05:00
5cf0ce6afa Merge pull request #212 from leokeba/master
Small fixes for graph UI element
2022-11-24 11:50:59 +01:00
35c2f5b6b1 small fixes to graph UI element to make X-axis labels legible and meaningful 2022-11-21 16:05:29 +01:00
b1b9fd2c79 Setting SVG overflow property to 'visible' to properly display numbers on the graph UI element 2022-11-21 15:33:56 +01:00
0aaca5449b Merge pull request #211 from leokeba/master
Fixed step handler to allow using float numbers and sliders
2022-11-14 11:22:34 +01:00
7b4ec5920a Fixed step handler to allow using float numbers and sliders 2022-11-11 17:29:02 +01:00
048f337d61 Merge pull request #208 from leokeba/master
fix: initialize graph data with empty array if null
2022-11-06 14:58:18 +01:00
8e3c12935e fix: Use ControlType Constant 2022-11-05 14:06:13 +01:00
f35d3a19d4 feat: implemented clearGraph function 2022-11-03 03:31:04 +01:00
48594de29a fix: initialize graph data with empty array if null 2022-11-03 01:37:46 +01:00
d53fdb57a9 Bump version 2022-10-24 13:51:53 +02:00
b8079cfb63 Bump version 2022-10-24 13:51:34 +02:00
e3ccc216bb Merge pull request #204 from ncmreynolds/main
Several LittleFS changes, fixes #192, adds option to not format LittleFS
2022-10-24 12:59:52 +02:00
09a550e614 Many LittleFS related changes/fixes 2022-10-13 21:10:20 +01:00
309e53dd1e Sort out includes for LittleFS 2022-10-13 21:09:05 +01:00
4b489c7ce5 Support ESP-IDF versions prior to 4.4/Arduino Core 2.0 2022-10-13 20:52:19 +01:00
0c9586730e Add feature to optionally _not_ format LittleFS to prepareFileSystem() 2022-10-13 19:53:55 +01:00
004a374d79 Add a little more feedback to the user and make it list the files. 2022-10-13 19:52:50 +01:00
e3eea09e43 Merge pull request #199 from MartinMueller2003/master
Significant restructuring by MartinMueller
2022-10-11 21:50:58 +02:00
bce624b0f2 Added more robust retry when a socket error occurs. 2022-09-27 10:31:07 -04:00
1595b3bd04 Fixed bug that caused a crash when sending graphpoint updates to multiple browsers. 2022-09-22 09:36:12 -04:00
a18254b16b Fixed include issues for LittleFS 2022-09-22 09:34:53 -04:00
811e0ede4d Cosmetic changes to debug messages 2022-09-22 09:34:11 -04:00
ea4a0df4d0 Major Restructuring next part 2022-09-21 15:45:48 -04:00
a2923e501f Major restructuring of the code to get past an issue in the web socket where many changes are requested but the WS API cant handle the requests. Requests are now tracked per UI client.
Moved the client out of the main code into its own code and moved control data marshaling into the control class.
NONE of these changes impact the users API. No code changes are needed by the users.
WARNING: The LittleFS support for the ESP32 has been updated to be compatible with the latest ESP32 infrastructure. This includes using an improved WebServer.
2022-09-21 15:37:20 -04:00
fdffb9c041 Added a check for an invalid json format to prevent the UI from crashing. Requests a new reload when the issue is encountered. 2022-09-21 15:33:05 -04:00
05ab1734e6 Merge pull request #197 from ncmreynolds/main
Add captive portal functionality, update examples and documentation, resolves #134
2022-09-21 10:06:57 +02:00
79b9acf9f4 Changes per suggestions by maintainer (Lukas Bachschwell). 2022-09-21 08:58:05 +01:00
90ba90a717 Make captive portal redirect on by default. 2022-09-20 15:29:34 +01:00
95f2f9bdd0 Merge pull request #190 from iangray001/master
Small bugfix for inline styles
2022-09-20 10:15:36 +02:00
e81cd1f7f5 Add captive portal functionality, update examples and documentation. 2022-09-19 17:38:10 +01:00
a150d2db1e Merge branch 's00500:master' into master 2022-07-17 11:20:31 +01:00
86c469f5cb Fix small bug applying styles to input elements 2022-07-17 11:17:22 +01:00
70acbc1d2f Merge pull request #180 from MartinMueller2003/master
Added a user data parameter to the callbacks to support passing a 'this' pointer for lambda functions
2022-06-26 17:18:13 +02:00
9d9a165693 Added an example using the button shortcut 2022-06-25 10:25:05 -04:00
a553d32570 Corrected class example 2022-06-25 07:40:09 -04:00
36f6932538 formatting 2022-06-25 07:31:34 -04:00
ba9f83a93b Formatting changes 2022-06-25 07:29:44 -04:00
782d339f83 Merge branch 's00500:master' into master 2022-06-25 07:17:37 -04:00
c303668a3c Added description and usage for the extended callback functionality 2022-06-25 07:16:51 -04:00
e0ff99f7b6 Merge pull request #177 from iangray001/master
Small fix for slider moving when grouped
2022-06-12 13:43:33 +01:00
511840fa97 Added an example for using the new extended parameter callback 2022-06-11 10:56:35 -04:00
1419b2dec0 Updated the rest of the callback functions to support the new UserData parameter 2022-06-11 01:20:25 -04:00
02e847a31e First part of extending callback to include a user parm.
Converted addControl to no longer use C Style function definitions. Now using C++ function overload to support multiple invocation modes.
2022-06-11 00:42:25 -04:00
9cb962122c Reworked removeControl function 2022-06-10 12:44:53 -04:00
7056cdf044 Update keywords to max 50 chars
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2022-05-31 08:27:11 +02:00
0f3a808e9e Bump version
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2022-05-30 21:35:15 +02:00
e3debb9689 Update output files 2022-05-29 10:14:52 +01:00
e059fe5d79 Merge branch 's00500-master' 2022-05-29 10:14:02 +01:00
83e1d0a1c9 Merge branch 'master' of https://github.com/s00500/ESPUI into s00500-master 2022-05-29 10:13:37 +01:00
59430669af Merge pull request #176 from pcbbc/master
Added 'type' property to text input elements
2022-05-29 10:00:25 +01:00
834a2a2010 Update README.md 2022-05-24 12:36:12 +01:00
59633c3f74 Merge branch 'master' of https://github.com/pcbbc/ESPUI 2022-05-24 11:21:57 +01:00
a3b253665d Updated documentation 2022-05-24 11:21:34 +01:00
31973aeac9 Update README.md 2022-05-23 14:10:28 +01:00
b47d595194 Update ESPUI.cpp 2022-05-23 00:06:42 +01:00
a53124de42 Added 'type' proptert to text input elements
Now the text input element can support other HTML input types suxh as date, time, color, password, etc
2022-05-22 23:31:48 +01:00
b28752f9c2 Merge branch 'master' of github.com:iangray001/ESPUI 2022-02-26 20:33:37 +00:00
a958feb152 Merge pull request #163 from iangray001/master
Fix for #162
2022-02-26 18:04:29 +00:00
c63537a358 Fix issue where grouped sliders are moved programatically. 2022-02-16 21:10:16 +00:00
ab5ac2dc1d Fix regression caused by f06781bc03. See comments in commit on Github. 2022-02-16 20:28:55 +00:00
d77daf0c8e Initialise the enabled value to fix #162 2022-02-16 15:40:31 +00:00
eb21863e09 Use early return to tidy up logic 2022-02-16 14:52:25 +00:00
e3e5c34953 Merge pull request #154 from iangray001/enabledisable
Support for Enabling and Disabling controls
2022-02-12 17:43:06 +00:00
c2dadf536e Fix #158, min/max not being applied correctly to grouped elements. 2022-02-11 20:47:15 +00:00
0fe60e91a9 Merge branch 'master' into enabledisable 2022-02-11 19:57:36 +00:00
b1fd9f71a4 Merge pull request #155 from d-a-v/emuAsync
minor adjustments for emulation on host via emuAsync lib (see readme)
2022-02-07 14:51:10 +01:00
271fd9dd02 doc: change title (review) 2022-02-05 17:46:42 +01:00
c131a6ad1c +doc 2022-02-02 16:47:43 +01:00
f06781bc03 minor fixes for emulation on host
(https://github.com/d-a-v/emuAsync)
2022-02-02 11:19:43 +01:00
9a4cb277e9 Also support disabling the Pads 2022-01-31 22:34:00 +00:00
d8b9c35655 Add active styles to Pad to give click feedback 2022-01-31 22:06:54 +00:00
896dc97c75 Support dynamic enabling and disabling of controls
See documentation in README for how this works. This commit
handles all the main controls. Pads are not yet supported.
2022-01-31 21:43:09 +00:00
7dcf6a10f4 Merge pull request #152 from iangray001/master
Vertical controls and a major rehaul of the documentation
2022-01-29 14:52:56 +00:00
576890e033 Dynamic visibility support
The ability to make controls visible and invisible
was basically already supported, we just need to add
some minor handling in controls.js.
2022-01-28 20:31:25 +00:00
d9a4854855 Merge branch 's00500:master' into master 2022-01-28 17:02:13 +00:00
f2fa9ba652 Update header files 2022-01-28 16:57:04 +00:00
a797ae1afa Update example with vertical controls and more documentation 2022-01-28 16:47:19 +00:00
c12f97de48 Update documentation to explain vertical layout
Also explains how to label groups of sliders/switchers.
Also makes a tiny tweak to the width of vertical sliders so they are the same width as switchers.
2022-01-28 16:45:26 +00:00
df0c6faa4d Add updated style header 2022-01-28 15:31:53 +00:00
ba048185db Add major new example 2022-01-25 21:35:33 +00:00
64b30d13a6 Major documentation rework 2022-01-25 21:32:34 +00:00
446f83c6c2 Merge pull request #149 from iangray001/master
One bugfix, one small feature
2022-01-23 17:59:14 +00:00
334bf16b09 Initial support for vertical switchers and sliders.
Documentation to be added in a later commit.
2022-01-21 23:30:08 +00:00
9cd15db1ad Add Time control
Adds a new invisible control that can fetch the current time from a
connected client. Documentation is in the README.
2022-01-20 21:50:06 +00:00
b33be0057f Merge branch 's00500:master' into master 2022-01-20 20:45:44 +00:00
bc34078f42 Fix bug with multiple clients
If two clients were connected at the same time, the chunked updates
from one could cause the other to recieve UI_EXTEND messages. This
is fixed by passing the client in to jsonDom.
2022-01-20 20:43:04 +00:00
fa097ce329 Merge pull request #148 from iangray001/chunkupdates2
Make initial UI update reliable (and 4 small fixes)
2022-01-19 19:32:07 +00:00
55b291eb50 Support update of Button text 2022-01-16 14:47:41 +00:00
bc6e2bb8af Correct call to elementHTML 2022-01-15 21:44:53 +00:00
fed603e219 Fix error in graph HTML output 2022-01-15 15:13:28 +00:00
12a3fb6c80 Fix bug with sliders getting out of sync. 2022-01-15 14:29:38 +00:00
9eddf2c504 Improve documentation. 2022-01-15 14:29:03 +00:00
6a513e9f65 Allow Max control to add maxlength to text input. Update docs. 2022-01-14 20:24:23 +00:00
254912aee5 Add a null check in updateControl
This means that calling any updates before the GUI is created will
now not cause a crash.
2022-01-13 21:29:22 +00:00
3b8f71a0f5 Fix a bug where the Dark colour wasn't being correctly applied. 2022-01-13 18:26:50 +00:00
a9460c8c69 Allow runtime updates to button controls. 2022-01-13 17:26:00 +00:00
dd0fe3165f Fix bug with updating slider values programatically 2022-01-12 23:53:55 +00:00
c059e30763 Fix issue with switchers getting out of sync with the DOM if updated. 2022-01-12 23:12:45 +00:00
5909471962 Further improve the reliability of UI element transfer.
A protocol has been implemented between the server and client to acknowledge
each UI_INITIAL_GUI and UI_EXTEND_GUI from the client javascript. This
prevents the internal websocket buffers from becoming flooded when the
number of controls gets too high.
2022-01-12 20:12:20 +00:00
cf535110ea Merge pull request #147 from iangray001/designupdates
Separators, grouped controls, and wide controls
2022-01-09 11:01:46 +00:00
f8462bcd48 Documentation typos 2022-01-08 21:31:19 +00:00
f5dd757240 Add support for wide panels.
Allows for panels to be displayed in single column mode, regardless of screen width.
For more information, see updates to README.md
2022-01-08 21:25:10 +00:00
9b228800f9 Implement separators, and grouped controls
This adds separator elements, and allows arbitrary grouping of controls. For details
see the updates to README.md
2022-01-08 20:58:22 +00:00
49 changed files with 4165 additions and 2246 deletions

660
README.md
View File

@ -1,10 +1,11 @@
# ESPUI
![ESPUI](https://github.com/s00500/ESPUI/blob/master/docs/ui_complete.png)
![ESPUI](docs/ui_complete.png)
ESPUI is a simple library to make a web user interface for your projects using
ESPUI is a simple library to make a web-based user interface for your projects using
the **ESP8266** or the **ESP32** It uses web sockets and lets you create,
control, and update elements on your GUI through multiple devices like phones
ol, and update elements on your GUI through multiple devices like phones
and tablets.
ESPUI uses simple Arduino-style syntax for creating a solid, functioning user
@ -14,35 +15,63 @@ 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.
The Library runs fine on any kind of **ESP8266** and **ESP32** (NodeMCU Boards, usw)
The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker, etc.).
## Changelog for 2.0:
- [Dependencies](#dependencies)
- [How to Install](#how-to-install)
- [Getting started](#getting-started)
- [UI Elements](#documentation)
* [Button](#button)
* [Switch](#switch)
* [Buttonpad](#buttonpad)
* [Labels](#labels)
* [Slider](#slider)
* [Number Input](#number-input)
* [Text Input](#text-input)
* [Date, Time, Colour and Password Input](#date-time-colour-and-password-input)
* [Select control](#select-control)
* [Getting the Time](#getting-the-time)
* [Separators](#separators)
- [Initialisation of the UI](#initialisation-of-the-ui)
- [Tabs](#tabs)
- [Log output](#log-output)
- [Colours](#colours)
- [Advanced Features](#advanced-features)
* [Dynamic Visibility](#dynamic-visibility)
* [Inline Styles](#inline-styles)
* [Disabling Controls](#disabling-controls)
* [Grouped controls](#grouped-controls)
* [Wide controls](#wide-controls)
* [Graph (Experimental)](#graph--experimental-)
* [Captive Portal](#captive-portal)
- [Notes for Development](#notes-for-development)
- [Contribute](#contribute)
- 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
### Contributed features
- Tabs by @eringerli #45
- Tabs by @eringerli
- 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)
- Inline CSS styles by @iangray001
- Separators by @iangray001
- Grouped and wide controls by @iangray001
- Transport layer rework by @iangray001
- Time control by @iangray001
- Vertical controls by @iangray001
- Time/date/password/color input types by @pcbbc
## Further Roadmap
## Roadmap
- Slider css issues
- implement Gauge
- File upload ?
- Fully implement graphs
- Expand number input features (floats etc.)
- Support for enabling and disabling controls
## Dependencies
This library is dependent on the following libraries to function properly.
This library is dependent on the following libraries.
- [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) (Last tested with
@ -56,37 +85,25 @@ This library is dependent on the following libraries to function properly.
Make sure all the dependencies are installed, then install like so:
#### Using PlattformIO (_recommended_)
#### Using PlatformIO (_recommended_)
Just include this library as a dependency on lib_deps like so:
Just include this library as a dependency in `lib_deps` like so:
```
lib_deps =
ESPUI
ESPAsyncWebserver
ESPAsyncTCP # or AsyncTCP on ESP32
ESP Async WebServer
ESPAsyncTCP # (or AsyncTCP on ESP32)
LittleFS_esp32 # (ESP32 only)
```
#### Directly Through Arduino IDE (_recommended_)
#### Using the 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
You can find this Library in the Arduino IDE library manager. Go to
`Sketch > Include Library > Library Manager` search for `ESPUI` and install.
#### Manual Install Arduino IDE
_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
[Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the
.zip in Sketchbook/Libraries/{Place "ESPUI" folder Here}
_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}
Go to Sketch>Include Library>Add .zip Library> Select the Downloaded .zip File.
If you cannot use the Library Manager, you can download the [repository](https://github.com/s00500/ESPUI/archive/master.zip) and follow
the [instructions to manually install libraries](https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library).
## Getting started
@ -118,102 +135,120 @@ more program memory to work with.
- Button
- Switch
- Control pad
- Control pad with center button
- Slider
- Text Input
- Date, Time, Colour and Password Input
- Numberinput
- Graph
- Option select
- Separator
- Time
- Graph (partial implementation)
Checkout the example for the usage or see the detailed info below
## Available colors:
- Turquoise
- Emerald
- Peterriver
- Wetasphalt
- Sunflower
- Carrot
- Alizarin
- Dark
- None
(Use like `ControlColor::Sunflower`)
## Documentation
The heart of ESPUI is
[ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer). ESPUI's
frontend is based on [Skeleton CSS](http://getskeleton.com/) and jQuery-like
lightweight [zepto.js](https://zeptojs.com/) for Handling Click Events Etc. The
communication between the _ESP_ and the client browser works using web
sockets. ESPUI does not need network access and can be used in standalone access
point mode, all resources are loaded directly from the ESPs memory.
This section will explain in detail how the Library is to be used from the
Arduino code side. In the arduino `setup()` routine the interface can be customised by adding UI Elements.
This is done by calling the corresponding library methods on the Library object
`ESPUI`. Eg: `ESPUI.button("button", &myCallback);` creates a button in the
interface that calls the `myCallback(Control *sender, int value)` function when changed. All buttons and
items call their callback whenever there is a state change from them. This means
the button will call the callback when it is pressed and also again when it is
released. To separate different events an integer number with the event name is
passed to the callback function that can be handled in a `switch(){}case{}`
statement. Here is an overview of the currently implemented different elements
of the UI library:
The heart of ESPUI is [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer). ESPUI's frontend is based on [Skeleton CSS](http://getskeleton.com/) and jQuery-like lightweight [zepto.js](https://zeptojs.com/) for handling events. The communication between the ESP and the client browser works using web sockets. ESPUI does not need network access and can be used in standalone access point mode, all resources are loaded directly from the ESPs memory.
<br><br>
This section will explain in detail how the Library is to be used from the Arduino code side. In the arduino `setup()` routine the interface can be customised by adding UI Elements. This is done by calling the corresponding library methods on the Library object `ESPUI`. Eg: `ESPUI.button("button", &myCallback);` creates a button in the interface that calls the `myCallback(Control *sender, int eventname)` function when changed. All buttons and items call their callback whenever there is a state change from them. This means the button will call the callback when it is pressed and also again when it is released. To separate different events, an integer number with the event name is passed to the callback function that can be handled in a `switch(){}case{}` statement.
<br><br>
Alternativly you may use the extended callback funtion which provides three parameters to the callback function `myCallback(Control *sender, int eventname, void * UserParameter)`. The `UserParameter` is provided as part of the `ESPUI.addControl` method set and allows the user to define contextual information that is to be presented to the callback function in an unmodified form.
<br><br>
The below example creates a button and defines a lambda function to implicitly create an `ExtendedCallback` which then invokes a more specialized button callback handler. The example uses the `UserParameter` to hold the `this` pointer to an object instance, providing a mechanism for sending the event to a specific object without the need for a switch / map / lookup translation of the Sender Id to an object reference.
```
void YourClassName::setup()
{
ButtonElementId = ESPUI.addControl(
ControlType::Button,
ButtonLabel.c_str(),
" Button Face Text ",
ControlColor::None,
ParentElementId,
[](Control *sender, int eventname, void* param)
{
if(param)
{
reinterpret_cast<YourClassName*>(param)->myButtonCallback(sender, eventname);
}
},
this); // <-Third parameter for the extended callback
// or
ButtonElementId = ESPUI.button(
" Button Face Text ",
[](Control *sender, int eventname, void* param)
{
if(param)
{
reinterpret_cast<YourClassName*>(param)->myButtonCallback(sender, eventname);
}
},
this); // <-Third parameter for the extended callback
}
```
```
void YourClassName::myButtonCallback(Control* sender, int eventname)
{
if (eventname == B_DOWN)
{
// Handle the button down event
}
else if (eventname == B_UP)
{
// Handle the button up event
}
}
```
<br>
<br>
#### Button
![Buttons](https://github.com/s00500/ESPUI/blob/master/docs/ui_button.png)
![Buttons](docs/ui_button.png)
Buttons have a name and a callback value. They have one event for press (`B_DOWN`) and one
for release (`B_UP`).
Buttons have a name and a callback value. Their text can be changed at runtime using `ESPUI.updateButton()`.
- B_DOWN
- B_UP
Events:
- `B_DOWN` - Fired when button is pressed.
- `B_UP` - Fired when button is released.
#### Switch
![Switches](https://github.com/s00500/ESPUI/blob/master/docs/ui_switches.png)
![Switches](docs/ui_switches.png)
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 when turning on (`S_ACTIVE`)
and one when turning off (`S_INACTIVE`).
their value (either by pressing them, or programmatically using `ESPUI.updateSwitcher()`) they change visibly
on all tablets or computers that currently display the interface.
- S_ACTIVE
- S_INACTIVE
Events:
- `S_ACTIVE` - Fired when turning on.
- `S_INACTIVE` - Fired when turning off.
#### Buttonpad
![control pads](https://github.com/s00500/ESPUI/blob/master/docs/ui_controlpad.png)
![control pads](docs/ui_controlpad.png)
Button pads come in two flavours: with or without a center button. They are very
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.
Button pads come in two flavours: with or without a center button. They are
useful for controlling movements of vehicles/cameras etc. 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
- `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](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
on creation and a initial value. The name is not changeable once the UI
initialised.
Labels are used to display textual information (i.e. states, values of sensors,
configuration parameters etc.). To send data from the code use `ESP.updateLabel()` .
Labels get a name on creation and a initial value.
Labels automatically wrap your text. If you want them to have multiple lines use
the normal `<br>` tag in the string you print to the label.
@ -230,107 +265,207 @@ This requires that the client has access to the image in question, either from t
#### Slider
![slider](https://github.com/s00500/ESPUI/blob/master/docs/ui_slider.png)
![slider](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. 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.
Sliders can be used to select (or display) a numerical value. Sliders provide
realtime data and are touch compatible. Note that like all ESPUI functions, the callback does not return an `int`
but a `String` so should be converted with the `.toInt()` function. See the examples for more details. Sliders can
be updated from code using `ESP.updateSlider()`.
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()`:
A slider usually only sends a new value when it is released to save network bandwidth.
This behaviour can be changed globally by setting `sliderContinuous` before `begin()`:
```
ESPUI.sliderContinuous = true;
ESPUI.begin("ESPUI Control");
ESPUI.sliderContinuous = true;
ESPUI.begin("ESPUI Control");
```
Events:
- `SL_VALUE` - Fired when a slider value changes.
#### Number Input
![number](https://github.com/s00500/ESPUI/blob/master/docs/ui_number.png)
![number](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:
The number input can be used to receive numbers from the user. 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);`
Number inputs can be updated from code using `ESP.updateNumber()`.
Note that HTML number boxes will respect their min and max when the user
clicks the up and down arrows, but it is possible on most clients to simply type
any number in. As with all user input, numbers should be validated in callback code
because all client side checks can be skipped. If any value from the UI might
cause a problem, validate it.
Events:
- `N_VALUE` - Fired when a number value changes.
#### Text Input
![text](https://github.com/s00500/ESPUI/blob/master/docs/ui_text.png)
![text](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:
The text input works very similar like the number input but allows any string to be entered.
If you attach a Max control to the text input then a max length will be applied
to the control.
```
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 );
text = ESPUI.text("Label", callback, ControlColor::Dark, "Initial value");
ESPUI.addControl(ControlType::Max, "", "32", ControlColor::None, text);
```
Check the **tabbedGui** example for a working demo
Text inputs can be updated from code using `ESP.updateText()`.
### Using Tabs
However even with a set maximum length, user input should still be validated
because it is easy to bypass client-side checks. Never trust user input.
![tabs](https://github.com/s00500/ESPUI/blob/master/docs/ui_tabs.png)
Events:
- `T_VALUE` - Fired when a text value changes.
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)
#### Date, Time, Colour and Password Input
`ESPUI.addControl( ControlType::Text, "Text Title:", "a Text Field", ControlColor::Alizarin, tab1, &textCall );`
![text](docs/ui_inputtypes.png)
As an extension to the text input control, you can also specify the type attribute to be used for the HTML input element.
This allows you to easily create input controls for Date, Time, Colour and Passwords, or indeed any other
[HTML Input Types](https://www.w3schools.com/html/html_form_input_types.asp) supported by your browser.
```
text_date = ESPUI.text("Date", callback, ControlColor::Dark, "2022-05-24");
ESPUI.setInputType(text_date, "date");
text_time = ESPUI.text("Time", callback, ControlColor::Dark, "13:00");
ESPUI.setInputType(text_time, "time");
text_colour = ESPUI.text("Colour", callback, ControlColor::Dark, "#FF0000");
ESPUI.setInputType(text_colour, "color");
text_password = ESPUI.text("Password", callback, ControlColor::Dark, "tiddles123");
ESPUI.setInputType(text_password, "password");
```
*Important!* This function should be called _before_ `ESPUI.begin` or results will be unreliable.
Note that not all browsers support all input types, and that the control displayed to edit the input is browser dependent.
However even with a type set, user input should still be validated
because it is easy to bypass client-side checks. Never trust user input.
#### Select control
![option1](docs/ui_select1.png)
![option2](docs/ui_select2.png)
The Select control lets the user select from a predefined list of options. First create a select widget like so
```
uint16_t select1 = ESPUI.addControl( ControlType::Select, "Select Title", "Initial Value", ControlColor::Alizarin, tab1, &selectExample );
```
Then add Options to it, 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. Selectors can be updated from code using `ESP.updateSelect()`.
Events:
- `S_VALUE` - Fired when a select value changes.
#### Getting the Time
ESPUI can create an invisible control that can be used to fetch the current time from the client
when they are connected to the UI. This could be used to intermittently provide an accurate time
source to your ESP. Remember that clients cannot be relied upon to be correct or truthful.
Create a Time control with the following:
```
//Add the invisible "Time" control
auto timeId = ESPUI.addControl(Time, "", "", None, 0, timeCallback);
```
After creating the UI, sending an update to the Time control will cause it to fetch the current
time from the client and then fire its callback with the result.
```
//Request an update to the time
ESPUI.updateTime(timeId);
//Will trigger timeCallback
```
In `timeCallback` you can then print the control's value as normal:
```
void timeCallback(Control *sender, int type) {
if(type == TM_VALUE) {
Serial.println(sender->value);
}
}
```
The returned string will be an [ISO string](https://www.w3schools.com/jsref/jsref_toisostring.asp) as returned by the Javascript `new Date().toISOString()`. The format is `YYYY-MM-DDTHH:mm:ss.sssZ` so for example: `2022-01-20T21:44:22.913Z`.
Events:
- `TM_VALUE` - Fires when the control is updated with `updateTime()`
#### Separators
![separators](docs/ui_separators.png)
You can use separators to break up the UI and better organise your controls. Adding a separator will force any following controls onto the subsequent line. Add separators as follows:
```
ESPUI.separator("Separator name");
//or
ESPUI.addControl(ControlType::Separator, "Separator name", "", ControlColor::None, maintab);
```
Separators fire no events.
### Initialisation of the UI
After all the elements are configured you can use `ESPUI.begin("Some Title");`
After all the elements are configured, call `ESPUI.begin("Some Title");`
to start the UI interface. (Or `ESPUI.beginLITTLEFS("Some Title");` respectively)
Make sure you setup a working network connection or AccesPoint **before** (See
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.
Make sure you setup a working network connection or AccessPoint **before** (see
the `gui.ino` example). The web interface can then be used from multiple devices at once and
also shows 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);`
### Tabs
Then its value can be updated by doing:
![tabs](docs/ui_tabs.png)
`ESPUI.updateControlValue(status, "Start");`
You can also update other parameters of the control like its color using:
Tabs can be used to organize your controls into pages. Check the **tabbedGui** example to see
how this is done. Tabs can be created as follows:
```
ESPUI.getControl(switchOne)->color = ControlColor::Carrot;
ESPUI.updateControl(switchOne);
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 parent. 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);
```
Note that the basic functions to add controls like `ESPUI.button()` or `ESPUI.slider()` do not add to any tab,
so they are good for building small UIs. However if you need to use tabs then you will have to add all your
controls using the full `ESPUI.addControl()` function.
### Log output
ESPUI has several different log levels. You can set them using the
@ -344,24 +479,39 @@ Loglevels are:
VerboseJSON outputs the most debug information.
### Advanced properties
### Colours
If you have many different widgets it might be necessary to adjust the JSON Buffers used internally in ESPUI before .begin() :
A selection of basic colours are available to use:
![Colours](docs/ui_colours.png)
If you want more control over the UI design, see the Inline Styles section below.
## Advanced Features
ESPUI includes a range of advanced features that can customise your UIs.
### Dynamic Visibility
Controls can be made visible or invisible at runtime with the `updateVisibility()` function.
```
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");
ESPUI.updateVisibility(controlId, false);
```
Note that you cannot hide individual controls from a [control group](#grouped-controls), you have to hide the entire group.
### Inline Styles
You can add custom CSS styles to controls. This allows you to style the UI with custom colors, drop shadows,
or other CSS effects. Add styles with the following functions:
```
setPanelStyle(uint16_t id, String style);
setElementStyle(uint16_t id, String style)
setPanelStyle(uint16_t id, String style);
setElementStyle(uint16_t id, String style)
```
A panel style is applied to the panel on which the UI element is placed, an element style is applied to the element itself.
@ -375,19 +525,133 @@ be used to refect a warning state by changing the color of a button, or for simi
For example, this code will set a control's panel to a random background color:
```
char stylecol[30];
sprintf(stylecol, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
ESPUI.setPanelStyle(switch1, stylecol);
char stylecol[30];
sprintf(stylecol, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
ESPUI.setPanelStyle(switch1, stylecol);
```
You can get quite creative with this.
![Inline Styles](docs/inlinestyles.gif)
Note: The images in this example are formed by setting a Label to contain an `<img>` tag:
The [completeExample](examples/completeExample/completeExample.cpp) example includes a range of things that you can do with inline styles.
![More Inline Styles](docs/ui_inlinestyles2.png)
### Disabling Controls
It is possible to dynamically enable and disable controls to, for example, provide feedback to the user that a particular feature is
temporarily unavailable. To do this use the following function call:
```
ESPUI.addControl(ControlType::Label, "Label", "<img src='path/to/image'>", ControlColor::Peterriver);
ESPUI.setEnabled(controlId, enabled);
```
Setting `enabled` to false will make the control noninteractive and it will visually change to illustrate this to the user. The control
will stop firing any events. Note that whilst the widget will change appearance, the panel of the control will remain whatever colour
it was set to. If you wish to also change the colour of the panel then you should use inline styles to show the noninteractive state. For example:
```
ESPUI.setEnabled(mainButton, false);
const String disabledstyle = "background-color: #bbb; border-bottom: #999 3px solid;";
ESPUI.setPanelStyle(mainButton, disabledstyle);
```
This CSS style sets the panel background and its border to grey. To put the control back to enabled use the following:
```
ESPUI.setEnabled(mainButton, true);
ESPUI.setPanelStyle(mainButton, ";");
```
Note that we have to set the inline style to `";"` (i.e. an empty CSS rule) because if we just try to set it to `""` this will be
interpreted as "do not change the style".
Controls can also be set to disabled before the UI is started.
### Grouped controls
Normally, whenever a control is added to the UI, a new panel is generated with a title. However, you can instead
set the "parent" of a new control to be an existing control. This allows you to add multiple widgets into the same
panel. For example:
```
panel1 = ESPUI.addControl(ControlType::Button, "Button Set", "Button A", ControlColor::Turquoise, Control::noParent, btncallback);
ESPUI.addControl(ControlType::Button, "", "Button B", ControlColor::None, panel1, btncallback);
ESPUI.addControl(ControlType::Button, "", "Button C", ControlColor::None, panel1, btncallback);
```
The first call to `addControl` has no parent (or it could be set to a tab if you are using a tabbed UI), so therefore a new panel is added containing one button
with the value `Button A`. The two subsequent calls have their parent set to the first control we added, so instead of creating
a new panel, the result is the following:
![Grouped buttons](docs/ui_groupedbuttons.png)
The grouped controls operate entirely independently, and can be assigned different callbacks, or updated separately. The grouping
is purely visual.
Most controls can be grouped this way, but the result is not always visually pleasant. This works best with labels, sliders, switchers,
and buttons.
![Other grouped controls](docs/ui_groupedbuttons2.png)
For sliders and switchers, you can also set the controls to be displayed vertically.
```
auto vertswitcher = ESPUI.addControl(Switcher, "Vertical Switcher", "0", Dark, tab1);
ESPUI.setVertical(vertswitcher);
```
This must be done before `ESPUI.begin()` is called. Vertical layouts are currently only supported for sliders and switchers, and it
is a purely visual change. Behaviour is identical. Mixing horizontal and vertical controls can result in some unpredictable layouts.
When you add multiple buttons to a single panel, the buttons have a title so they can be differentiated. For sliders and switchers this is
not the case. Therefore you might want to add additional labels so that the controls can be distinguished. There is not yet automatic
support for doing this, so the approach is to add labels that have been styled using [inline styles](#inline-styles). By doing this
you can acheieve effects such as this:
![Labelling grouped controls](docs/ui_groupedbuttons3.png)
The code to do this is in the [completeExample](examples/completeExample/completeExample.cpp) example.
### Wide controls
Controls can be set to be displayed "wide" with the function:
```
ESPUI.setPanelWide(controlid, true);
```
*Important!* This function should be called _before_ `ESPUI.begin` or results will be unreliable.
Setting a control to wide tells ESPUI to lay out that control as if there was only a single column, even on wide displays.
This can be applied to every element to force a single column layout, or to individual elements to customise the display.
![Wide controls](docs/ui_widecontrols.png)
Note that this will have no effect on small screens.
### Graph (Experimental)
![graph](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.
_There are many issues with the graph component currently and work is ongoing. Consider helping us out with development!_
### Captive Portal
ESPUI will redirect all unknown URLs it is asked for to the 'root' of the local HTTP server instead of responding with an HTTP code 404. This makes it act as a simple 'captive portal'. Note you must also set up the ESP to be a DNS server that responds to all DNS requests with the IP address of the ESP. This only effective when the ESP is acting as a WiFi hotspot in AP mode and assigning itself as the DNS server to connected clients.
All the example sketches include the DNS related code and will work as captive portals when used as a hotspot. In the event you wish to disable this feature you can do so by removing the DNS server code and adding the code below.
```
ESPUI.captivePortal = false;
```
@ -410,6 +674,34 @@ 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. (Do **NOT** commit all the minified versions for the non changed files)
# Experimental debugging environment using emulation on host
It is possible to run or debug this library on a unix-like computer (Linux,
macOS, WSL) without flashing on hardware, and with your favourite debugging
tools (gdb, valgrind, ...). This is accomplished through the
esp8266/Arduino "emulation on host" environment.
A fake AsyncWebserver library is needed because lwIP is not yet ported to
the emulation environment. Full instructions can be found in this project's
[readme page](https://github.com/d-a-v/emuAsync).
## Changelog for 2.1:
- Adds the ability to have inline styles for widgets and panels
- Adds LittleFS on the ESP32
- Cleans up examples
- Adds Button Animation
- Adds chunking for the widgets so you can add even more of them
- Fixes lots of bugs related to invisible UI elements and sliders
- Adds the ability to change port
## Changelog for 2.0:
- ArduinoJSON 6.10.0 Support
- Split pad into pad and padWithCenter
- Cleaned order of parameters on switch and pad
- Changes all numbers to actually be numbers (slider value, number value, min and max)
# Contribute
Liked this Library? You can **support** me by sending me a :coffee:

View File

@ -111,7 +111,7 @@
}
svg:not(:root) {
overflow: hidden;
overflow: visible;
}
/* Grouping content

View File

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

View File

@ -13,22 +13,44 @@
.card {
min-height: 100px;
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;
margin-bottom: 40px;
min-width: 500px;
color: #fff;
}
@media (min-width: 1205px) {
.wide.card {
min-width: 1075px;
}
}
@media (min-width: 1790px) {
.wide.card {
min-width: 1650px;
}
}
@media (max-width: 630px) {
.card {
min-width: 98%;
}
}
.sectionbreak.columns {
color: black;
}
.sectionbreak.columns hr {
border: none;
height: 2px;
background-color: #666
}
.card-slider {}
.turquoise {
@ -137,7 +159,7 @@
.column,
.columns {
margin-right: 2%;
margin-right: 35px;
}
.column:first-child,
@ -400,7 +422,7 @@ button {
background-color: #999999;
}
button:active {
button:enabled:active {
background-color: #666666;
transform: translateX(4px) translateY(4px);
}
@ -423,6 +445,8 @@ button:active {
button,
.button {
margin-bottom: 1rem;
margin-left: 0.3rem;
margin-right: 0.3rem;
}
/* Utilities
@ -563,6 +587,13 @@ hr {
background-color: #eee;
}
.control:not(.disabled) a.confirm:active {
background-color:#777
}
.control:not(.disabled) li:active {
background-color:#777
}
/* Switch
*/
@ -576,7 +607,8 @@ hr {
display: block;
font-size: 14px;
height: 26px;
margin-bottom: 12px;
margin-left: 0.3rem;
margin-right: 0.3rem;
position: relative;
width: 60px;
-webkit-transition: background-color 0.2s ease-in-out;
@ -933,6 +965,7 @@ input[id^="num"] {
body div>ul.navigation {
margin: 0;
margin-bottom: 30px;
padding: 0;
border-bottom: 3px solid #666;
overflow: hidden;
@ -1102,3 +1135,66 @@ svg {
text {
text-anchor: end;
}
/* Styles to implement vertical orientations */
.vert-switcher {
transform: rotate(270deg);
margin-top: 15px;
margin-bottom: 25px;
}
.vert-slider {
width: 150px;
transform: rotate(270deg);
display: inline-block;
margin: 50px -42px 70px -42px;
}
.vert-slider span {
transform: rotate(90deg);
}
/* Styles to implement disabled controls */
button:disabled {
color: #333;
background-color: #999;
}
select:disabled {
color: #333;
background-color: #999;
}
input:disabled {
color: #333;
background-color: #999;
}
.range-slider__range:disabled {
background-color: #999;
}
.range-slider__range:disabled::-webkit-slider-thumb {
background-color: #aaa;
}
.range-slider__range:disabled::-moz-range-thumb {
background-color: #aaa;
}
.switch.disabled .in::before {
background:#bbb;
border: 1px solid #ddd;
}
.switch.disabled .in::after {
background:#bbb;
}
.switch.checked.disabled {
background: #b1d092;
}

File diff suppressed because one or more lines are too long

1521
data/js/controls.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,191 +1,24 @@
const UI_INITIAL_GUI=200;const UI_RELOAD=201;const UPDATE_OFFSET=100;const UI_EXTEND_GUI=210;const UI_TITEL=0;const UI_PAD=1;const UPDATE_PAD=101;const UI_CPAD=2;const UPDATE_CPAD=102;const UI_BUTTON=3;const UPDATE_BUTTON=103;const UI_LABEL=4;const UPDATE_LABEL=104;const UI_SWITCHER=5;const UPDATE_SWITCHER=105;const UI_SLIDER=6;const UPDATE_SLIDER=106;const UI_NUMBER=7;const UPDATE_NUMBER=107;const UI_TEXT_INPUT=8;const UPDATE_TEXT_INPUT=108;const UI_GRAPH=9;const ADD_GRAPH_POINT=10;const CLEAR_GRAPH=109;const UI_TAB=11;const UPDATE_TAB=111;const UI_SELECT=12;const UPDATE_SELECT=112;const UI_OPTION=13;const UPDATE_OPTION=113;const UI_MIN=14;const UPDATE_MIN=114;const UI_MAX=15;const UPDATE_MAX=115;const UI_STEP=16;const UPDATE_STEP=116;const UI_GAUGE=17;const UPDATE_GAUGE=117;const UI_ACCEL=18;const 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(){}
const UI_INITIAL_GUI=200;const UI_RELOAD=201;const UPDATE_OFFSET=100;const UI_EXTEND_GUI=210;const UI_TITEL=0;const UI_PAD=1;const UPDATE_PAD=101;const UI_CPAD=2;const UPDATE_CPAD=102;const UI_BUTTON=3;const UPDATE_BUTTON=103;const UI_LABEL=4;const UPDATE_LABEL=104;const UI_SWITCHER=5;const UPDATE_SWITCHER=105;const UI_SLIDER=6;const UPDATE_SLIDER=106;const UI_NUMBER=7;const UPDATE_NUMBER=107;const UI_TEXT_INPUT=8;const UPDATE_TEXT_INPUT=108;const UI_GRAPH=9;const ADD_GRAPH_POINT=10;const CLEAR_GRAPH=109;const UI_TAB=11;const UPDATE_TAB=111;const UI_SELECT=12;const UPDATE_SELECT=112;const UI_OPTION=13;const UPDATE_OPTION=113;const UI_MIN=14;const UPDATE_MIN=114;const UI_MAX=15;const UPDATE_MAX=115;const UI_STEP=16;const UPDATE_STEP=116;const UI_GAUGE=17;const UPDATE_GAUGE=117;const UI_ACCEL=18;const UPDATE_ACCEL=118;const UI_SEPARATOR=19;const UPDATE_SEPARATOR=119;const UI_TIME=20;const UPDATE_TIME=120;const UP=0;const DOWN=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_DARK=7;const C_NONE=255;var graphData=new Array();var hasAccel=false;var sliderContinuous=false;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";case C_EMERALD:return"emerald";case C_PETERRIVER:return"peterriver";case C_WETASPHALT:return"wetasphalt";case C_SUNFLOWER:return"sunflower";case C_CARROT:return"carrot";case C_ALIZARIN:return"alizarin";case C_DARK:case C_NONE:return"dark";default:return"";}}
var websock;var websockConnected=false;var WebSocketTimer=null;function requestOrientationPermission(){}
function saveGraphData(){localStorage.setItem("espuigraphs",JSON.stringify(graphData));}
function restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);return savedData[id];}
function restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);let idData=savedData[id];return Array.isArray(idData)?idData:[];}
return[];}
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 &#8635;");$("#conStatus").off();$("#conStatus").on({click:restart,});}
function restart(){$(document).add("*").off();$("#row").html("");conStatusError();start();}
function conStatusError(){if(true===websockConnected){websockConnected=false;websock.close();$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection &#8635;");$("#conStatus").off();$("#conStatus").on({click:restart,});}}
function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);if(window.location.port!=""||window.location.port!=80||window.location.port!=443){websock=new WebSocket("ws://"+window.location.hostname+":"+window.location.port+"/ws");}else{websock=new WebSocket("ws://"+window.location.hostname+"/ws");}
websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log(evt);conStatusError();};var handleEvent=function(evt){console.log(evt);var data=JSON.parse(evt.data);var e=document.body;var center="";panelStyle=data.hasOwnProperty('panelStyle')?" style='"+data.panelStyle+"'":"";elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"'":"";switch(data.type){case UI_INITIAL_GUI:$("#row").html("");$("#tabsnav").html("");$("#tabscontent").html("");if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;}
data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});break;case UI_RELOAD:window.location.reload();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");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<span id='l"+
data.id+
"' "+elementStyle+" class='label label-wrap'>"+
data.value+
"</span>"+
"</div>");}
break;case UI_BUTTON:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='one columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<button id='btn"+
data.id+
"' "+elementStyle+" "+
"onmousedown='buttonclick("+
data.id+
", true)' "+
"onmouseup='buttonclick("+
data.id+
", false)'>"+
data.value+
"</button></div>");$("#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");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='one columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<label id='sl"+
data.id+
"' "+elementStyle+" class='switch "+
(data.value=="1"?"checked":"")+
"'>"+
"<div class='in'><input type='checkbox' id='s"+
data.id+
"' onClick='switcher("+
data.id+
",null)' "+
(data.value=="1"?"checked":"")+
"/></div>"+
"</label>"+
"</div>");switcher(data.id,data.value);}
break;case UI_CPAD:case UI_PAD:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<nav class='control'>"+
"<ul>"+
"<li><a onmousedown='padclick(UP, "+
data.id+
", true)' onmouseup='padclick(UP, "+
data.id+
", false)' id='pf"+
data.id+
"'>&#9650;</a></li>"+
"<li><a onmousedown='padclick(RIGHT, "+
data.id+
", true)' onmouseup='padclick(RIGHT, "+
data.id+
", false)' id='pr"+
data.id+
"'>&#9650;</a></li>"+
"<li><a onmousedown='padclick(LEFT, "+
data.id+
", true)' onmouseup='padclick(LEFT, "+
data.id+
", false)' id='pl"+
data.id+
"'>&#9650;</a></li>"+
"<li><a onmousedown='padclick(DOWN, "+
data.id+
", true)' onmouseup='padclick(DOWN, "+
data.id+
", false)' id='pb"+
data.id+
"'>&#9650;</a></li>"+
"</ul>"+
(data.type==UI_CPAD?"<a class='confirm' onmousedown='padclick(CENTER,"+
data.id+
", true)' onmouseup='padclick(CENTER, "+
data.id+
", false)' id='pc"+
data.id+
"'>OK</a>":"")+
"</nav>"+
"</div>");$("#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");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter card-slider "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<div class='range-slider'>"+
"<input id='sl"+
data.id+
"' type='range' min='0' max='100' value='"+
data.value+
"' "+elementStyle+" class='range-slider__range'>"+
"<span class='range-slider__value'>"+
data.value+
"</span>"+
"</div>"+
"</div>");rangeSlider(!sliderContinuous);}
break;case UI_NUMBER:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<input style='color:black;' "+elementStyle+" id='num"+
data.id+
"' type='number' value='"+
data.value+
"' onchange='numberchange("+
data.id+
")' />"+
"</div>");}
break;case UI_TEXT_INPUT:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<input style='color:black;' "+elementStyle+" id='text"+
data.id+
"' value='"+
data.value+
"' onchange='textchange("+
data.id+
")' />"+
"</div>");}
if(null===WebSocketTimer){WebSocketTimer=setInterval(function(){if(websock.readyState===3){restart();}},5000);}
websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log("websock Error");console.log(evt);restart();};var handleEvent=function(evt){console.log(evt);try{var data=JSON.parse(evt.data);}
catch(Event){console.error(Event);websock.send("uiok:"+0);return;}
var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:$("#row").html("");$("#tabsnav").html("");$("#tabscontent").html("");if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;}
data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>(data.controls.length-1)){websock.send("uiok:"+(data.controls.length-1));}
break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>data.startindex+(data.controls.length-1)){websock.send("uiok:"+(data.startindex+(data.controls.length-1)));}
break;case UI_RELOAD:window.location.reload();break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GAUGE:case UI_SEPARATOR:if(data.visible)addToHTML(data);break;case UI_BUTTON:if(data.visible){addToHTML(data);$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);},});}
break;case UI_SWITCHER:if(data.visible){addToHTML(data);switcher(data.id,data.value);}
break;case UI_CPAD:case UI_PAD:if(data.visible){addToHTML(data);$("#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:if(data.visible){addToHTML(data);rangeSlider(!sliderContinuous);}
break;case UI_TAB:if(data.visible){$("#tabsnav").append("<li><a onmouseup='tabclick("+data.id+")' href='#tab"+data.id+"'>"+data.value+"</a></li>");$("#tabscontent").append("<div id='tab"+data.id+"'></div>");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");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<select style='color:black;' "+elementStyle+" id='select"+
data.id+
"' onchange='selectchange("+
data.id+
")' />"+
"</div>");}
break;case UI_OPTION:if(data.parentControl){var parent=$("#select"+data.parentControl);parent.append("<option id='option"+
data.id+
"' value='"+
@ -195,78 +28,80 @@ data.selected+
">"+
data.label+
"</option>");}
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");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"<figure id='graph"+
data.id+
"'>"+
"<figcaption>"+
data.label+
"</figcaption>"+
"</figure>"+
"</div>");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");}
if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"WILL BE A GAUGE <input style='color:black;' id='gauge"+
data.id+
"' type='number' value='"+
data.value+
"' onchange='numberchange("+
data.id+
")' />"+
"</div>");}
break;case UI_ACCEL:if(hasAccel)break;var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
hasAccel=true;if(data.visible){parent.append("<div id='id"+
data.id+
"' "+panelStyle+" class='two columns card tcenter "+
colorClass(data.color)+
"'>"+
"<h5>"+
data.label+
"</h5><hr/>"+
"ACCEL // Not implemented fully!<div class='accelerometer' id='accel"+
data.id+
"' ><div class='ball"+
data.id+
"'></div><pre class='accelerometeroutput"+
data.id+
"'></pre>"+
"</div>");requestOrientationPermission();}
break;case UI_MIN:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("min",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("min",data.value);}}
break;case UI_MAX:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("max",data.value);}else if($('#text'+data.parentControl).length){$('#text'+data.parentControl).attr("maxlength",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("max",data.value);}}
break;case UI_STEP:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("step",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("step",data.value);}}
break;case UI_GRAPH:if(data.visible){addToHTML(data);graphData[data.id]=restoreGraphData(data.id);renderGraphSvg(graphData[data.id],"graph"+data.id);}
break;case ADD_GRAPH_POINT:var ts=new Date().getTime();graphData[data.id].push({x:ts,y:data.value});saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case CLEAR_GRAPH:graphData[data.id]=[];saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case UI_ACCEL:if(hasAccel)break;hasAccel=true;if(data.visible){addToHTML(data);requestOrientationPermission();}
break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);if(data.hasOwnProperty('elementStyle')){$("#l"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_SWITCHER:switcher(data.id,data.value=="0"?0:1);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_SLIDER:slider_move($("#id"+data.id),data.value,"100",false);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_SLIDER:$("#sl"+data.id).attr("value",data.value)
slider_move($("#sl"+data.id).parent().parent(),data.value,"100",false);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#num"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#text"+data.id).attr("style",data.elementStyle);}
if(data.hasOwnProperty('inputType')){$("#text"+data.id).attr("type",data.inputType);}
break;case UPDATE_SELECT:$("#select"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#select"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_BUTTON:case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#gauge"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_ACCEL:break;default:console.error("Unknown type or event");break;}
break;case UPDATE_BUTTON:$("#btn"+data.id).val(data.value);$("#btn"+data.id).text(data.value);if(data.hasOwnProperty('elementStyle')){$("#btn"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#gauge"+data.id).attr("style",data.elementStyle);}
break;case UPDATE_ACCEL:break;case UPDATE_TIME:var rv=new Date().toISOString();websock.send("time:"+rv+":"+data.id);break;default:console.error("Unknown type or event");break;}
if(data.type>=UI_TITEL&&data.type<UPDATE_OFFSET){processEnabled(data);}
if(data.type>=UPDATE_OFFSET&&data.type<UI_INITIAL_GUI){var element=$("#id"+data.id);if(data.hasOwnProperty('panelStyle')){$("#id"+data.id).attr("style",data.panelStyle);}
if(data.type==UPDATE_SLIDER){element.removeClass("slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin");element.addClass("slider-"+colorClass(data.color));}else{element.removeClass("turquoise emerald peterriver wetasphalt sunflower carrot alizarin");element.addClass(colorClass(data.color));}}
if(data.hasOwnProperty('visible')){if(data['visible'])
$("#id"+data.id).show();else
$("#id"+data.id).hide();}
if(data.type==UPDATE_SLIDER){element.removeClass("slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin");element.addClass("slider-"+colorClass(data.color));}else{element.removeClass("turquoise emerald peterriver wetasphalt sunflower carrot alizarin");element.addClass(colorClass(data.color));}
processEnabled(data);}
$(".range-slider__range").each(function(){$(this)[0].value=$(this).attr("value");$(this).next().html($(this).attr("value"));});};websock.onmessage=handleEvent;}
function sliderchange(number){var val=$("#sl"+number).val();websock.send("slvalue:"+val+":"+number);}
function sliderchange(number){var val=$("#sl"+number).val();websock.send("slvalue:"+val+":"+number);$(".range-slider__range").each(function(){$(this).attr("value",$(this)[0].value);});}
function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);}
function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);}
function tabclick(number){var val=$("#tab"+number).val();websock.send("tabvalue:"+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);}}
function padclick(type,number,isdown){if($("#id"+number+" nav").hasClass("disabled")){return;}
switch(type){case CENTER:if(isdown)websock.send("pcdown:"+number);else websock.send("pcup:"+number);break;case UP:if(isdown)websock.send("pfdown:"+number);else websock.send("pfup:"+number);break;case DOWN:if(isdown)websock.send("pbdown:"+number);else websock.send("pbup:"+number);break;case LEFT:if(isdown)websock.send("pldown:"+number);else websock.send("plup:"+number);break;case RIGHT:if(isdown)websock.send("prdown:"+number);else websock.send("prup:"+number);break;}}
function switcher(number,state){if(state==null){if(!$("#sl"+number).hasClass("checked")){websock.send("sactive:"+number);$("#sl"+number).addClass("checked");}else{websock.send("sinactive:"+number);$("#sl"+number).removeClass("checked");}}else if(state==1){$("#sl"+number).addClass("checked");$("#sl"+number).prop("checked",true);}else if(state==0){$("#sl"+number).removeClass("checked");$("#sl"+number).prop("checked",false);}}
var rangeSlider=function(isDiscrete){var range=$(".range-slider__range");var slidercb=function(){sliderchange($(this).attr("id").replace(/^\D+/g,""));};range.on({input:function(){$(this).next().html(this.value)}});range.each(function(){$(this).next().html(this.value);if($(this).attr("callbackSet")!="true"){if(!isDiscrete){$(this).on({input:slidercb});}else{$(this).on({change:slidercb});}
$(this).attr("callbackSet","true");}});};
$(this).attr("callbackSet","true");}});};var addToHTML=function(data){panelStyle=data.hasOwnProperty('panelStyle')?" style='"+data.panelStyle+"' ":"";panelwide=data.hasOwnProperty('wide')?"wide":"";if(!data.hasOwnProperty('parentControl')||$("#tab"+data.parentControl).length>0){var parent=data.hasOwnProperty('parentControl')?$("#tab"+data.parentControl):$("#row");var html="";switch(data.type){case UI_LABEL:case UI_BUTTON:case UI_SWITCHER:case UI_CPAD:case UI_PAD:case UI_SLIDER:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GRAPH:case UI_GAUGE:case UI_ACCEL:html="<div id='id"+data.id+"' "+panelStyle+" class='two columns "+panelwide+" card tcenter "+
colorClass(data.color)+"'><h5>"+data.label+"</h5><hr/>"+
elementHTML(data)+
"</div>";break;case UI_SEPARATOR:html="<div id='id"+data.id+"' "+panelStyle+" class='sectionbreak columns'>"+
"<h5>"+data.label+"</h5><hr/></div>";break;case UI_TIME:break;}
parent.append(html);}else{var parent=$("#id"+data.parentControl);parent.append(elementHTML(data));}}
var elementHTML=function(data){var id=data.id
var elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"' ":"";var inputType=data.hasOwnProperty('inputType')?" type='"+data.inputType+"' ":"";switch(data.type){case UI_LABEL:return"<span id='l"+id+"' "+elementStyle+
" class='label label-wrap'>"+data.value+"</span>";case UI_BUTTON:return"<button id='btn"+id+"' "+elementStyle+
" onmousedown='buttonclick("+id+", true)'"+
" onmouseup='buttonclick("+id+", false)'>"+
data.value+"</button>";case UI_SWITCHER:return"<label id='sl"+id+"' "+elementStyle+
" class='switch "+(data.value=="1"?"checked":"")+
(data.hasOwnProperty('vertical')?" vert-switcher ":"")+
"'>"+
"<div class='in'>"+
"<input type='checkbox' id='s"+id+"' onClick='switcher("+id+",null)' "+
(data.value=="1"?"checked":"")+"/></div></label>";case UI_CPAD:case UI_PAD:return"<nav class='control'><ul>"+
"<li><a onmousedown='padclick(UP, "+id+", true)' "+
"onmouseup='padclick(UP, "+id+", false)' id='pf"+id+"'>&#9650;</a></li>"+
"<li><a onmousedown='padclick(RIGHT, "+id+", true)' "+
"onmouseup='padclick(RIGHT, "+id+", false)' id='pr"+id+"'>&#9650;</a></li>"+
"<li><a onmousedown='padclick(LEFT, "+id+", true)' "+
"onmouseup='padclick(LEFT, "+id+", false)' id='pl"+id+"'>&#9650;</a></li>"+
"<li><a onmousedown='padclick(DOWN, "+id+", true)' "+
"onmouseup='padclick(DOWN, "+id+", false)' id='pb"+id+"'>&#9650;</a></li>"+
"</ul>"+
(data.type==UI_CPAD?"<a class='confirm' onmousedown='padclick(CENTER,"+id+", true)' "+
"onmouseup='padclick(CENTER, "+id+", false)' id='pc"+id+"'>OK</a>":"")+
"</nav>";case UI_SLIDER:return"<div class='range-slider "+
(data.hasOwnProperty('vertical')?" vert-slider ":"")+
"'>"+
"<input id='sl"+id+"' type='range' min='0' max='100' value='"+data.value+"' "+
elementStyle+" class='range-slider__range'><span class='range-slider__value'>"+
data.value+"</span></div>";case UI_NUMBER:return"<input style='color:black; "+data.elementStyle+"' id='num"+id+
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_TEXT_INPUT:return"<input "+inputType+"style='color:black; "+data.elementStyle+"' id='text"+id+
"' value='"+data.value+"' onchange='textchange("+id+")' />";case UI_SELECT:return"<select style='color:black; "+data.elementStyle+"' id='select"+id+
"' onchange='selectchange("+id+")' />";case UI_GRAPH:return"<figure id='graph"+id+"'><figcaption>"+data.label+"</figcaption></figure>";case UI_GAUGE:return"WILL BE A GAUGE <input style='color:black;' id='gauge"+id+
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_ACCEL:return"ACCEL // Not implemented fully!<div class='accelerometer' id='accel"+id+
"' ><div class='ball"+id+"'></div><pre class='accelerometeroutput"+id+"'></pre>";default:return"";}}
var processEnabled=function(data){switch(data.type){case UI_SWITCHER:case UPDATE_SWITCHER:if(data.enabled){$("#sl"+data.id).removeClass('disabled');$("#s"+data.id).prop("disabled",false);}else{$("#sl"+data.id).addClass('disabled');$("#s"+data.id).prop("disabled",true);}
break;case UI_SLIDER:case UPDATE_SLIDER:$("#sl"+data.id).prop("disabled",!data.enabled);break;case UI_NUMBER:case UPDATE_NUMBER:$("#num"+data.id).prop("disabled",!data.enabled);break;case UI_TEXT_INPUT:case UPDATE_TEXT_INPUT:$("#text"+data.id).prop("disabled",!data.enabled);break;case UI_SELECT:case UPDATE_SELECT:$("#select"+data.id).prop("disabled",!data.enabled);break;case UI_BUTTON:case UPDATE_BUTTON:$("#btn"+data.id).prop("disabled",!data.enabled);break;case UI_PAD:case UI_CPAD:case UPDATE_PAD:case UPDATE_CPAD:if(data.enabled){$("#id"+data.id+" nav").removeClass('disabled');}else{$("#id"+data.id+" nav").addClass('disabled');}
break;}}

View File

@ -55,13 +55,13 @@ function lineGraph(parent, xAccessor, yAccessor) {
// generate labels
for (var i = xMin; i <= xMax; i++) {
if ((i - xMin) % pixelsPerTick === 0 && i !== xMin) {
if ((i - xMin) % (pixelsPerTick*3) === 0 && i !== xMin) {
var text = document.createElementNS(
"http://www.w3.org/2000/svg",
"text"
);
// primitive formatting
text.innerHTML = Math.floor(transform(i));
text.innerHTML = new Date(Math.floor(transform(i))).toLocaleTimeString();
text.setAttribute("x", i);
text.setAttribute("y", yMin);
// offset the text by 1 em

View File

@ -1,5 +1,5 @@
function lineGraph(parent,xAccessor,yAccessor){const width=620;const height=420;const gutter=40;const pixelsPerTick=30;function numericTransformer(dataMin,dataMax,pxMin,pxMax){var dataDiff=dataMax-dataMin,pxDiff=pxMax-pxMin,dataRatio=pxDiff/dataDiff,coordRatio=dataDiff/pxDiff;return{toCoord:function(data){return(data-dataMin)*dataRatio+pxMin;},toData:function(coord){return(coord-pxMin)*coordRatio+dataMin;}};}
function axisRenderer(orientation,transform){var axisGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var axisPath=document.createElementNS("http://www.w3.org/2000/svg","path");axisGroup.setAttribute("class",orientation+"-axis");var xMin=gutter;var xMax=width-gutter;var yMin=height-gutter;var yMax=gutter;if(orientation==="x"){axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMax+" "+yMin);for(var i=xMin;i<=xMax;i++){if((i-xMin)%pixelsPerTick===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",i);text.setAttribute("y",yMin);text.setAttribute("dy","1em");axisGroup.appendChild(text);}}}else{axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMin+" "+yMax);for(var i=yMax;i<=yMin;i++){if((i-yMin)%pixelsPerTick===0&&i!==yMin){var tickGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var gridLine=document.createElementNS("http://www.w3.org/2000/svg","path");text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",xMin);text.setAttribute("y",i);text.setAttribute("dx","-.5em");text.setAttribute("dy",".3em");gridLine.setAttribute("d","M "+xMin+" "+i+" L "+xMax+" "+i);tickGroup.appendChild(gridLine);tickGroup.appendChild(text);axisGroup.appendChild(tickGroup);}}}
function axisRenderer(orientation,transform){var axisGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var axisPath=document.createElementNS("http://www.w3.org/2000/svg","path");axisGroup.setAttribute("class",orientation+"-axis");var xMin=gutter;var xMax=width-gutter;var yMin=height-gutter;var yMax=gutter;if(orientation==="x"){axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMax+" "+yMin);for(var i=xMin;i<=xMax;i++){if((i-xMin)%(pixelsPerTick*3)===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=new Date(Math.floor(transform(i))).toLocaleTimeString();text.setAttribute("x",i);text.setAttribute("y",yMin);text.setAttribute("dy","1em");axisGroup.appendChild(text);}}}else{axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMin+" "+yMax);for(var i=yMax;i<=yMin;i++){if((i-yMin)%pixelsPerTick===0&&i!==yMin){var tickGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var gridLine=document.createElementNS("http://www.w3.org/2000/svg","path");text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",xMin);text.setAttribute("y",i);text.setAttribute("dx","-.5em");text.setAttribute("dy",".3em");gridLine.setAttribute("d","M "+xMin+" "+i+" L "+xMax+" "+i);tickGroup.appendChild(gridLine);tickGroup.appendChild(text);axisGroup.appendChild(tickGroup);}}}
axisGroup.appendChild(axisPath);parent.appendChild(axisGroup);}
function lineRenderer(xAccessor,yAccessor,xTransform,yTransform){var line=document.createElementNS("http://www.w3.org/2000/svg","path");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+

BIN
docs/ui_colours.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/ui_groupedbuttons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/ui_groupedbuttons2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
docs/ui_groupedbuttons3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
docs/ui_inlinestyles2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/ui_inputtypes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
docs/ui_separators.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
docs/ui_widecontrols.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

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

View File

@ -0,0 +1 @@
// placeholder

View File

@ -8,7 +8,17 @@ DNSServer dnsServer;
#if defined(ESP32)
#include <WiFi.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#include <umm_malloc/umm_heap_select.h>
#ifndef MMU_IRAM_HEAP
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
#warning then check http://<ip>/heap
#endif // MMU_IRAM_HEAP
#ifndef DEBUG_ESP_OOM
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
#endif
#endif
const char* ssid = "ESPUI";
@ -55,8 +65,9 @@ void buttonCallback(Control* sender, int type)
}
}
void buttonExample(Control* sender, int type)
void buttonExample(Control* sender, int type, void* param)
{
Serial.println(String("param: ") + String(long(param)));
switch (type)
{
case B_DOWN:
@ -210,7 +221,8 @@ void setup(void)
}
#else
uint32_t chipid = ESP.getChipId();
#endif char ap_ssid[25];
#endif
char ap_ssid[25];
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
WiFi.softAP(ap_ssid);
@ -233,6 +245,10 @@ void setup(void)
Serial.print("IP address: ");
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
#ifdef ESP8266
{ HeapSelectIram doAllocationsInIRAM;
#endif
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
uint16_t select1 = ESPUI.addControl(
@ -249,7 +265,7 @@ void setup(void)
button1 = ESPUI.addControl(
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
ESPUI.addControl(
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample);
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample, (void*)19);
ESPUI.addControl(
ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
@ -279,6 +295,10 @@ void setup(void)
*/
ESPUI.begin("ESPUI Control");
#ifdef ESP8266
} // HeapSelectIram
#endif
}
void loop(void)

View File

@ -8,7 +8,17 @@ DNSServer dnsServer;
#if defined(ESP32)
#include <WiFi.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#include <umm_malloc/umm_heap_select.h>
#ifndef MMU_IRAM_HEAP
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
#warning then check http://<ip>/heap
#endif // MMU_IRAM_HEAP
#ifndef DEBUG_ESP_OOM
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
#endif
#endif
const char* ssid = "ESPUI";
@ -60,8 +70,9 @@ void buttonCallback(Control* sender, int type)
}
}
void buttonExample(Control* sender, int type)
void buttonExample(Control* sender, int type, void* param)
{
Serial.println(String("param: ") + String(long(param)));
switch (type)
{
case B_DOWN:
@ -224,10 +235,14 @@ void setup(void)
Serial.print("IP address: ");
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
#ifdef ESP8266
{ HeapSelectIram doAllocationsInIRAM;
#endif
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press");
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press", (void*)19);
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
@ -255,8 +270,11 @@ void setup(void)
* since it is transmitted in cleartext. Just add a string as username and
* password, for example begin("ESPUI Control", "username", "password")
*/
ESPUI.begin("ESPUI Control");
#ifdef ESP8266
} // HeapSelectIram
#endif
}
void loop(void)

View File

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

View File

@ -8,7 +8,17 @@ DNSServer dnsServer;
#if defined(ESP32)
#include <WiFi.h>
#else
// esp8266
#include <ESP8266WiFi.h>
#include <umm_malloc/umm_heap_select.h>
#ifndef MMU_IRAM_HEAP
#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary)
#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap)
#warning then check http://<ip>/heap
#endif // MMU_IRAM_HEAP
#ifndef DEBUG_ESP_OOM
#error on ESP8266 and ESPUI, you must define OOM debug option when developping
#endif
#endif
const char* ssid = "ESPUI";
@ -54,8 +64,9 @@ void buttonCallback(Control* sender, int type)
}
}
void buttonExample(Control* sender, int type)
void buttonExample(Control* sender, int type, void* param)
{
Serial.println(String("param: ") + String(long(param)));
switch (type)
{
case B_DOWN:
@ -232,6 +243,10 @@ void setup(void)
Serial.print("IP address: ");
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
#ifdef ESP8266
{ HeapSelectIram doAllocationsInIRAM;
#endif
uint16_t tab1 = ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1");
uint16_t tab2 = ESPUI.addControl(ControlType::Tab, "Settings 2", "Settings 2");
uint16_t tab3 = ESPUI.addControl(ControlType::Tab, "Settings 3", "Settings 3");
@ -251,7 +266,7 @@ void setup(void)
ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1);
button1 = ESPUI.addControl(
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback);
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample);
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample, (void*)19);
ESPUI.addControl(ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, tab2, &padExample);
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample);
switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample);
@ -278,6 +293,10 @@ void setup(void)
*/
ESPUI.begin("ESPUI Control");
#ifdef ESP8266
} // HeapSelectIram
#endif
}
void loop(void)

View File

@ -25,6 +25,7 @@ beginLITTLEFS KEYWORD2
print KEYWORD2
updateSwitcher KEYWORD2
updateSlider KEYWORD2
captivePortal LITERAL1
#######################################
# Instances (KEYWORD2)

View File

@ -1,6 +1,6 @@
{
"name": "ESPUI",
"keywords": "espressif web interface iot control simple easy ui userinterface",
"keywords": "espressif web interface iot easy ui",
"description": "ESP32 and ESP8266 Web Interface Library",
"repository": {
"type": "git",
@ -31,7 +31,7 @@
"frameworks": "arduino"
}
],
"version": "2.1.0",
"version": "2.2.2",
"frameworks": "arduino",
"platforms": "*"
}

View File

@ -1,5 +1,5 @@
name=ESPUI
version=2.1.0
version=2.2.2
author=Lukas Bachschwell
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
sentence=ESP32 and ESP8266 Web Interface Library

View File

@ -13,25 +13,31 @@ src_dir = ./src
data_dir = ../../data
[env]
lib_extra_dirs = ../../
framework = arduino
board_build.filesystem = littlefs
lib_extra_dirs = ../../
lib_deps =
bblanchon/ArduinoJson @ ^6.18.5
https://github.com/esphome/ESPAsyncWebServer @ 3.0.0 ; Updated lib, seems to have recent patches.
lib_ignore =
ESP Async WebServer ; force the use of the esphome version
AsyncTCP ; force the use of the esphome version
LittleFS_esp32 ; force the use of the ESP32 built into the core version
; Additional scripts: Usage: see https://github.com/s00500/ESPUI/issues/144#issuecomment-1005135077
;extra_scripts =
; LittleFSBuilder.py
[env:esp8266]
platform = espressif8266
framework = arduino
board = nodemcuv2
lib_deps =
bblanchon/ArduinoJson @ ^6.18.5
me-no-dev/ESP Async WebServer @ ^1.2.3
[env:esp32]
platform = espressif32
framework = arduino
board = esp32dev
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout
lib_deps =
lorol/LittleFS_esp32@^1.0.6
bblanchon/ArduinoJson @ ^6.18.5
me-no-dev/ESP Async WebServer @ ^1.2.3
${env.lib_deps}
me-no-dev/AsyncTCP@1.1.1

View File

@ -60,8 +60,9 @@ void buttonCallback(Control* sender, int type)
}
}
void buttonExample(Control* sender, int type)
void buttonExample(Control* sender, int type, void* param)
{
Serial.println(String("param: ") + String(long(param)));
switch (type)
{
case B_DOWN:
@ -228,7 +229,7 @@ void setup(void)
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press");
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press", (void*)19);
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,29 @@
#ifndef ESPUI_h
#define ESPUI_h
#pragma once
// comment out to turn off debug output
#define DEBUG_ESPUI true
#define WS_AUTHENTICATION false
#include "Arduino.h"
#include "ArduinoJson.h"
#include "stdlib_noniso.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <stdlib_noniso.h>
#ifdef ESP32
#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR >= 4) || ESP_IDF_VERSION_MAJOR > 4
#include <LittleFS.h>
#else
#include <LITTLEFS.h>
#endif
#else
#include <LittleFS.h>
#endif
#include <map>
#include <ESPAsyncWebServer.h>
#include "ESPUIcontrol.h"
#include "ESPUIclient.h"
#if defined(ESP32)
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LITTLEFS.h>
#include "WiFi.h"
#else
@ -21,9 +32,7 @@
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Hash.h>
#include <LittleFS.h>
#define FILE_WRITE "w"
@ -31,147 +40,18 @@
// Message Types (and control types)
enum ControlType : uint8_t
enum MessageTypes : uint8_t
{
// fixed controls
Title = 0,
// updatable controls
Pad,
PadWithCenter,
Button,
Label,
Switcher,
Slider,
Number,
Text,
Graph,
GraphPoint,
Tab,
Select,
Option,
Min,
Max,
Step,
Gauge,
Accel,
UpdateOffset = 100,
UpdatePad = 101,
UpdatePadWithCenter,
ButtonButton,
UpdateLabel,
UpdateSwitcher,
UpdateSlider,
UpdateNumber,
UpdateText,
ClearGraph,
UpdateTab,
UpdateSelection,
UpdateOption,
UpdateMin,
UpdateMax,
UpdateStep,
UpdateGauge,
UpdateAccel,
InitialGui = 200,
Reload = 201,
ExtendGUI = 210
ExtendGUI = 210,
UpdateGui = 220,
ExtendedUpdateGui = 230,
};
#define UI_INITIAL_GUI ControlType::InitialGui
#define UI_RELOAD ControlType::Reload
#define UI_EXTEND_GUI ControlType::ExtendGUI
#define UI_TITLE ControlType::Title
#define UI_LABEL ControlType::Label
#define UI_BUTTON ControlType::Button
#define UI_SWITCHER ControlType::Switcher
#define UI_PAD ControlType::Pad
#define UI_CPAD ControlType::Cpad
#define UI_SLIDER ControlType::Slider
#define UI_NUMBER ControlType::Number
#define UI_TEXT_INPUT ControlType::Text
#define UI_GRAPH ControlType::Graph
#define UI_ADD_GRAPH_POINT ControlType::GraphPoint
#define UPDATE_LABEL ControlType::UpdateLabel
#define UPDATE_SWITCHER ControlType::UpdateSwitcher
#define UPDATE_SLIDER ControlType::UpdateSlider
#define UPDATE_NUMBER ControlType::UpdateNumber
#define UPDATE_TEXT_INPUT ControlType::UpdateText
#define CLEAR_GRAPH ControlType::ClearGraph
// Colors
enum ControlColor : uint8_t
{
Turquoise,
Emerald,
Peterriver,
Wetasphalt,
Sunflower,
Carrot,
Alizarin,
Dark,
None = 0xFF
};
#define COLOR_TURQUOISE ControlColor::Turquoise
#define COLOR_EMERALD ControlColor::Emerald
#define COLOR_PETERRIVER ControlColor::Peterriver
#define COLOR_WETASPHALT ControlColor::Wetasphalt
#define COLOR_SUNFLOWER ControlColor::Sunflower
#define COLOR_CARROT ControlColor::Carrot
#define COLOR_ALIZARIN ControlColor::Alizarin
#define COLOR_DARK ControlColor::Dark
#define COLOR_NONE ControlColor::None
class Control
{
public:
ControlType type;
uint16_t id; // just mirroring the id here for practical reasons
const char* label;
void (*callback)(Control*, int);
String value;
ControlColor color;
bool visible;
uint16_t parentControl;
String panelStyle;
String elementStyle;
Control* next;
static constexpr uint16_t noParent = 0xffff;
Control(ControlType type, const char* label, void (*callback)(Control*, int), const String& value,
ControlColor color, bool visible = true, uint16_t parentControl = Control::noParent)
: type(type),
label(label),
callback(callback),
value(value),
color(color),
visible(visible),
parentControl(parentControl),
next(nullptr)
{
id = idCounter++;
}
Control(const Control& control)
: type(control.type),
id(control.id),
label(control.label),
callback(control.callback),
value(control.value),
color(control.color),
visible(control.visible),
parentControl(control.parentControl),
next(control.next)
{ }
private:
static uint16_t idCounter;
};
#define UI_INITIAL_GUI MessageTypes::InitialGui
#define UI_EXTEND_GUI MessageTypes::ExtendGUI
#define UI_RELOAD MessageTypes::Reload
// Values
#define B_DOWN -1
@ -195,6 +75,7 @@ private:
#define N_VALUE 9
#define T_VALUE 10
#define S_VALUE 11
#define TM_VALUE 12
enum Verbosity : uint8_t
{
@ -206,16 +87,27 @@ enum Verbosity : uint8_t
class ESPUIClass
{
public:
#ifdef ESP32
ESPUIClass()
{
verbosity = Verbosity::Quiet;
jsonUpdateDocumentSize = 2000;
jsonInitialDocumentSize = 8000;
sliderContinuous = false;
ControlsSemaphore = xSemaphoreCreateMutex();
xSemaphoreGive(ControlsSemaphore);
}
unsigned int jsonUpdateDocumentSize;
unsigned int jsonInitialDocumentSize;
bool sliderContinuous;
SemaphoreHandle_t ControlsSemaphore = NULL;
#endif // def ESP32
unsigned int jsonUpdateDocumentSize = 2000;
#ifdef ESP8266
unsigned int jsonInitialDocumentSize = 2000;
unsigned int jsonChunkNumberMax = 5;
#else
unsigned int jsonInitialDocumentSize = 8000;
unsigned int jsonChunkNumberMax = 0;
#endif
bool sliderContinuous = false;
void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
bool captivePortal = true;
void setVerbosity(Verbosity verbosity);
void begin(const char* _title, const char* username = nullptr, const char* password = nullptr,
@ -225,31 +117,41 @@ public:
void beginLITTLEFS(const char* _title, const char* username = nullptr, const char* password = nullptr,
uint16_t port = 80); // Setup server and page in LITTLEFS mode
void prepareFileSystem(); // Initially preps the filesystem and loads a lot of
void prepareFileSystem(bool format = true); // Initially preps the filesystem and loads a lot of
// stuff into LITTLEFS
void list(); // Lists LITTLEFS directory
uint16_t addControl(ControlType type, const char* label, const String& value = String(""),
ControlColor color = ControlColor::Turquoise, uint16_t parentControl = Control::noParent,
void (*callback)(Control*, int) = nullptr);
bool removeControl(uint16_t id, bool force_reload_ui = false);
uint16_t addControl(ControlType type, const char* label);
uint16_t addControl(ControlType type, const char* label, const String& value);
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color);
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl);
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, void (*callback)(Control*, int));
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, void (*callback)(Control*, int, void *), void* UserData);
bool removeControl(uint16_t id, bool force_rebuild_ui = false);
// create Elements
uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color,
const String& value = ""); // Create Event Button
uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color,
bool startState = false); // Create Toggle Button
uint16_t pad(const char* label, void (*callback)(Control*, int),
ControlColor color); // Create Pad Control
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int),
ControlColor color); // Create Pad Control with Centerbutton
// Create Event Button
uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value = "");
uint16_t button(const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData);
uint16_t slider(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0,
int max = 100); // Create Slider Control
uint16_t number(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0,
int max = 100); // Create a Number Input Control
uint16_t text(const char* label, void (*callback)(Control*, int), ControlColor color,
const String& value = ""); // Create a Text Input Control
uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color, bool startState = false); // Create Toggle Button
uint16_t switcher(const char* label, void (*callback)(Control*, int, void*), ControlColor color, bool startState, void* UserData); // Create Toggle Button
uint16_t pad(const char* label, void (*callback)(Control*, int), ControlColor color); // Create Pad Control
uint16_t pad(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); // Create Pad Control
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int), ControlColor color); // Create Pad Control with Centerbutton
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); // Create Pad Control with Centerbutton
uint16_t slider(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0, int max = 100); // Create Slider Control
uint16_t slider(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, int min, int max, void* UserData); // Create Slider Control
uint16_t number(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0, int max = 100); // Create a Number Input Control
uint16_t number(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, int min, int max, void* UserData); // Create a Number Input Control
uint16_t text(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value = ""); // Create a Text Input Control
uint16_t text(const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData); // Create a Text Input Control
// Output only
uint16_t label(const char* label, ControlColor color,
@ -257,54 +159,82 @@ public:
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
uint16_t separator(const char* label); //Create separator
// Input only
uint16_t accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color);
uint16_t accelerometer(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData);
// Update Elements
Control* getControl(uint16_t id);
Control* getControlNoLock(uint16_t id);
// Update Elements
void updateControlValue(uint16_t id, const String& value, int clientId = -1);
void updateControlValue(Control* control, const String& value, int clientId = -1);
void updateControlLabel(uint16_t control, const char * value, int clientId = -1);
void updateControlLabel(Control* control, const char * value, int clientId = -1);
void updateControl(uint16_t id, int clientId = -1);
void updateControl(Control* control, int clientId = -1);
void print(uint16_t id, const String& value);
void updateLabel(uint16_t id, const String& value);
void updateButton(uint16_t id, const 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, const String& nValue, int clientId = -1);
void updateSelect(uint16_t id, const String& nValue, int clientId = -1);
void updateGauge(uint16_t id, int number, int clientId);
void updateTime(uint16_t id, int clientId = -1);
void clearGraph(uint16_t id, int clientId = -1);
void addGraphPoint(uint16_t id, int nValue, int clientId = -1);
void setPanelStyle(uint16_t id, String style, int clientId = -1);
void setElementStyle(uint16_t id, String style, int clientId = -1);
void setInputType(uint16_t id, String type, int clientId = -1);
void setPanelWide(uint16_t id, bool wide);
void setVertical(uint16_t id, bool vert = true);
void setEnabled(uint16_t id, bool enabled = true, int clientId = -1);
void updateVisibility(uint16_t id, bool visibility, int clientId = -1);
// Variables
const char* ui_title = "ESPUI"; // Store UI Title and Header Name
Control* controls = nullptr;
void jsonReload();
void jsonDom(AsyncWebSocketClient* client = nullptr);
Verbosity verbosity;
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false);
Verbosity verbosity = Verbosity::Quiet;
AsyncWebServer* server;
protected:
friend class ESPUIclient;
friend class ESPUIcontrol;
void RemoveToBeDeletedControls();
AsyncWebSocket* ws;
private:
const char* basicAuthUsername = nullptr;
const char* basicAuthPassword = nullptr;
bool basicAuth = true;
uint16_t controlCount = 0;
#define ClientUpdateType_t ESPUIclient::ClientUpdateType_t
void NotifyClients(ClientUpdateType_t newState);
void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState);
void ClearControlUpdateFlags();
bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document, uint16_t clientId);
std::map<uint32_t, ESPUIclient*> MapOfClients;
Control* prepareJSONChunk(AsyncWebSocketClient* client, Control* control, JsonArray* items);
};
extern ESPUIClass ESPUI;
#endif

523
src/ESPUIclient.cpp Normal file
View File

@ -0,0 +1,523 @@
#include "ESPUI.h"
#include "ESPUIclient.h"
#include "ESPUIcontrol.h"
// JSONSlave:
// helper to process exact JSON serialization size
// it takes ~2ms on esp8266 and avoid large String reallocation which is really worth the cost
class JSONSlave: public Print
{
public:
size_t write (uint8_t c) override { counter++; return 1; }
size_t write (const uint8_t* buf, size_t count) override { counter += count; return count; }
size_t get_counter () { return counter; }
static size_t serializedSize (JsonDocument& doc)
{
JSONSlave counter;
serializeJson(doc, counter);
return counter.get_counter();
}
static size_t serialize (JsonDocument& doc, String& str)
{
size_t s = serializedSize(doc) + 10; // 10 is paranoid
str.reserve(s);
serializeJson(doc, str);
return s;
}
static String toString (JsonDocument& doc)
{
String str;
serialize(doc, str);
return str;
}
protected:
size_t counter = 0;
};
ESPUIclient::ESPUIclient(AsyncWebSocketClient * _client):
client(_client)
{
fsm_EspuiClient_state_Idle_imp.SetParent(this);
fsm_EspuiClient_state_SendingUpdate_imp.SetParent(this);
fsm_EspuiClient_state_Rebuilding_imp.SetParent(this);
fsm_EspuiClient_state_Reloading_imp.SetParent(this);
fsm_EspuiClient_state_Idle_imp.Init();
}
ESPUIclient::ESPUIclient(const ESPUIclient& source):
client(source.client)
{
fsm_EspuiClient_state_Idle_imp.SetParent(this);
fsm_EspuiClient_state_SendingUpdate_imp.SetParent(this);
fsm_EspuiClient_state_Rebuilding_imp.SetParent(this);
fsm_EspuiClient_state_Reloading_imp.SetParent(this);
fsm_EspuiClient_state_Idle_imp.Init();
}
ESPUIclient::~ESPUIclient()
{
}
bool ESPUIclient::CanSend()
{
bool Response = false;
if (nullptr != client)
{
Response = client->canSend();
}
return Response;
}
void ESPUIclient::FillInHeader(DynamicJsonDocument& document)
{
document[F("type")] = UI_EXTEND_GUI;
document[F("sliderContinuous")] = ESPUI.sliderContinuous;
document[F("startindex")] = 0;
document[F("totalcontrols")] = ESPUI.controlCount;
JsonArray items = document.createNestedArray(F("controls"));
JsonObject titleItem = items.createNestedObject();
titleItem[F("type")] = (int)UI_TITLE;
titleItem[F("label")] = ESPUI.ui_title;
}
bool ESPUIclient::IsSyncronized()
{
return ((ClientUpdateType_t::Synchronized == ClientUpdateType) &&
(&fsm_EspuiClient_state_Idle_imp == pCurrentFsmState));
}
bool ESPUIclient::SendClientNotification(ClientUpdateType_t value)
{
bool Response = false;
do // once
{
if(!CanSend())
{
// Serial.println(F("ESPUIclient::NotifyClient"));
break;
}
DynamicJsonDocument document(ESPUI.jsonUpdateDocumentSize);
FillInHeader(document);
if(ClientUpdateType_t::ReloadNeeded == value)
{
// Serial.println(F("ESPUIclient::SendClientNotification:set type to reload"));
document["type"] = int(UI_RELOAD);
}
// dont send any controls
Response = SendJsonDocToWebSocket(document);
// Serial.println(String("ESPUIclient::SendClientNotification:NotificationSent:Response: ") + String(Response));
} while (false);
return Response;
}
void ESPUIclient::NotifyClient(ClientUpdateType_t newState)
{
SetState(newState);
pCurrentFsmState->NotifyClient();
#ifdef OldWay
do // once
{
// Serial.println(String("ESPUIclient::NotifyClient: State: ") + String(int(newState)));
SetState(newState);
if (HasBeenNotified)
{
// do not need to do anything
break;
}
if(TransferIsInprogress)
{
// record that a notification was needed while we were transfering data to the client
DelayedNotification = true;
break;
}
DelayedNotification = false;
if (SendJsonDocToWebSocket(document))
{
HasBeenNotified = true;
}
} while (false);
return HasBeenNotified;
#endif // def OldWay
}
// Handle Websockets Communication
void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len)
{
// Serial.println(String("ESPUIclient::OnWsEvent: type: ") + String(type));
switch (type)
{
case WS_EVT_PONG:
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity)
{
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_PONG"));
}
#endif
break;
}
case WS_EVT_ERROR:
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity)
{
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_ERROR"));
}
#endif
break;
}
case WS_EVT_CONNECT:
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity)
{
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_CONNECT"));
Serial.println(client->id());
}
#endif
// Serial.println("ESPUIclient:onWsEvent:WS_EVT_CONNECT: Call NotifyClient: RebuildNeeded");
NotifyClient(ClientUpdateType_t::RebuildNeeded);
break;
}
case WS_EVT_DATA:
{
// Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_DATA"));
String msg = "";
msg.reserve(len + 1);
for (size_t i = 0; i < len; i++)
{
msg += (char)data[i];
}
String cmd = msg.substring(0, msg.indexOf(":"));
String value = msg.substring(cmd.length() + 1, msg.lastIndexOf(':'));
uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
{
Serial.println(String(F(" WS msg: ")) + msg);
Serial.println(String(F(" WS cmd: ")) + cmd);
Serial.println(String(F(" WS id: ")) + String(id));
Serial.println(String(F("WS value: ")) + String(value));
}
#endif
if (cmd.equals(F("uiok")))
{
// Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck"));
pCurrentFsmState->ProcessAck(id);
break;
}
if (cmd.equals(F("uiuok")))
{
// Serial.println(F("WS_EVT_DATA: uiuok. Unlock new async notifications"));
break;
}
Control* control = ESPUI.getControl(id);
if (nullptr == control)
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity)
{
Serial.println(String(F("No control found for ID ")) + String(id));
}
#endif
break;
}
control->onWsEvent(cmd, value);
break;
}
default:
{
// Serial.println(F("ESPUIclient::OnWsEvent:default"));
break;
}
} // end switch
}
/*
Prepare a chunk of elements as a single JSON string. If the allowed number of elements is greater than the total
number this will represent the entire UI. More likely, it will represent a small section of the UI to be sent. The
client will acknowledge receipt by requesting the next chunk.
*/
uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
DynamicJsonDocument & rootDoc,
bool InUpdateMode)
{
#ifdef ESP32
xSemaphoreTake(ESPUI.ControlsSemaphore, portMAX_DELAY);
#endif // def ESP32
// Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode));
int elementcount = 0;
do // once
{
// Follow the list until control points to the startindex'th node
Control* control = ESPUI.controls;
uint32_t currentIndex = 0;
JsonArray items = rootDoc[F("controls")];
while ((startindex > currentIndex) && (nullptr != control))
{
// only count active controls
if (!control->ToBeDeleted())
{
if(InUpdateMode)
{
// In update mode we only count the controls that have been updated.
if(control->IsUpdated())
{
++currentIndex;
}
}
else
{
// not in update mode. Count all active controls
++currentIndex;
}
}
control = control->next;
}
// any controls left to be processed?
if(nullptr == control)
{
// Serial.println("prepareJSONChunk: No controls to process");
break;
}
// keep track of the number of elements we have serialised into this
// message. Overflow is detected and handled later in this loop
// and needs an index to the last item added.
while (nullptr != control)
{
// skip deleted controls or controls that have not been updated
if (control->ToBeDeleted())
{
// Serial.println(String("prepareJSONChunk: Ignoring Deleted control: ") + String(control->id));
control = control->next;
continue;
}
if(InUpdateMode)
{
if(control->IsUpdated())
{
// dont skip this control
}
else
{
// control has not been updated. Skip it
control = control->next;
continue;
}
}
JsonObject item = items.createNestedObject();
elementcount++;
control->MarshalControl(item, InUpdateMode);
if (rootDoc.overflowed() || (ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0))
{
// String("prepareJSONChunk: too much data in the message. Remove the last entry");
if (1 == elementcount)
{
Serial.println(String(F("ERROR: prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser."));
rootDoc.clear();
item = items.createNestedObject();
control->MarshalErrorMessage(item);
elementcount = 0;
}
else
{
// Serial.println(String("prepareJSONChunk: Defering control: ") + String(control->id));
// Serial.println(String("prepareJSONChunk: elementcount: ") + String(elementcount));
items.remove(elementcount);
--elementcount;
}
// exit the loop
control = nullptr;
}
else
{
control = control->next;
}
} // end while (control != nullptr)
} while (false);
#ifdef ESP32
xSemaphoreGive(ESPUI.ControlsSemaphore);
#endif // def ESP32
// Serial.println(String("prepareJSONChunk: elementcount: ") + String(elementcount));
return elementcount;
}
/*
Convert & Transfer Arduino elements to JSON elements. This function sends a chunk of
JSON describing the controls of the UI, starting from the control at index startidx.
If startidx is 0 then a UI_INITIAL_GUI message will be sent, else a UI_EXTEND_GUI.
Both message types contain a list of serialised UI elements. Only a portion of the UI
will be sent in order to avoid websocket buffer overflows. The client will acknowledge
receipt of a partial message by requesting the next chunk of UI.
The protocol is:
SERVER: SendControlsToClient(0):
"UI_INITIAL_GUI: n serialised UI elements"
CLIENT: controls.js:handleEvent()
"uiok:n"
SERVER: SendControlsToClient(n):
"UI_EXTEND_GUI: n serialised UI elements"
CLIENT: controls.js:handleEvent()
"uiok:2*n"
etc.
Returns true if all controls have been sent (aka: Done)
*/
bool ESPUIclient::SendControlsToClient(uint16_t startidx,
ClientUpdateType_t TransferMode)
{
bool Response = false;
// Serial.println(String("ESPUIclient:SendControlsToClient:startidx: ") + String(startidx));
do // once
{
if(!CanSend())
{
// Serial.println("ESPUIclient:SendControlsToClient: Cannot Send to clients.");
break;
}
if (startidx >= ESPUI.controlCount)
{
// Serial.println("ESPUIclient:SendControlsToClient: No more controls to send.");
Response = true;
break;
}
DynamicJsonDocument document(ESPUI.jsonInitialDocumentSize);
FillInHeader(document);
document[F("startindex")] = startidx;
document[F("totalcontrols")] = 65534; // ESPUI.controlCount;
if(0 == startidx)
{
// Serial.println("ESPUIclient:SendControlsToClient: Tell client we are starting a transfer of controls.");
document["type"] = (ClientUpdateType_t::RebuildNeeded == TransferMode) ? UI_INITIAL_GUI : UI_EXTEND_GUI;
}
// Serial.println(String("ESPUIclient:SendControlsToClient:type: ") + String((uint32_t)document["type"]));
// Serial.println("ESPUIclient:SendControlsToClient: Build Controls.");
if(prepareJSONChunk(startidx, document, ClientUpdateType_t::UpdateNeeded == TransferMode))
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
{
Serial.println(F("ESPUIclient:SendControlsToClient: Sending elements --------->"));
serializeJson(document, Serial);
Serial.println();
}
#endif
// Serial.println("ESPUIclient:SendControlsToClient: Send message.");
if(true == SendJsonDocToWebSocket(document))
{
// Serial.println("ESPUIclient:SendControlsToClient: Sent.");
}
else
{
// Serial.println("ESPUIclient:SendControlsToClient: Send failed.");
}
}
else
{
// Serial.println("ESPUIclient:SendControlsToClient: No elements to send.");
Response = true;
}
} while(false);
// Serial.println(String("ESPUIclient:SendControlsToClient:Response: ") + String(Response));
return Response;
}
bool ESPUIclient::SendJsonDocToWebSocket(DynamicJsonDocument& document)
{
bool Response = true;
do // once
{
if (!CanSend())
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
{
Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: Cannot Send to client. Not sending websocket message"));
}
#endif
// Serial.println("ESPUIclient::SendJsonDocToWebSocket: Cannot Send to client. Not sending websocket message");
Response = false;
break;
}
String json = JSONSlave::toString(document);
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
{
Serial.println(String(F("ESPUIclient::SendJsonDocToWebSocket: json: '")) + json + "'");
}
#endif
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
{
Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: client.text"));
}
#endif
// Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: client.text"));
client->text(json);
} while (false);
return Response;
}
void ESPUIclient::SetState(ClientUpdateType_t value)
{
// only a higher priority state request can replace the current state request
if(uint32_t(ClientUpdateType) < uint32_t(value))
{
ClientUpdateType = value;
}
}

62
src/ESPUIclient.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include "ESPUIclientFsm.h"
#include "ESPUIcontrol.h"
class ESPUIclient
{
public:
enum ClientUpdateType_t
{ // this is an orderd list. highest number is highest priority
Synchronized = 0,
UpdateNeeded = 1,
RebuildNeeded = 2,
ReloadNeeded = 3,
};
protected:
// bool HasBeenNotified = false; // Set when a notification has been sent and we are waiting for a reply
// bool DelayedNotification = false; // set if a delayed notification is needed
ClientUpdateType_t ClientUpdateType = ClientUpdateType_t::RebuildNeeded;
AsyncWebSocketClient * client = nullptr;
friend class fsm_EspuiClient_state_Idle;
friend class fsm_EspuiClient_state_SendingUpdate;
friend class fsm_EspuiClient_state_Rebuilding;
friend class fsm_EspuiClient_state_WaitForAck;
friend class fsm_EspuiClient_state_Reloading;
friend class fsm_EspuiClient_state;
fsm_EspuiClient_state_Idle fsm_EspuiClient_state_Idle_imp;
fsm_EspuiClient_state_SendingUpdate fsm_EspuiClient_state_SendingUpdate_imp;
fsm_EspuiClient_state_Rebuilding fsm_EspuiClient_state_Rebuilding_imp;
fsm_EspuiClient_state_Reloading fsm_EspuiClient_state_Reloading_imp;
fsm_EspuiClient_state* pCurrentFsmState = &fsm_EspuiClient_state_Idle_imp;
time_t EspuiClientEndTime = 0;
// bool NeedsNotification() { return pCurrentFsmState != &fsm_EspuiClient_state_Idle_imp; }
bool CanSend();
void FillInHeader(ArduinoJson::DynamicJsonDocument& document);
uint32_t prepareJSONChunk(uint16_t startindex, DynamicJsonDocument& rootDoc, bool InUpdateMode);
bool SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode);
bool SendClientNotification(ClientUpdateType_t value);
public:
ESPUIclient(AsyncWebSocketClient * _client);
ESPUIclient(const ESPUIclient & source);
virtual ~ESPUIclient();
void NotifyClient(ClientUpdateType_t value);
void onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len);
bool IsSyncronized();
uint32_t id() { return client->id(); }
void SetState(ClientUpdateType_t value);
bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document);
};

107
src/ESPUIclientFsm.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "ESPUI.h"
#include "ESPUIclient.h"
//----------------------------------------------
// FSM definitions
//----------------------------------------------
void fsm_EspuiClient_state::Init()
{
// Serial.println(String("fsm_EspuiClient_state:Init: ") + GetStateName());
Parent->pCurrentFsmState = this;
}
//----------------------------------------------
//----------------------------------------------
//----------------------------------------------
bool fsm_EspuiClient_state_Idle::NotifyClient()
{
bool Response = false;
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient"));
ClientUpdateType_t TypeToProcess = Parent->ClientUpdateType;
// Clear the type so that we capture any changes in type that happen
// while we are processing the current request.
Parent->ClientUpdateType = ClientUpdateType_t::Synchronized;
// Start processing the current request.
switch (TypeToProcess)
{
case ClientUpdateType_t::Synchronized:
{
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:Synchronized"));
// Parent->fsm_EspuiClient_state_Idle_imp.Init();
Response = true; // Parent->SendClientNotification(ClientUpdateType_t::UpdateNeeded);
break;
}
case ClientUpdateType_t::UpdateNeeded:
{
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:UpdateNeeded"));
Parent->fsm_EspuiClient_state_SendingUpdate_imp.Init();
Response = Parent->SendClientNotification(ClientUpdateType_t::UpdateNeeded);
break;
}
case ClientUpdateType_t::RebuildNeeded:
{
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:RebuildNeeded"));
Parent->fsm_EspuiClient_state_Rebuilding_imp.Init();
Response = Parent->SendClientNotification(ClientUpdateType_t::RebuildNeeded);
break;
}
case ClientUpdateType_t::ReloadNeeded:
{
// Serial.println(F("fsm_EspuiClient_state_Idle: NotifyClient:State:ReloadNeeded"));
Parent->fsm_EspuiClient_state_Reloading_imp.Init();
Response = Parent->SendClientNotification(ClientUpdateType_t::ReloadNeeded);
break;
}
}
return Response;
}
void fsm_EspuiClient_state_Idle::ProcessAck(uint16_t)
{
// This is an unexpected request for control data from the browser
// treat it as if it was a rebuild operation
// Serial.println(F("fsm_EspuiClient_state_Idle: ProcessAck"));
Parent->NotifyClient(ClientUpdateType_t::RebuildNeeded);
}
//----------------------------------------------
//----------------------------------------------
//----------------------------------------------
bool fsm_EspuiClient_state_SendingUpdate::NotifyClient()
{
// Serial.println(F("fsm_EspuiClient_state_SendingUpdate:NotifyClient"));
return true; /* Ignore request */
}
void fsm_EspuiClient_state_SendingUpdate::ProcessAck(uint16_t ControlIndex)
{
// Serial.println(F("fsm_EspuiClient_state_SendingUpdate: ProcessAck"));
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded))
{
// No more data to send. Go back to idle or start next request
Parent->fsm_EspuiClient_state_Idle_imp.Init();
Parent->fsm_EspuiClient_state_Idle_imp.NotifyClient();
}
}
//----------------------------------------------
//----------------------------------------------
//----------------------------------------------
bool fsm_EspuiClient_state_Rebuilding::NotifyClient()
{
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: NotifyClient"));
return true; /* Ignore request */
}
void fsm_EspuiClient_state_Rebuilding::ProcessAck(uint16_t ControlIndex)
{
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: ProcessAck"));
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::RebuildNeeded))
{
// No more data to send. Go back to idle or start next request
Parent->fsm_EspuiClient_state_Idle_imp.Init();
Parent->fsm_EspuiClient_state_Idle_imp.NotifyClient();
}
}

79
src/ESPUIclientFsm.h Normal file
View File

@ -0,0 +1,79 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
// forward declaration
class ESPUIclient;
/*****************************************************************************/
/*
* Generic fsm base class.
*/
/*****************************************************************************/
/*****************************************************************************/
class fsm_EspuiClient_state
{
public:
fsm_EspuiClient_state() {};
virtual ~fsm_EspuiClient_state() {}
void Init();
virtual bool NotifyClient() = 0;
virtual void ProcessAck(uint16_t id) = 0;
virtual String GetStateName () = 0;
void SetParent(ESPUIclient * value) { Parent = value; }
protected:
ESPUIclient * Parent = nullptr;
}; // fsm_EspuiClient_state
class fsm_EspuiClient_state_Idle : public fsm_EspuiClient_state
{
public:
fsm_EspuiClient_state_Idle() {}
virtual ~fsm_EspuiClient_state_Idle() {}
virtual bool NotifyClient();
virtual void ProcessAck(uint16_t id);
String GetStateName() { return String(F("Idle")); }
}; // fsm_EspuiClient_state_Idle
class fsm_EspuiClient_state_SendingUpdate : public fsm_EspuiClient_state
{
public:
fsm_EspuiClient_state_SendingUpdate() {}
virtual ~fsm_EspuiClient_state_SendingUpdate() {}
virtual bool NotifyClient();
virtual void ProcessAck(uint16_t id);
String GetStateName() { return String(F("Sending Update")); }
}; // fsm_EspuiClient_state_SendingUpdate
class fsm_EspuiClient_state_Rebuilding : public fsm_EspuiClient_state
{
public:
fsm_EspuiClient_state_Rebuilding() {}
virtual ~fsm_EspuiClient_state_Rebuilding() {}
virtual bool NotifyClient();
virtual void ProcessAck(uint16_t id);
String GetStateName() { return String(F("Sending Rebuild")); }
}; // fsm_EspuiClient_state_Rebuilding
class fsm_EspuiClient_state_Reloading : public fsm_EspuiClient_state
{
public:
fsm_EspuiClient_state_Reloading() {}
virtual ~fsm_EspuiClient_state_Reloading() {}
virtual bool NotifyClient() { return false; }
virtual void ProcessAck(uint16_t) {}
String GetStateName() { return String(F("Reloading")); }
}; // fsm_EspuiClient_state_Reloading

252
src/ESPUIcontrol.cpp Normal file
View File

@ -0,0 +1,252 @@
#include "ESPUI.h"
static uint16_t idCounter = 0;
static const String ControlError = "*** ESPUI ERROR: Could not transfer control ***";
Control::Control(ControlType type, const char* label, void (*callback)(Control*, int, void*), void* UserData,
const String& value, ControlColor color, bool visible, uint16_t parentControl)
: type(type),
label(label),
callback(nullptr),
extendedCallback(callback),
user(UserData),
value(value),
color(color),
visible(visible),
wide(false),
vertical(false),
enabled(true),
parentControl(parentControl),
next(nullptr)
{
id = ++idCounter;
}
Control::Control(const Control& Control)
: type(Control.type),
id(Control.id),
label(Control.label),
callback(Control.callback),
extendedCallback(Control.extendedCallback),
user(Control.user),
value(Control.value),
color(Control.color),
visible(Control.visible),
parentControl(Control.parentControl),
next(Control.next)
{ }
void Control::SendCallback(int type)
{
if(callback)
{
callback(this, type);
}
if (extendedCallback)
{
extendedCallback(this, type, user);
}
}
void Control::DeleteControl()
{
ControlSyncState = ControlSyncState_t::deleted;
extendedCallback = nullptr;
callback = nullptr;
}
void Control::MarshalControl(JsonObject & item, bool refresh)
{
item[F("id")] = id;
ControlType TempType = (ControlType::Password == type) ? ControlType::Text : type;
if(refresh)
{
item[F("type")] = uint32_t(TempType) + uint32_t(ControlType::UpdateOffset);
}
else
{
item[F("type")] = uint32_t(TempType);
}
item[F("label")] = label;
item[F ("value")] = (ControlType::Password == type) ? F ("--------") : value;
item[F("visible")] = visible;
item[F("color")] = (int)color;
item[F("enabled")] = enabled;
if (!panelStyle.isEmpty()) {item[F("panelStyle")] = panelStyle;}
if (!elementStyle.isEmpty()) {item[F("elementStyle")] = elementStyle;}
if (!inputType.isEmpty()) {item[F("inputType")] = inputType;}
if (wide == true) {item[F("wide")] = true;}
if (vertical == true) {item[F("vertical")] = true;}
if (parentControl != Control::noParent)
{
item[F("parentControl")] = String(parentControl);
}
// special case for selects: to preselect an option, you have to add
// "selected" to <option>
if (ControlType::Option == type)
{
Control* ParentControl = ESPUI.getControlNoLock(parentControl);
if (nullptr == ParentControl)
{
item[F("selected")] = emptyString;
}
else if (ParentControl->value == value)
{
item[F("selected")] = F("selected");
}
else
{
item[F("selected")] = "";
}
}
}
void Control::MarshalErrorMessage(JsonObject & item)
{
item[F("id")] = id;
item[F("type")] = uint32_t(ControlType::Label);
item[F("label")] = ControlError.c_str();
item[F("value")] = ControlError;
item[F("visible")] = true;
item[F("color")] = (int)ControlColor::Carrot;
item[F("enabled")] = true;
if (parentControl != Control::noParent)
{
item[F("parentControl")] = String(parentControl);
}
}
void Control::onWsEvent(String & cmd, String& data)
{
do // once
{
if (!HasCallback())
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity)
{
Serial.println(String(F("Control::onWsEvent:No callback found for ID ")) + String(id));
}
#endif
break;
}
// Serial.println("Control::onWsEvent:Generating callback");
if (cmd.equals(F("bdown")))
{
SendCallback(B_DOWN);
break;
}
if (cmd.equals(F("bup")))
{
SendCallback(B_UP);
break;
}
if (cmd.equals(F("pfdown")))
{
SendCallback(P_FOR_DOWN);
break;
}
if (cmd.equals(F("pfup")))
{
SendCallback(P_FOR_UP);
break;
}
if (cmd.equals(F("pldown")))
{
SendCallback(P_LEFT_DOWN);
break;
}
else if (cmd.equals(F("plup")))
{
SendCallback(P_LEFT_UP);
}
else if (cmd.equals(F("prdown")))
{
SendCallback(P_RIGHT_DOWN);
}
else if (cmd.equals(F("prup")))
{
SendCallback(P_RIGHT_UP);
}
else if (cmd.equals(F("pbdown")))
{
SendCallback(P_BACK_DOWN);
}
else if (cmd.equals(F("pbup")))
{
SendCallback(P_BACK_UP);
}
else if (cmd.equals(F("pcdown")))
{
SendCallback(P_CENTER_DOWN);
}
else if (cmd.equals(F("pcup")))
{
SendCallback(P_CENTER_UP);
}
else if (cmd.equals(F("sactive")))
{
value = "1";
SendCallback(S_ACTIVE);
}
else if (cmd.equals(F("sinactive")))
{
value = "0";
// updateControl(c, client->id());
SendCallback(S_INACTIVE);
}
else if (cmd.equals(F("slvalue")))
{
value = data;
// updateControl(c, client->id());
SendCallback(SL_VALUE);
}
else if (cmd.equals(F("nvalue")))
{
value = data;
// updateControl(c, client->id());
SendCallback(N_VALUE);
}
else if (cmd.equals(F("tvalue")))
{
value = data;
// updateControl(c, client->id());
SendCallback(T_VALUE);
}
else if (cmd.equals(F("tabvalue")))
{
SendCallback(0);
}
else if (cmd.equals(F("svalue")))
{
value = data;
// updateControl(c, client->id());
SendCallback(S_VALUE);
}
else if (cmd.equals(F("time")))
{
value = data;
// updateControl(c, client->id());
SendCallback(TM_VALUE);
}
else
{
#if defined(DEBUG_ESPUI)
if (ESPUI.verbosity)
{
Serial.println(F("Control::onWsEvent:Malformed message from the websocket"));
}
#endif
}
} while (false);
}

133
src/ESPUIcontrol.h Normal file
View File

@ -0,0 +1,133 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
enum ControlType : uint8_t
{
// fixed Controls
Title = 0,
// updatable Controls
Pad,
PadWithCenter,
Button,
Label,
Switcher,
Slider,
Number,
Text,
Graph,
GraphPoint,
Tab,
Select,
Option,
Min,
Max,
Step,
Gauge,
Accel,
Separator,
Time,
Password = 99,
UpdateOffset = 100,
};
enum ControlColor : uint8_t
{
Turquoise,
Emerald,
Peterriver,
Wetasphalt,
Sunflower,
Carrot,
Alizarin,
Dark,
None = 0xFF
};
class Control
{
public:
ControlType type;
uint16_t id; // just mirroring the id here for practical reasons
const char* label;
void (*callback)(Control*, int);
void (*extendedCallback)(Control*, int, void*);
void* user;
String value;
ControlColor color;
bool visible;
bool wide;
bool vertical;
bool enabled;
uint16_t parentControl;
String panelStyle;
String elementStyle;
String inputType;
Control* next;
static constexpr uint16_t noParent = 0xffff;
Control(ControlType type,
const char* label,
void (*callback)(Control*, int, void*),
void* UserData,
const String& value,
ControlColor color,
bool visible,
uint16_t parentControl);
Control(const Control& Control);
void SendCallback(int type);
bool HasCallback() { return ((nullptr != callback) || (nullptr != extendedCallback)); }
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh);
void MarshalErrorMessage(ArduinoJson::JsonObject& item);
bool ToBeDeleted() { return (ControlSyncState_t::deleted == ControlSyncState); }
void DeleteControl();
bool IsUpdated() { return ControlSyncState_t::synchronized != ControlSyncState; }
void HasBeenUpdated() { ControlSyncState = ControlSyncState_t::updated; }
void HasBeenSynchronized() {ControlSyncState = ControlSyncState_t::synchronized;}
void onWsEvent(String& cmd, String& data);
private:
enum ControlSyncState_t
{
synchronized = 0,
updated,
deleted,
};
ControlSyncState_t ControlSyncState = ControlSyncState_t::synchronized;
};
#define UI_TITLE ControlType::Title
#define UI_LABEL ControlType::Label
#define UI_BUTTON ControlType::Button
#define UI_SWITCHER ControlType::Switcher
#define UI_PAD ControlType::Pad
#define UI_CPAD ControlType::Cpad
#define UI_SLIDER ControlType::Slider
#define UI_NUMBER ControlType::Number
#define UI_TEXT_INPUT ControlType::Text
#define UI_GRAPH ControlType::Graph
#define UI_ADD_GRAPH_POINT ControlType::GraphPoint
#define UPDATE_LABEL ControlType::UpdateLabel
#define UPDATE_SWITCHER ControlType::UpdateSwitcher
#define UPDATE_SLIDER ControlType::UpdateSlider
#define UPDATE_NUMBER ControlType::UpdateNumber
#define UPDATE_TEXT_INPUT ControlType::UpdateText
#define CLEAR_GRAPH ControlType::ClearGraph
// Colors
#define COLOR_TURQUOISE ControlColor::Turquoise
#define COLOR_EMERALD ControlColor::Emerald
#define COLOR_PETERRIVER ControlColor::Peterriver
#define COLOR_WETASPHALT ControlColor::Wetasphalt
#define COLOR_SUNFLOWER ControlColor::Sunflower
#define COLOR_CARROT ControlColor::Carrot
#define COLOR_ALIZARIN ControlColor::Alizarin
#define COLOR_DARK ControlColor::Dark
#define COLOR_NONE ControlColor::None

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,5 +1,27 @@
// Old PNG File
//const char HTML_INDEX[] PROGMEM = R"=====(
//<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
//)=====";
// SVG Water Drop Icon
//const char HTML_INDEX[] PROGMEM = R"=====(
//<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href="data:image/svg+xml,%3Csvg data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M504.08 256C504.08 119 393 7.92 256 7.92S7.92 119 7.92 256 119 504.08 256 504.08 504.08 393 504.08 256z' style='fill:%23009040'/%3E%3Cpath d='M289.15 157.48A306.25 306.25 0 0 1 256 84.69a306.22 306.22 0 0 1-33.14 72.8c-28.24 56.86-73.39 86.85-73.39 158.38 0 61.55 45 111.44 106.53 111.44 61.55 0 106.53-49.89 106.53-111.44.01-71.54-45.14-101.52-73.38-158.39z' style='fill:%2398c8f0'/%3E%3Cpath d='M289.15 157.48A306.25 306.25 0 0 1 256 84.69s-61.57 342.62 0 342.62c61.55 0 106.53-49.89 106.53-111.44.01-71.54-45.14-101.52-73.38-158.39z' style='fill:%23b0e0ff'/%3E%3Cpath d='M262.08 409.12c48.17 0 83.38-39 83.38-87.22.01-55.99-131.55 87.22-83.38 87.22z' style='fill:%23ffffff'/%3E%3C/svg%3E"><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
//)=====";
// Apple icon only
//const char HTML_INDEX[] PROGMEM = R"=====(
//<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="apple-touch-icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0AQMAAAAHA5RxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAGUExURYi/Sv///zEELvQAAAFGSURBVFjD7dUxbsUgEIRhLAqXPoKPskczRyNKkTJX4AiUFAhHr4g0kfZXslJKU36VwbOz6XZPT48//jqf4B/g7+Bv4JX8AjfwE/wA38Ez+AaeyC9wAz/BD/AdPINv4In8AjfwE/z44S19nx08g2/gifwCN/AT/ADfwTP4Bp7Ir3/y4Pfk4H2P4Hta8H8F85D/lDfK5+95tuC8BOdR59f8eV/m98M0v0+G+f3Tze+rZn6/VfP7sJjfn8ncvl3q0s9TXfp8qEv/d3XZF01d9ksV131UxHV/JXHZd0td9uNUl306wDt4A6/gBTz5vnTgxCf4AO/gDbyCF/Dk+wKf4AO8g7d/8h78nhm8L70PvmcN/q8ezAPlh/JG+cQ81+C89OA80vwu8Ju8gFfwBt7BB/gEX+A3eQGv4A28gw/wCb7Ab/L78cdf/gVoGRZWiw+tcgAAAABJRU5ErkJggg==><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
//)=====";
// SVG Logo Only
//const char HTML_INDEX[] PROGMEM = R"=====(
//<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 180'%3E%3Cpath d='M74 46v28h55v27l40-40l-40-40v27zM104 100v28h-55v27l-40-40l40-40v27z' fill='%2388BF4A' /%3E%3C/svg%3E"><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
//)=====";
// SVG Logo & Apple icon
const char HTML_INDEX[] PROGMEM = R"=====(
<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
<!DOCTYPE html><html><head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 180'%3E%3Cpath d='M74 46v28h55v27l40-40l-40-40v27zM104 100v28h-55v27l-40-40l40-40v27z' fill='%2388BF4A' /%3E%3C/svg%3E"><link rel="apple-touch-icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0AQMAAAAHA5RxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAGUExURYi/Sv///zEELvQAAAFGSURBVFjD7dUxbsUgEIRhLAqXPoKPskczRyNKkTJX4AiUFAhHr4g0kfZXslJKU36VwbOz6XZPT48//jqf4B/g7+Bv4JX8AjfwE/wA38Ez+AaeyC9wAz/BD/AdPINv4In8AjfwE/z44S19nx08g2/gifwCN/AT/ADfwTP4Bp7Ir3/y4Pfk4H2P4Hta8H8F85D/lDfK5+95tuC8BOdR59f8eV/m98M0v0+G+f3Tze+rZn6/VfP7sJjfn8ncvl3q0s9TXfp8qEv/d3XZF01d9ksV131UxHV/JXHZd0td9uNUl306wDt4A6/gBTz5vnTgxCf4AO/gDbyCF/Dk+wKf4AO8g7d/8h78nhm8L70PvmcN/q8ezAPlh/JG+cQ81+C89OA80vwu8Ju8gFfwBt7BB/gEX+A3eQGv4A28gw/wCb7Ab/L78cdf/gVoGRZWiw+tcgAAAABJRU5ErkJggg==><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head><body onload=javascript:start();><div><h4><div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span></h4></div><hr><div class=container><div id=row class="row u-full-width"></div><ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul><div id=tabscontent class="tabscontent u-full-width"></div></div></body></html>
)=====";
const uint8_t HTML_INDEX_GZIP[916] PROGMEM = { 31,139,8,0,19,56,231,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 };
const uint8_t HTML_INDEX_GZIP[916] PROGMEM = { 31,139,8,0,210,8,51,99,2,255,133,148,235,115,162,58,20,192,255,21,174,159,238,157,221,22,95,181,237,174,56,19,20,108,85,68,64,240,241,45,64,42,193,240,40,9,162,254,245,155,128,157,238,157,189,211,235,12,201,201,57,191,243,200,17,206,240,175,137,57,94,239,86,154,20,177,132,140,134,245,42,13,35,4,195,209,48,65,12,74,65,4,11,138,152,82,178,183,187,167,209,144,97,70,208,104,156,165,172,200,200,80,110,142,13,153,194,4,41,39,140,170,60,43,152,20,112,4,165,76,105,85,56,100,145,18,162,19,14,208,93,125,248,46,225,20,51,12,201,29,13,32,65,74,167,53,26,18,156,30,165,2,17,165,69,35,238,30,148,76,194,60,68,75,138,10,244,166,132,144,193,31,56,129,7,36,231,233,225,167,15,41,26,244,191,99,79,53,237,170,61,159,30,50,192,127,75,199,141,52,247,192,37,85,28,129,53,6,134,216,179,133,245,188,22,130,58,13,213,181,171,1,176,152,174,198,242,57,82,45,174,28,171,177,163,207,150,220,58,152,113,223,195,43,87,174,68,188,49,24,240,53,20,158,102,46,162,14,18,190,232,189,113,80,18,237,73,196,91,233,170,225,105,110,205,118,188,141,221,237,188,91,93,130,237,158,254,46,30,167,231,149,118,207,227,114,179,155,221,78,226,116,59,165,83,219,248,195,229,89,119,58,183,184,31,231,57,163,139,188,170,220,155,150,143,34,190,150,218,206,154,24,64,205,64,239,184,82,171,235,19,126,152,95,113,118,62,244,151,94,224,246,74,109,246,82,223,116,51,91,216,109,11,96,224,134,78,173,32,149,163,147,171,232,73,167,2,96,18,6,170,133,177,143,106,91,216,118,220,142,174,126,211,116,45,10,52,227,219,75,190,171,68,35,212,157,227,218,170,167,198,235,104,39,207,38,20,28,198,154,195,250,36,223,202,167,231,120,97,237,103,131,119,253,153,245,142,17,184,196,157,243,214,157,251,86,95,245,178,106,82,189,155,187,131,118,62,30,55,125,188,27,144,21,212,219,3,251,221,120,218,189,50,179,180,125,3,94,7,231,216,167,37,157,128,141,28,22,57,81,15,253,199,120,53,120,30,4,91,237,201,95,129,14,114,39,104,222,21,213,205,182,182,190,121,177,143,187,173,77,204,100,121,217,111,244,246,222,2,23,99,162,245,22,107,208,89,172,181,190,55,121,189,154,49,104,155,177,123,158,95,193,133,203,213,89,123,120,70,149,248,43,188,182,237,69,237,253,148,251,173,115,230,119,237,124,159,30,129,17,131,243,242,210,174,150,78,251,108,234,214,197,184,102,213,114,146,117,12,135,86,70,156,85,198,194,93,159,131,64,148,176,15,53,111,23,234,203,211,62,181,123,187,237,140,128,151,176,23,94,30,114,63,97,215,93,87,175,246,206,195,41,72,144,255,24,87,176,110,169,70,244,245,209,41,173,100,60,254,237,77,166,236,66,16,141,16,98,205,75,44,7,148,202,105,86,36,144,224,43,186,231,167,255,131,107,101,3,210,160,192,57,147,104,17,40,114,76,229,43,202,89,118,159,224,244,62,230,70,185,177,254,65,81,130,67,84,124,137,28,10,152,71,95,18,65,243,173,211,47,33,6,125,31,133,183,111,254,223,164,92,15,19,105,232,103,225,69,202,82,146,193,80,137,225,9,54,246,31,148,193,130,253,253,207,79,78,132,248,36,102,79,191,17,37,28,42,9,196,233,11,119,71,197,231,200,105,40,154,195,84,16,60,165,195,32,43,169,20,16,72,169,66,160,143,200,200,124,123,227,157,69,188,8,142,113,90,174,131,222,92,163,226,150,160,241,16,69,243,52,168,248,76,91,100,213,205,216,18,98,121,247,86,18,210,204,46,62,170,110,97,74,34,80,126,113,154,194,143,88,45,46,226,3,100,56,75,165,79,241,78,64,127,68,41,201,103,66,1,220,154,247,17,233,119,213,127,23,240,177,137,198,214,119,172,103,247,47,103,53,186,226,210,5,0,0 };

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long