Commit 8c4fc341 authored by Eric Duminil's avatar Eric Duminil
Browse files

IotWebConf in src, not in platformio

parent 9d0a0e38
set(COMPONENT_SRCS
src/IotWebConf.cpp
src/IotWebConfMultipleWifi.cpp
src/IotWebConfOptionalGroup.cpp
src/IotWebConfParameter.cpp
src/IotWebConfESP32HTTPUpdateServer.cpp
)
set(COMPONENT_ADD_INCLUDEDIRS
src/
)
list(APPEND COMPONENT_REQUIRES "arduino")
register_component()
#ADD_DEFINITIONS(-DESP32)
list(APPEND DEFINITIONS "ESP32")
{
"folders": [
{
"path": "."
},
{
"path": "../IotWebConf-examples/IotWebConf01Minimal"
},
{
"path": "../IotWebConf-examples/IotWebConf02StatusAndReset"
},
{
"path": "../IotWebConf-examples/IotWebConf03CustomParameters"
},
{
"path": "../IotWebConf-examples/IotWebConf03TypedParameters"
},
{
"path": "../IotWebConf-examples/IotWebConf04UpdateServer"
},
{
"path": "../IotWebConf-examples/IotWebConf05Callbacks"
},
{
"path": "../IotWebConf-examples/IotWebConf06MqttApp"
},
{
"path": "../IotWebConf-examples/IotWebConf07MqttRelay"
},
{
"path": "../IotWebConf-examples/IotWebConf08WebRelay"
},
{
"path": "../IotWebConf-examples/IotWebConf09CustomConnection"
},
{
"path": "../IotWebConf-examples/IotWebConf10CustomHtml"
},
{
"path": "../IotWebConf-examples/IotWebConf11AdvancedRuntime"
},
{
"path": "../IotWebConf-examples/IotWebConf12CustomParameterType"
},
{
"path": "../IotWebConf-examples/IotWebConf13OptionalGroup"
},
{
"path": "../IotWebConf-examples/IotWebConf14GroupChain"
},
{
"path": "../IotWebConf-examples/IotWebConf15MultipleWifi"
},
{
"path": "../IotWebConf-examples/IotWebConf16OffLineMode"
},
{
"path": "../IotWebConf-examples/IotWebConf17JsonConfig"
}
],
"settings": {
"workbench.tree.indent": 16
}
}
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="CheckStyle-IDEA-Module">
<option name="configuration">
<map />
</option>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="sonarModuleSettings">
<option name="alternativeWorkingDirPath" value="" />
<option name="localAnalysisScripName" value="&lt;PROJECT&gt;" />
<option name="serverName" value="&lt;PROJECT&gt;" />
<option name="useAlternativeWorkingDir" value="false" />
<option name="workingDirSelection" value="&lt;MODULE&gt;" />
</component>
</module>
\ No newline at end of file
The MIT License (MIT)
Copyright 2018 Balazs Kelemen <prampec+arduino@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# IotWebConf [![Build Status](https://github.com/prampec/IotWebConf/workflows/PlatformIO%20CI/badge.svg?branch=master)](https://github.com/prampec/IotWebConf/actions/workflows/test.platformio.yml)
## Upgrading to v3.0.0
Lately version 3.0.0 is released. This release is not backward compatible with
older versions, and some modification have to be done on existing codes.
**Please visit [Migration Guide](doc/MigrationGuide-v3.0.0.md) for
details!**
## Summary
IotWebConf is an Arduino library for ESP8266/ESP32 to provide a non-blocking standalone WiFi/AP web configuration portal.
**For ESP8266, IotWebConf requires the esp8266 board package version 2.4.2 or later!**
Please subscribe to the [discussion forum](https://groups.google.com/forum/#!forum/iotwebconf), if you want to be informed on the latest news.
Also visit experimental [Discord server](https://discord.gg/GR3uQeD).
**HELP WANTED!** If you are testing any GIT branches, please give me feedback to provide stable releases for the public.
## Highlights
- Manages WiFi connection settings,
- Provides a config portal user interface,
- You can extend the configuration with your own sophisticated propery structure, that is stored automatically,
- Option to configure multiple WiFi connections. (Try next when the
last used one is just not available.)
- HTML customization,
- Validation support for the configuration property items,
- User code will be notified of status changes with callback methods,
- Configuration (including your custom items) stored in the EEPROM,
- Firmware OTA update support,
- Config portal remains available even after WiFi is connected,
- Automatic "Sign in to network" pop up in your browser (captive portal),
- Non-blocking - Your custom code will not be blocked in the whole process.
- Well documented header file, and examples from simple to complex levels.
![Screenshot](https://sharedinventions.com/wp-content/uploads/2018/11/Screenshot_20181105-191748a.png)
![Screenshot](https://sharedinventions.com/wp-content/uploads/2019/02/Screenshot-from-2019-02-03-22-16-51b.png)
## How it works
The idea is that the Thing will provide a web interface to allow modifying its configuration. E.g. for connecting to a local WiFi network, it needs the SSID and the password.
When no WiFi is configured, or the configured network is unavailable it creates its own AP (access point), and lets clients connect to it directly to make the configuration.
Furthermore there is a button (or let's say a Pin), that when pressed on startup will cause a default password to be used instead of the configured (forgotten) one.
You can find the default password in the sources. :)
IotWebConf saves configuration in the "EEPROM". You can extend the config portal with your custom configuration items. Those items will be also maintained by IotWebConf.
Visit [Users Manual](doc/UsersManual.md) for detailed description!
## Use cases
1. **You turn on your IoT the first time** - It turns into AP (access point) mode, and waits for you on the 192.168.4.1 address with a web interface to set up your local network (and other configurations). For the first time a default password is used when you connect to the AP. When you connect to the AP, your device will likely automatically pop up the portal page. (We call this a Captive Portal.) When configuration is done, you must leave the AP. The device detects that no one is connected, and continues with normal operation.
1. **WiFi configuration is changed, e.g. the Thing is moved to another location** - When the Thing cannot connect to the configured WiFi, it falls back to AP mode, and waits for you to change the network configuration. When no configuration was made, then it keeps trying to connect with the already configured settings. The Thing will not switch off the AP while anyone is connected to it, so you must leave the AP when finished with the configuration.
1. **You want to connect to the AP, but have forgotten the configured AP WiFi password you set up previously** - Connect the appropriate pin on the Arduino to ground with a push button. Holding the button pressed while powering up the device causes the Thing to start the AP mode with the default password. (See Case 1. The pin is configured in the code.)
1. **You want to change the configuration before the Thing connects to the Internet** - Fine! The Thing always starts up in AP mode and provides you a time frame to connect to it and make any modification to the configuration. Any time one is connected to the AP (provided by the device) the AP will stay on until the connection is closed. So take your time for the changes, the Thing will wait for you while you are connected to it.
1. **You want to change the configuration at runtime** - No problem. IotWebConf keeps the config portal up and running even after the WiFi connection is finished. In this scenario you must enter username "admin" and password (already configured) to enter the config portal. Note, that the password provided for the authentication is not hidden from devices connected to the same WiFi network. You might want to force rebooting of the Thing to apply your changes.
## User notes
- In the config portal you can double-tap on a password to reveal what
you have typed in. (Double-tap again to hide revealed text.)
- When accessing the config portal via connected WiFi network a dialog
with user-name and password will pop up. The password is the one you
have configured for "AP password". The user name is "admin".
- Consult [Users Manual](doc/UsersManual.md) for more details!
## IotWebConf vs. WiFiManager
tzapu's WiFiManager is a great library. The features of IotWebConf may appear very similar to WiFiManager. However, IotWebConf tries to be different.
- WiFiManager does not allow you to configure **mutiple WiFi** connections. In IotWebConf there is a way to define more connections: if one is not available, the next is tried automatically.
- ~~WiFiManager does not manage your **custom properties**.~~ IotWebConf stores your configuration in "EEPROM".
- WiFiManager does not do **validation**. IotWebConf allow you to validate your property changes made in the config portal.
- ~~WiFiManager does not support ESP32.~~
- ~~With WiFiManager you cannot use both startup and **on-demand configuration**.~~ With IotWebConf the config portal remains available via the connected local WiFi.
- WiFiManager provides list of available networks, and an information page, while these features are cool, IotWebConf tries to keep the code simple. So these features are not (yet) provided by IotWebConf.
- IotWebConf is fitted for more advanced users. You can keep control of the web server setup, configuration item input field behavior, and validation.
## Security aspects
- The initial system password must be modified by the user, so there is no build-in password.
- When connecting in AP mode, the WiFi provides an encryption layer (WPA/WPA2), so all your communication here is known to be safe. (The exact wifi encryption depends on the used board/chipset and implementation in the related esp/arduino framework.)
- When connecting through a WiFi router (WiFi mode), the Thing will ask for authentication when someone requests the config portal. This is required as the Thing will be visible for all devices sharing the same network. But be warned by the following note...
- NOTE: **When connecting through a WiFi router (WiFi mode), your communication is not hidden from devices connecting to the same network.** It communicates over unencrypted HTTP. So either: Do not allow ambiguous devices connecting to your WiFi router, or configure your Thing only in AP mode!
- However IotWebConf has a detailed debug output, passwords are not shown in this log by default. You have
to enable password visibility manually in the IotWebConf.h with the IOTWEBCONF_DEBUG_PWD_TO_SERIAL
if it is needed.
## Compatibility
IotWebConf is primary built for ESP8266. But meanwhile it was discovered, that the code can be adopted
to ESP32. There are two major problems.
- ESP8266 uses specific naming for it's classes (e.g. ESP8266WebServer). However, ESP32 uses a more generic naming (e.g. WebServer). The idea here is to use the generic naming hoping that ESP8266 will adopt these "standards" sooner or later.
- ESP32 does not provide an HTTPUpdateServer implementation. So in this project we have implemented one. Whenever ESP32 provides an official HTTPUpdateServer, this local implementation will be removed.
## Customizing and extending functionality
IotWebConf is ment to be developer friendly by providing lots
of customization options. See [HackingGuide](doc/HackingGuide.md) for
details.
## TODO / Feature requests
- We might want to add a "verify password" field.
- Provide an option, where IotWebConf renders HTML-response,
handles HTTP-request for a specific branch of groups.
- Separate WiFi management from the code, so config portal can also
be a standalone solution without any WiFi.
## Known issues
- It is reported, that there might be unstable working with different lwIP variants. If you experiment serious problems, try to select another lwIP variant for your board in the Tools menu! (Tested with "v2 Lower Memory" version.)
## Credits
Although IotWebConf started without being influenced by any other solutions, in the final code you can find some segments borrowed from the WiFiManager library.
- https://github.com/tzapu/WiFiManager
Thanks to [all contributors](https://github.com/prampec/IotWebConf/graphs/contributors) providing patches for the library!
# IotWebConf hacking guide
IotWebConf comes with a lot of examples. These examples are intended
to be easy to use, with clear goals. While IotWebConf is also ment to be
developer friendly, providing lots of customization options.
This documentation will try to explain features where you can customize
IotWebConf on your need, or where a feature explanation might be out of the
scope of regular examples.
Please note, that header files are full of
documentation, so please heavily consult ```IotWebConf.h``` header file
while/beside reading this documentation.
__Contents__:
- [PlatformIO](#using-iotwebconf-with-platformio)
- [Compile time configuration](#compile-time-configuration)
- [Groups and Parameters](#groups-and-parameters)
- [Optional and chained groups](#optional-and-chained-groups)
- [Using System parameter-group](#using-system-parameter-group)
- [Alternative WiFi connection](#alternative-wifi-connection)
- [Accessing system properties](#accessing-system-properties)
- [Use custom style](#use-custom-style)
- [Create your property class](#create-your-property-class)
- [Typed parameters](#typed-parameters-experimental)
- [Control on WiFi connection status change](#control-on-wifi-connection-status-change)
- [Use alternative WebServer](#use-alternative-webserver)
## Using IotWebConf with PlatformIO
It is recommended to use PlatformIO instead of the Arduino environment.
With v3.0.0, a folder ```pio``` is provided with scripts that transforms
examples to PlatformIO compatible format. You can use these as templates
for your project. (On the other hand, these scripts creating soft-link
loops, and having soft link loops in these folders might cause Arduino
and other environment to fail. Just keep in mind, if something goes
wrong with your IDE, then examples-pio is likely the corporate.)
## Compile time configuration
IotWebConf includes a configuration file named IotWebConfSettings.h.
This configuration file works on C pre-compiler mechanism. This means
you cannot use it in Arduino environment, so I encourage everyone to
switch to PlatformIO.
In the PlatformIO you can do configuration changes by adding lines to
platformio.ini like this:
```
build_flags =
-DIOTWEBCONF_DEFAULT_WIFI_CONNECTION_TIMEOUT_MS="60000"
-DIOTWEBCONF_DEBUG_DISABLED
```
**Note:** You must not use ```#define IOTWEBCONF_CONFIG_START 20```, or
similar defines in your .ino file (e.g. before the includes). It will eventually
just not work, as all .cpp files are compiled separately for each other.
Thus, you must use the ```-D``` compiler flag for the job.
## Groups and Parameters
With version 3.0.0 IotWebConf introduces individual parameter classes for
each type, and you can organize your parameters into groups.
You can also free to add groups into groups to make a tree hierarchy.
## Optional and chained groups
With ```OptionalParameterGroup```, the group you have defined will have
a special appearance in the config portal, as the fieldset in which the
group items are shown can be hidden (inactive) / shown (active).
E.g you want to create a group with property items, that are not mandatory,
so you can hide these options in the config portal by default, and
only reveal the contents, when it is strictly requested.
There is a specific example covering this very feature under
```IotWebConf13OptionalGroup```.
```ChainedParameterGroup```s can be linked. One after another. The
property sets will reveal on after another, when user requests is. The
difference between ```OptionalParameterGroup``` and ```ChainedParameterGroup```
is that second group item in a chained list can only be added, when
the first item is already visible.
There is a specific example covering this very feature under
```IotWebConf14GroupChain```.
## Using system parameter-group
By default, you should add your own parameter group, that will appear as
a new field-set on the Config Portal. However, there is a special group
maintained by IotWebConf called the System group, where you are also
allowed to add your own custom properties.
Example:
```
iotWebConf.addSystemParameter(&stringParam);
```
You can directly access system-parameter group by calling
```getSystemParameterGroup()```.
Example:
```
ParameterGroup* systemParameters = iotWebConf.getSystemParameterGroup();
systemParameters.label = "My Custom Label";
```
There is another group "WiFi parameters" managed by IotWebConf, that
can be retrieved by getWifiParameterGroup().
## Alternative WiFi connection
With v3.0.0 you can set up multiple WiFi connection by utilizing the
MultipleWifiAddition class can be found in IotWebConfMultipleWifi.h .
This class basically set up some handlers in iotWebConf to
1. display optional WiFi settings in admin GUI,
2. use these alternative settings in case previous WiFi connection
attempts fails.
The maximal number of connection settings are determined compile-time,
as we want to avoid any dynamic memory allocations in Arduino.
There is a complete example covering this topic, please visit example
```IotWebConf15MultipleWifi```!
## Accessing system properties
IotWebConf comes with some parameters, that are required for the basic
functionality. You can retrieve these parameter by getters, e.g.
```getThingNameParameter()```. You can directly modify these items as
seen in the code block below.
There is a dedicated example covering this topic, so please visit
example ```IotWebConf11AdvancedRuntime```!
```
// -- Update Thing name
strncpy(
iotWebConf.getThingNameParameter()->valueBuffer,
"My changed name",
iotWebConf.getThingNameParameter()->getLength());
iotWebConf.saveConfig();
```
Here is list of some of the system parameter-acccessors, please consult
IotWebConf.h for further details.
- getSystemParameterGroup()
- getThingNameParameter()
- getApPasswordParameter()
- getWifiParameterGroup()
- getWifiSsidParameter()
- getWifiPasswordParameter()
- getApTimeoutParameter()
## Use custom style
You can provide your own custom HTML template by updating default
HTML format provider. For this you should utilize the
```setHtmlFormatProvider()``` method.
There is a complete example about this topic, so please visit example
```IotWebConf10CustomHtml```!
## Create your property class
With version 3.0.0 you are free to create your own property class.
It is done by inheriting the iotwebconf::Parameter C++ class. You can use
other property types e.g. PasswordProperty as a template for this.
Now, custom properties are mainly handy, when you would like to create
some special HTML form item. But eventually you can change the whole
behaviour of your parameter handling. E.g. by overriding ```storeValue()```
and ```loadValue()``` you can basically convert your internal data format
to whatever you like. The [Typed parameters](#typed-parameters-experimental)
approach is just an excellent example for this option.
You can also override ParameterGroup class in case you need some special
group appearance.
There is a complete example about this topic, so please visit example
```IotWebConf12CustomParameterType```!
## Typed parameters (experimental)
A new parameter structure is introduced, where the parameters does not
require a "valueBuffer" anymore. Storing the parameter is done in a
native format, e.g. a 8-bit integers are stored in one byte of EEPROM.
This was achieved by utilizing the ```template``` technology of C++.
While the result is spectacular, the ```template``` makes thing very
complicated under the hood.
Builder pattern is also introduced for the typed parameters. See example
```IotWebConf03TypedParameters``` for details. Please compare example
IotWebConf03TypedParameters and IotWebConf03TypedParameters for the
difference in the usage of the two different approach.
**Please note, that Typed Parameters are very experimental, and the
interface might be a subject of change in the future.**
![UML diagram of the Typed Parameters approach.](TParameter.png)
(This image was created by PlantUML, the source file is generate with command
```hpp2plantuml -i src/IotWebConfTParameter.h -o doc/TParameter.plantuml```)
## Control on WiFi connection status change
IotWebConf provides a feature to control WiFi connection events by defining
your custom handler event handler.
With ```setWifiConnectionFailedHandler()``` you can set up a handler, that
will be called, when a connection to a WiFi network failed (most likely
timed out). Now, when you return with a new valid connection-info from
your callback, IotWebConf will not fall back to AP mode, but try the
connection you have just provided. With this method you can theoretically
set up multiple WiFi networks for IotWebConf to try connect to after
one-by-one if the previous one fails. Some days IotWebConf might also
provide this feature out of the box.
There is a second method, where you can define a specific handler, this
is the ```setWifiConnectionHandler()```. Your method will be called when
IotWebConf trying to establish connection to a WiFi network.
For details please consult ```IotWebConf.h``` header file!
## Use alternative WebServer
There was an expressed need from your side for supporting specific types of
Web servers. (E.g https
web server or async web server.) So, with v3.0.0 there is an option to
use web server of your choice. To achieve this, you will call IotWebConf
constructor, that accepts a ```WebServerWrapper``` pointer.
In the WebServerWrapper you have the implement all expected web server
functionalities (as seen in the header file). You can use
the ```StandardWebServerWrapper``` as a template for that.
Further more, you also need to provide your custom ```WebRequestWrapper```
instances when calling ```handleCaptivePortal()```, ```handleConfig()``` and
```handleNotFound()```.
Unfortunately I currently do not have the time to implement solutions
for Async Web Server os Secure Web Server. If you can do that with the
instruction above, please provide me the pull request!
# Migration guide to v3.0.0
In v3.0.0 some changes were introduced, that are not backward
compatible with v2.x.x versions.
This guide contains all modifications that should be done in existing
codes, to reflect the changes.
For better understanding some code examples are also shown here, but
I would recommend comparing git changes in the examples.
## Changes introduced in v3.0.0
- [Namespaces](#namespaces)
- [Parameter classes](#parameter-classes)
- [Parameter grouping](#grouping-parameters)
- [Default value handling](#default-value-handling)
- [Hidden parameters](#hidden-parameters)
- [UpdateServer changes](#updateserver-changes)
- [configSave](#configsave)
- [formValidator](#formvalidator)
## Namespaces
With v3.0.0, IotWebConf library started to use namespaces. Namespace
is a C++ technique, where to goal is to avoid name collision over
different libraries.
The namespace for IotWebConf become ```iotwebconf::```. From now on
you should use this prefix for each type defined by the library
except for the IotWebConf class itself.
There are more ways to update your code. Let's see some variations!
### Migration steps: easy way
For easy migration IotWebConf has provided a header file prepared
with predefined aliases to hide namespaces, so you can still use
the legacy types.
Include helper header file as follows.
Code before:
```C++
#include <IotWebConf.h>
```
Code after:
```C++
#include <IotWebConf.h>
#include <IotWebConfUsing.h>
```
### Migration steps: proper way
Use namespace prefixes before every type name.
Code before:
```C++
IotWebConfParameter mqttServerParam =
IotWebConfParameter("MQTT server", "mqttServer", mqttServerValue, STRING_LEN);
```
Code after:
```C++
iotwebconf::Parameter mqttServerParam =
iotwebconf::Parameter("MQTT server", "mqttServer", mqttServerValue, STRING_LEN);
```
### Migration steps: optimist way
Define namespaces at the beginning of the code and use simple type name.
Everywhere later on. This works only until name-collision with other
library occur.
Code after:
```C++
using namespace iotwebconf;
...
Parameter mqttServerParam =
Parameter("MQTT server", "mqttServer", mqttServerValue, STRING_LEN);
```
## Parameter classes
Previously there was just the ```IotWebConfParameter``` and the
actual type was provided as an argument of this one-and-only type.
Now it turned out, that it is a better idea to use specific classes
for each individual types. So from now on you must specify the type
of the parameter by creating that very type e.g. using
```IotWebConfTextParameter```.
For compatibility reasons the signature is the same before, except
the type string should not be provided anymore.
New parameter types are also introduced (e.g.
```IotWebConfSelectParameter```),
and it is very likely that with newer versions, more and more types will
arrive.
Creating your custom parameter is now become much more easy as well.
### Migrations steps
Replace IotWebConfParameter types with specific parameter type.
Code before:
```C++
IotWebConfParameter mqttServerParam =
IotWebConfParameter("MQTT server", "mqttServer", mqttServerValue , STRING_LEN);
IotWebConfParameter mqttUserPasswordParam =
IotWebConfParameter("MQTT password", "mqttPass", mqttUserPasswordValue , STRING_LEN, "password");
```
Code after:
```C++
IotWebConfTextParameter mqttServerParam =
IotWebConfTextParameter("MQTT server", "mqttServer", mqttServerValue , STRING_LEN);
IotWebConfPasswordParameter mqttUserPasswordParam =
IotWebConfPasswordParameter("MQTT password", "mqttPass ", mqttUserPasswordValue, STRING_LEN);
```
Note, that ```IotWebConfTextParameter``` and
```IotWebConfPasswordParameter``` words are just aliases and eventually you
should use ```iotwebconf::TextParameter```,
```iotwebconf::PasswordParameter```, etc.
_Note, that with version 3.0.0 a new typed parameter approach is introduced,
you might want to immediately migrate to this parameter types, but
typed-parameters are still in testing phase and might be a subject of
change._
## Grouping parameters
With v3.0.0 "separator" disappears. Separators were used to create
field sets in the rendered HTML. Now you must directly define connected
items by adding them to specific parameter groups.
(It is also possible to add a group within a group.)
You need to add prepared groups to IotWebConf instead of individual
parameters. (However there is a specific group created by IotWebConf for
storing system parameters, you can also add your
properties into the system group.)
Code before:
```C++
IotWebConfSeparator separator1 =
IotWebConfSeparator();
IotWebConfParameter intParam =
IotWebConfParameter("Int param", "intParam", intParamValue, NUMBER_LEN, "number",
"1..100", nullptr, "min='1' max='100' step='1'");
...
void setup()
{
...
iotWebConf.addParameter(&separator1);
iotWebConf.addParameter(&intParam);
...
```
Code after:
```C++
IotWebConfParameterGroup group1 =
IotWebConfParameterGroup("group1", "");
IotWebConfNumberParameter intParam =
IotWebConfNumberParameter("Int param", "intParam", intParamValue, NUMBER_LEN,
"20", "1..100", "min='1' max='100' step='1'");
...
void setup()
{
...
group1.addItem(&intParam);
...
iotWebConf.addParameterGroup(&group1);
...
```
Also note, that ```IotWebConfParameterGroup``` and
```IotWebConfNumberParameter``` words are just aliases and eventually you
should use ```iotwebconf::ParameterGroup```,
```iotwebconf::NumberParameter```, etc.
## Default value handling
For the Parameters you could always specify "defaultValue". In v2.x
.x this value was intended to be appeared in the config portal, if no
values are specified. Now with v3.0.0, defaultValue has a different
meaning. Now it is
automatically assigned to the parameter, when this is the **first
time** configuration is loading.
This means you do not have to set these values manually.
In the example below, the body of the ```if``` is done by IotWebConf
automatically.
```
// -- Initializing the configuration.
bool validConfig = iotWebConf.init();
if (!validConfig)
{
// DO NOT DO THIS! Use default values instead.
strncpy(mqttServerValue, "192.168.1.10", STRING_LEN);
}
```
## Hidden parameters
IotWebConf can save and load parameters, that are not populated to the
web interface. To mark an item as hidden, you should have set the last
parameter of the constructor to visible=false.
From v3.0.0, you will need to add hidden items to a specific group managed
by IotWebConf.
```
iotWebConf.addHiddenParameter(&myHiddenParameter);
```
## UpdateServer changes
In prior versions, IotWebConf activated HTTP Update server automatically.
With version 3.0.0, IotWebConf dropped the dependency to UpdateServer. The
activation will still be triggered, but the actual switching action
should be provided externally (at your code).
A quite complicated code needs to introduced because of this change, and
you need to manually include UpdateServer to your code. See example:
```IotWebConf04UpdateServer``` for details!
Changed lines:
```
// Include Update server
#ifdef ESP8266
# include <ESP8266HTTPUpdateServer.h>
#elif defined(ESP32)
# include <IotWebConfESP32HTTPUpdateServer.h>
#endif
// Create Update Server
#ifdef ESP8266
ESP8266HTTPUpdateServer httpUpdater;
#elif defined(ESP32)
HTTPUpdateServer httpUpdater;
#endif
// In setup register callbacks performing Update Server hooks.
iotWebConf.setupUpdateServer(
[](const char* updatePath) { httpUpdater.setup(&server, updatePath); },
[](const char* userName, char* password) { httpUpdater.updateCredentials(userName, password); });
```
Note, that ESP32 still doesn't provide Update Server solution out of the
box. IotWebConf still provides an implementation for that, but it is now
completely independent of the core codes.
## configSave
Method configSave is renamed to saveConfig.
## formValidator
The formValidator() methods from now on will have a
```webRequestWrapper``` parameter.
```
bool formValidator(iotwebconf::WebRequestWrapper* webRequestWrapper);
```
\ No newline at end of file
@startuml
/' Objects '/
namespace iotwebconf {
class BoolDataType {
+BoolDataType()
#fromString() : bool
}
class CharArrayDataType <len> {
+CharArrayDataType()
#update() : bool
#getInputLength() : int
#applyDefaultValue() : void
#loadValue() : void
#storeValue() : void
}
class CheckboxTParameter {
+CheckboxTParameter()
#renderHtml() : String
+isChecked() : bool
#getInputType() : char*
-_checkedStr : const char*
#update() : void
}
abstract class ConfigItemBridge {
#ConfigItemBridge()
#{abstract} toString() : String
#{abstract} update() : bool
#getInputLength() : int
+debugTo() : void
+update() : void
}
abstract class DataType <ValueType, (_DefaultValueType)> {
+DataType()
#toString() : String
#_value : ValueType
+getValue() : ValueType&
+operator*() : ValueType&
#_defaultValue : _DefaultValueType
#{abstract} update() : bool
#validate() : bool
#getStorageSize() : int
}
class DoubleDataType {
+DoubleDataType()
#fromString() : double
}
class FloatDataType {
+FloatDataType()
#fromString() : float
}
class FloatTParameter {
+FloatTParameter()
+isMaxDefined() : bool
+isMinDefined() : bool
#getInputType() : char*
+getMax() : float
+getMin() : float
}
abstract class InputParameter {
+InputParameter()
+getCustomHtml() : String
#getHtmlTemplate() : String
#renderHtml() : String
#{abstract} getInputType() : char*
+customHtml : const char*
+errorMessage : const char*
+label : const char*
+placeholder : const char*
#clearErrorMessage() : void
+renderHtml() : void
+setPlaceholder() : void
}
class IntTParameter <ValueType, (base)> {
+IntTParameter()
+getMax() : ValueType
+getMin() : ValueType
+isMaxDefined() : bool
+isMinDefined() : bool
#getInputType() : char*
}
class IpDataType {
#toString() : String
#update() : bool
}
class OptionsTParameter <len> {
+OptionsTParameter()
#OptionsTParameter()
#_optionNames : const char*
#_optionValues : const char*
#_nameLength : size_t
#_optionCount : size_t
+setNameLength() : void
+setOptionCount() : void
+setOptionNames() : void
+setOptionValues() : void
}
class PasswordTParameter <len> {
+PasswordTParameter()
#renderHtml() : String
+update() : bool
#getInputType() : char*
-_customHtmlPwd : const char*
+debugTo() : void
}
abstract class PrimitiveDataType <ValueType> {
+PrimitiveDataType()
-_max : ValueType
-_min : ValueType
#{abstract} fromString() : ValueType
#getMax() : ValueType
#getMin() : ValueType
#isMaxDefined() : ValueType
#isMinDefined() : ValueType
-_maxDefined : bool
-_minDefined : bool
#update() : bool
#applyDefaultValue() : void
#loadValue() : void
+setMax() : void
+setMin() : void
#storeValue() : void
}
abstract class PrimitiveInputParameter <ValueType> {
+PrimitiveInputParameter()
+getCustomHtml() : String
+{abstract} getMax() : ValueType
+{abstract} getMin() : ValueType
+step : ValueType
+{abstract} isMaxDefined() : bool
+{abstract} isMinDefined() : bool
+setStep() : void
}
class SelectTParameter <len> {
+SelectTParameter()
+SelectTParameter()
#renderHtml() : String
}
class SignedIntDataType <ValueType, (base)> {
+SignedIntDataType()
#fromString() : ValueType
}
class StringDataType {
#toString() : String
#update() : bool
}
class TextTParameter <len> {
+TextTParameter()
#getInputType() : char*
}
class UnsignedIntDataType <ValueType, (base)> {
+UnsignedIntDataType()
#fromString() : ValueType
}
}
/' Inheritance relationships '/
iotwebconf.BoolDataType <|-- iotwebconf.CheckboxTParameter
iotwebconf.CharArrayDataType <|-- iotwebconf.PasswordTParameter
iotwebconf.CharArrayDataType <|-- iotwebconf.TextTParameter
iotwebconf.ConfigItemBridge <|-- iotwebconf.DataType
iotwebconf.ConfigItemBridge <|-- iotwebconf.InputParameter
iotwebconf.DataType <|-- iotwebconf.CharArrayDataType
iotwebconf.DataType <|-- iotwebconf.IpDataType
iotwebconf.DataType <|-- iotwebconf.PrimitiveDataType
iotwebconf.DataType <|-- iotwebconf.StringDataType
iotwebconf.FloatDataType <|-- iotwebconf.FloatTParameter
iotwebconf.InputParameter <|-- iotwebconf.CheckboxTParameter
iotwebconf.InputParameter <|-- iotwebconf.PasswordTParameter
iotwebconf.InputParameter <|-- iotwebconf.PrimitiveInputParameter
iotwebconf.InputParameter <|-- iotwebconf.TextTParameter
iotwebconf.OptionsTParameter <|-- iotwebconf.SelectTParameter
iotwebconf.PrimitiveDataType <|-- iotwebconf.BoolDataType
iotwebconf.PrimitiveDataType <|-- iotwebconf.DoubleDataType
iotwebconf.PrimitiveDataType <|-- iotwebconf.FloatDataType
iotwebconf.PrimitiveDataType <|-- iotwebconf.SignedIntDataType
iotwebconf.PrimitiveDataType <|-- iotwebconf.UnsignedIntDataType
iotwebconf.PrimitiveInputParameter <|-- iotwebconf.FloatTParameter
iotwebconf.PrimitiveInputParameter <|-- iotwebconf.IntTParameter
iotwebconf.SignedIntDataType <|-- iotwebconf.IntTParameter
iotwebconf.TextTParameter <|-- iotwebconf.OptionsTParameter
/' Aggregation relationships '/
/' Nested objects '/
@enduml
# IotWebConf users manual template
This documentation is mainly for makers are about to came out with their
own product, and prepare for that with a Users manual.
Or for ones who are want to understand the basic functionality of
IotWebConf.
This document can be used as a template, modify it with the actual
product specific details. We will only cover the basic functionality
(including status and reset), but will __not__ talk about options like:
- Firmware update (OTA),
- Static IP option,
- Multiply WiFi option,
- Offline mode option,
- Skip AP-mode option.
#### Hardware setup
The document assumes, that the device is equipped with
the indicator LED and the "reset" button, just as it can be seen
in example `IotWebConf02StatusAndReset`.
As mentioned above a "default" options-set is assumed as well.
## Starting up the device at the first time
When you are starting up the device for the first time, the device
will create its own WiFi Access Point with SSID _testThing_.
The status indicator rapidly flashes (it is mostly on).
Use your smartphone (or tablet, or computer) to detect the created
WiFi network, and connect to it by using the default password
_smrtThng8266_. After a successful WiFi connection a configuration
page is popped up in your smartphone's web browser.
Note, that at this point even if the network is not configured,
the device is already ready to use with the factory defaults in
an off-line manner.
## Configuration page (Config portal)
After you have connected to the access point created by the
device (as described above), you need to enter the configuration
page on the web-browser of your smartphone.
On the configuration page you will see some fields you can
set.
Except for the password fields, you will see the item values
previously set. (Or for first time setup, the factory default.)
For the password fields you will never see any previously set
values, and typed values are also hidden. You can __reveal the
password__ text you have typed by double-clicking (double-tapping) on
the password field. You can then double-click (double-tap)
a second time to hide the text again. It is recommended to
hide the passwords before submitting the configuration form,
as browsers are likely to save non-password form values to
use them as recommendation.
_TODO: you can provide some more description on specific
fields you are about to use._
When you are finished providing all the values of your need,
please press the Apply button.
Some fields are protected with constraints, and a validation is
performed. In case there is an error in the validation of
any field none of them are saved. You even need to re-type values
for filled-out passwords in this case.
## Configuration options
After the first boot, there are some values needs to be set up.
These items are maked with __*__ (star) in the list below.
You can set up the following values in the configuration page:
- __Thing name__ - Please change the name of the device to
a name you think describes it the most. It is advised to
incorporate a location here in case you are planning to
set up multiple devices in the same area. You should only use
english letters, and the "_" underscore character. Thus, must not
use Space, dots, etc. E.g. `lamp_livingroom` __*__
- __AP password__ - This password is used, when you want to
access the device later on. You must provide a password with at least 8,
at most 32 characters.
You are free to use any characters, further more you are
encouraged to pick a password at least 12 characters long containing
at least 3 character classes. __*__
- __WiFi SSID__ - The name of the WiFi network you want the device
to connect to. __*__
- __WiFi password__ - The password of the network above. Note, that
unsecured passwords are not supported in your protection. __*__
_TODO: add your own configuration here_
Note, that in "First boot" mode you are free to save any
partial data, but on-line mode will not enter until you have
provided all mandatory data.
## Connecting to a WiFi network
When you have successfully applied the mandatory configurations,
the device will try to connect to the required WiFi network. While
connecting, the indicator LED will alter between On/Off in a moderated
speed.
If the WiFi connection is successfull, the indicator LED will turn off,
and performes rapid blinks with long delays.
If the WiFi connection fails, the device will __fall back to Access Point
mode__. This means, that the device will form its own WiFi network, on
what you can connect to, and correct network connection setup.
This time you will see the `Thing Name` value as the access point name
(SSID),
and you need to use the `AP password` you have configured previously.
This also means, that if the configured WiFi network is not available,
the device will fall back to Access Point mode, stays there for some
seconds (so that you can perform changes if needed), and after some
seconds it will __retry connecting to the WiFi network__.
The Access Point mode is indicated with rapid blinks, where the indicator
LED is mostly on.
Access Point mode will kept as long as any connection is alive to it.
Thus, you need to disconnect from the Access Point for the device to
continue its operation.
## Second startup and rescue mode
In case you have already set up a WiFi network in the config portal,
the device will automatically tries to (re)connect to it. However,
when the device boots up it will __always starts the Access Point
mode__ as described in previous section.
In case you have __lost the password__ you have configured, you need to
enter the rescue mode. To __enter the rescue mode__, you need to press
and hold the button on the device while powering it up. This time you
can enter the Access Point provided by the device with the factory-default
password, that is _smrtThng8266_. The rescue mode will not be release
until you have connected to the access point, it will be
released after you have disconnected from it.
## Configuration in connected mode
After the device successfully connected to the configured WiFi network
the temporary Access Point is terminated, but you can still connect
to the device using its IP address. To determine the IP address of the
device, you might want to consult with your WiFi router. (Devices
are intented to be access by name as well, but this option is not
reliable, thus, it cannot be recommended.)
When you want to access the Config Portal of the device __via a WiFi
network__ from your Web Browser, a login page will be displayed, where
you need to enter:
- User name: `admin`
- Password: the password you have set up previously as __AP Password__.
#### Security notes connecting from WiFi network
While WiFi networks are known to be relatively safe by hiding its trafic
from the public, it is not safe between parties connected to the network.
And our device does not support secure Web connection.
This means, when enter the configuration page via WiFi network, other
parties can monitor your traffic.
Recommendations to avoid compromisation:
- Try to make your configurations in Access Point mode,
- Set up a dedicated WiFi network to your IOT devices, where uncertain
parties are not allowed to connect.
## Blinking codes
Prevoius chapters were mentioned blinking patterns, now here is a
table summarize the menaning of the blink codes.
- __Rapid blinking__ (mostly on, interrupted by short off periods) -
Entered Access Point mode. This means the device create an own WiFi
network around it. You can connect to the device with your smartphone
(or WiFi capable computer).
- __Alternating on/off blinking__ - Trying to connect the configured
WiFi network.
- __Mostly off with occasional short flash__ - The device is online.
## Troubleshooting
>I have turned on my device, but it blinks like crazy. What should I do?
- Diagnose: Your device is not configured.
- Solution:
You need to turn on your smartphone, search for WiFi networks, and connect
to `testThing`. Follow the instruction at
[Starting up the device at the first time](#starting-up-the-device-at-the-first-time)
>After I start up the device, the device just blinks as crazy and
>later chills. But while it is blinking I cannot connect to it. Is this
>intended?
- Diagnose: This is an expected behaviour. At startup time you are able
connect directly
to the device directly via a temporary created access point to perform some
configuration changes. (The idea here, is that you can change WiFi
setting in case it was changed before trying to connect to it.)
>My device is either blinks like crazy, or with an alternating pattern,
>but eventually it does not stop that. Why is that?
- Diagnose: Your device cannot connect to the configured WiFi
network.
Your network setup was changed, you have miss-typed the settings, or
the device is out of the network range.
- Solution: At the time the device rapidly blinks, connect to it with
your smartphone and alter the WiFi configuration (SSID and password).
If this doesn't help try to provide stronger WiFi signal for the device.
> I have forgot the password I have set. What should I do?
- Solution: Turn off your device. Press and hold the button on the
device while powering it up. The device will start up in Access Pont
mode, you can connect to this temporary WiFi network with your
smartphone using the initial password, that is _smrtThng8266_, and
set up a new password for the device.
\ No newline at end of file
# Datatypes (KEYWORD1)
# Methods and Functions (KEYWORD2)
# Constants (LITERAL1)
# IotWebConf.h
WifiAuthInfo KEYWORD1
HtmlFormatProvider KEYWORD1
getHead KEYWORD2
getStyle KEYWORD2
getScript KEYWORD2
getHeadExtension KEYWORD2
getHeadEnd KEYWORD2
getFormStart KEYWORD2
getFormEnd KEYWORD2
getFormSaved KEYWORD2
getEnd KEYWORD2
getUpdate KEYWORD2
getConfigVer KEYWORD2
StandardWebRequestWrapper KEYWORD1
StandardWebServerWrapper KEYWORD1
WifiParameterGroup KEYWORD1
IotWebConf KEYWORD1
setConfigPin KEYWORD2
setStatusPin KEYWORD2
setupUpdateServer KEYWORD2
init KEYWORD2
doLoop KEYWORD2
handleCaptivePortal KEYWORD2
handleConfig KEYWORD2
handleNotFound KEYWORD2
setWifiConnectionCallback KEYWORD2
setConfigSavingCallback KEYWORD2
setConfigSavedCallback KEYWORD2
setFormValidator KEYWORD2
setApConnectionHandler KEYWORD2
setWifiConnectionHandler KEYWORD2
setWifiConnectionFailedHandler KEYWORD2
addParameterGroup KEYWORD2
addHiddenParameter KEYWORD2
addSystemParameter KEYWORD2
getThingName KEYWORD2
delay KEYWORD2
setWifiConnectionTimeoutMs KEYWORD2
blink KEYWORD2
fineBlink KEYWORD2
stopCustomBlink KEYWORD2
disableBlink KEYWORD2
enableBlink KEYWORD2
isBlinkEnabled KEYWORD2
getState KEYWORD2
setApTimeoutMs KEYWORD2
getApTimeoutMs KEYWORD2
resetWifiAuthInfo KEYWORD2
skipApStartup KEYWORD2
forceApMode KEYWORD2
getSystemParameterGroup KEYWORD2
getThingNameParameter KEYWORD2
getApPasswordParameter KEYWORD2
getWifiParameterGroup KEYWORD2
getWifiSsidParameter KEYWORD2
getWifiPasswordParameter KEYWORD2
getApTimeoutParameter KEYWORD2
saveConfig KEYWORD2
setHtmlFormatProvider KEYWORD2
getHtmlFormatProvider KEYWORD2
#IotWebConfParameter.h
SerializationData KEYWORD1
ConfigItem KEYWORD1
visible KEYWORD2
getId KEYWORD2
IotWebConfParameterGroup KEYWORD1
ParameterGroup KEYWORD1
addItem KEYWORD2
label KEYWORD2
IotWebConfParameter KEYWORD1
Parameter KEYWORD1
label KEYWORD2
valueBuffer KEYWORD2
defaultValue KEYWORD2
errorMessage KEYWORD2
getLength KEYWORD2
IotWebConfTextParameter KEYWORD1
TextParameter KEYWORD1
placeholder KEYWORD2
customHtml KEYWORD2
IotWebConfPasswordParameter KEYWORD1
PasswordParameter KEYWORD1
IotWebConfNumberParameter KEYWORD1
NumberParameter KEYWORD1
IotWebConfCheckboxParameter KEYWORD1
CheckboxParameter KEYWORD1
isChecked KEYWORD2
IotWebConfSelectParameter KEYWORD1
SelectParameter KEYWORD1
#IotWebConfOptionalGroup.h
OptionalGroupHtmlFormatProvider KEYWORD1
OptionalParameterGroup KEYWORD1
ChainedParameterGroup KEYWORD1
setNext KEYWORD2
getNext KEYWORD2
#IotWebConfOptionalGroup.h
ChainedWifiParameterGroup KEYWORD1
MultipleWifiAddition KEYWORD1
name=IotWebConf
version=3.2.0
author=Balazs Kelemen <prampec+arduino@gmail.com>
maintainer=Balazs Kelemen <prampec+arduino@gmail.com>
sentence=ESP8266/ESP32 non-blocking WiFi/AP web configuration.
paragraph=IotWebConf will start up in AP (access point) mode, and provide a config portal for entering WiFi connection and other user-settings. The configuration is persisted in EEPROM. The config portal will stay available after WiFi connection was made. A WiFiManager alternative.
category=Communication
url=https://github.com/prampec/IotWebConf
architectures=esp8266,esp32
includes=IotWebConf.h
/**
* IotWebConf.cpp -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <EEPROM.h>
#include "IotWebConf.h"
#ifdef IOTWEBCONF_CONFIG_USE_MDNS
# ifdef ESP8266
# include <ESP8266mDNS.h>
# elif defined(ESP32)
# include <ESPmDNS.h>
# endif
#endif
#define IOTWEBCONF_STATUS_ENABLED ((this->_statusPin >= 0) && this->_blinkEnabled)
////////////////////////////////////////////////////////////////
namespace iotwebconf
{
IotWebConf::IotWebConf(
const char* defaultThingName, DNSServer* dnsServer, WebServerWrapper* webServerWrapper,
const char* initialApPassword, const char* configVersion)
{
this->_thingNameParameter.defaultValue = defaultThingName;
this->_dnsServer = dnsServer;
this->_webServerWrapper = webServerWrapper;
this->_initialApPassword = initialApPassword;
this->_configVersion = configVersion;
this->_apTimeoutParameter.visible = false;
this->_systemParameters.addItem(&this->_thingNameParameter);
this->_systemParameters.addItem(&this->_apPasswordParameter);
this->_systemParameters.addItem(&this->_wifiParameters);
this->_systemParameters.addItem(&this->_apTimeoutParameter);
this->_allParameters.addItem(&this->_systemParameters);
this->_allParameters.addItem(&this->_customParameterGroups);
this->_allParameters.addItem(&this->_hiddenParameters);
this->_wifiAuthInfo = {this->_wifiParameters._wifiSsid, this->_wifiParameters._wifiPassword};
}
char* IotWebConf::getThingName()
{
return this->_thingName;
}
void IotWebConf::setConfigPin(int configPin)
{
this->_configPin = configPin;
}
void IotWebConf::setStatusPin(int statusPin, int statusOnLevel)
{
this->_statusPin = statusPin;
this->_statusOnLevel = statusOnLevel;
}
bool IotWebConf::init()
{
// -- Setup pins.
if (this->_configPin >= 0)
{
pinMode(this->_configPin, INPUT_PULLUP);
this->_forceDefaultPassword = (digitalRead(this->_configPin) == LOW);
}
if (IOTWEBCONF_STATUS_ENABLED)
{
pinMode(this->_statusPin, OUTPUT);
digitalWrite(this->_statusPin, !this->_statusOnLevel);
}
// -- Load configuration from EEPROM.
bool validConfig = this->loadConfig();
if (!validConfig)
{
// -- No config
this->_apPassword[0] = '\0';
this->_wifiParameters._wifiSsid[0] = '\0';
this->_wifiParameters._wifiPassword[0] = '\0';
}
this->_apTimeoutMs = atoi(this->_apTimeoutStr) * 1000;
// -- Setup mdns
#ifdef ESP8266
WiFi.hostname(this->_thingName);
#elif defined(ESP32)
WiFi.setHostname(this->_thingName);
#endif
#ifdef IOTWEBCONF_CONFIG_USE_MDNS
MDNS.begin(this->_thingName);
MDNS.addService("http", "tcp", IOTWEBCONF_CONFIG_USE_MDNS);
#endif
return validConfig;
}
//////////////////////////////////////////////////////////////////
void IotWebConf::addParameterGroup(ParameterGroup* group)
{
this->_customParameterGroups.addItem(group);
}
void IotWebConf::addHiddenParameter(ConfigItem* parameter)
{
this->_hiddenParameters.addItem(parameter);
}
void IotWebConf::addSystemParameter(ConfigItem* parameter)
{
this->_systemParameters.addItem(parameter);
}
int IotWebConf::initConfig()
{
int size = this->_allParameters.getStorageSize();
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("Config version: ");
Serial.println(this->_configVersion);
Serial.print("Config size: ");
Serial.println(size);
#endif
return size;
}
/**
* Load the configuration from the eeprom.
*/
bool IotWebConf::loadConfig()
{
int size = this->initConfig();
EEPROM.begin(
IOTWEBCONF_CONFIG_START + IOTWEBCONF_CONFIG_VERSION_LENGTH + size);
bool result;
if (this->testConfigVersion())
{
int start = IOTWEBCONF_CONFIG_START + IOTWEBCONF_CONFIG_VERSION_LENGTH;
IOTWEBCONF_DEBUG_LINE(F("Loading configurations"));
this->_allParameters.loadValue([&](SerializationData* serializationData)
{
this->readEepromValue(start, serializationData->data, serializationData->length);
start += serializationData->length;
});
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
this->_allParameters.debugTo(&Serial);
#endif
result = true;
}
else
{
IOTWEBCONF_DEBUG_LINE(F("Wrong config version. Applying defaults."));
this->_allParameters.applyDefaultValue();
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
this->_allParameters.debugTo(&Serial);
#endif
result = false;
}
EEPROM.end();
return result;
}
void IotWebConf::saveConfig()
{
int size = this->initConfig();
if (this->_configSavingCallback != nullptr)
{
this->_configSavingCallback(size);
}
EEPROM.begin(
IOTWEBCONF_CONFIG_START + IOTWEBCONF_CONFIG_VERSION_LENGTH + size);
this->saveConfigVersion();
int start = IOTWEBCONF_CONFIG_START + IOTWEBCONF_CONFIG_VERSION_LENGTH;
IOTWEBCONF_DEBUG_LINE(F("Saving configuration"));
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
this->_allParameters.debugTo(&Serial);
Serial.println();
#endif
this->_allParameters.storeValue([&](SerializationData* serializationData)
{
this->writeEepromValue(start, serializationData->data, serializationData->length);
start += serializationData->length;
});
EEPROM.end();
this->_apTimeoutMs = atoi(this->_apTimeoutStr) * 1000;
if (this->_configSavedCallback != nullptr)
{
this->_configSavedCallback();
}
}
void IotWebConf::readEepromValue(int start, byte* valueBuffer, int length)
{
for (int t = 0; t < length; t++)
{
*((char*)valueBuffer + t) = EEPROM.read(start + t);
}
}
void IotWebConf::writeEepromValue(int start, byte* valueBuffer, int length)
{
for (int t = 0; t < length; t++)
{
EEPROM.write(start + t, *((char*)valueBuffer + t));
}
}
bool IotWebConf::testConfigVersion()
{
for (byte t = 0; t < IOTWEBCONF_CONFIG_VERSION_LENGTH; t++)
{
if (EEPROM.read(IOTWEBCONF_CONFIG_START + t) != this->_configVersion[t])
{
return false;
}
}
return true;
}
void IotWebConf::saveConfigVersion()
{
for (byte t = 0; t < IOTWEBCONF_CONFIG_VERSION_LENGTH; t++)
{
EEPROM.write(IOTWEBCONF_CONFIG_START + t, this->_configVersion[t]);
}
}
void IotWebConf::setWifiConnectionCallback(std::function<void()> func)
{
this->_wifiConnectionCallback = func;
}
void IotWebConf::setConfigSavingCallback(std::function<void(int size)> func)
{
this->_configSavingCallback = func;
}
void IotWebConf::setConfigSavedCallback(std::function<void()> func)
{
this->_configSavedCallback = func;
}
void IotWebConf::setFormValidator(
std::function<bool(WebRequestWrapper* webRequestWrapper)> func)
{
this->_formValidator = func;
}
void IotWebConf::setWifiConnectionTimeoutMs(unsigned long millis)
{
this->_wifiConnectionTimeoutMs = millis;
}
////////////////////////////////////////////////////////////////////////////////
void IotWebConf::handleConfig(WebRequestWrapper* webRequestWrapper)
{
if (this->_state == OnLine)
{
// -- Authenticate
if (!webRequestWrapper->authenticate(
IOTWEBCONF_ADMIN_USER_NAME, this->_apPassword))
{
IOTWEBCONF_DEBUG_LINE(F("Requesting authentication."));
webRequestWrapper->requestAuthentication();
return;
}
}
bool dataArrived = webRequestWrapper->hasArg("iotSave");
if (!dataArrived || !this->validateForm(webRequestWrapper))
{
// -- Display config portal
IOTWEBCONF_DEBUG_LINE(F("Configuration page requested."));
// Send chunked output instead of one String, to avoid
// filling memory if using many parameters.
webRequestWrapper->sendHeader(
"Cache-Control", "no-cache, no-store, must-revalidate");
webRequestWrapper->sendHeader("Pragma", "no-cache");
webRequestWrapper->sendHeader("Expires", "-1");
webRequestWrapper->setContentLength(CONTENT_LENGTH_UNKNOWN);
webRequestWrapper->send(200, "text/html; charset=UTF-8", "");
String content = htmlFormatProvider->getHead();
content.replace("{v}", "Config ESP");
content += htmlFormatProvider->getScript();
content += htmlFormatProvider->getStyle();
content += htmlFormatProvider->getHeadExtension();
content += htmlFormatProvider->getHeadEnd();
content += htmlFormatProvider->getFormStart();
webRequestWrapper->sendContent(content);
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.println("Rendering parameters:");
this->_systemParameters.debugTo(&Serial);
this->_customParameterGroups.debugTo(&Serial);
#endif
// -- Add parameters to the form
this->_systemParameters.renderHtml(dataArrived, webRequestWrapper);
this->_customParameterGroups.renderHtml(dataArrived, webRequestWrapper);
content = htmlFormatProvider->getFormEnd();
if (this->_updatePath != nullptr)
{
String pitem = htmlFormatProvider->getUpdate();
pitem.replace("{u}", this->_updatePath);
content += pitem;
}
// -- Fill config version string;
{
String pitem = htmlFormatProvider->getConfigVer();
pitem.replace("{v}", this->_configVersion);
content += pitem;
}
content += htmlFormatProvider->getEnd();
webRequestWrapper->sendContent(content);
webRequestWrapper->sendContent(F(""));
webRequestWrapper->stop();
}
else
{
// -- Save config
IOTWEBCONF_DEBUG_LINE(F("Updating configuration"));
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
this->_systemParameters.debugTo(&Serial);
this->_customParameterGroups.debugTo(&Serial);
Serial.println();
#endif
this->_systemParameters.update(webRequestWrapper);
this->_customParameterGroups.update(webRequestWrapper);
this->saveConfig();
String page = htmlFormatProvider->getHead();
page.replace("{v}", "Config ESP");
page += htmlFormatProvider->getScript();
page += htmlFormatProvider->getStyle();
// page += _customHeadElement;
page += htmlFormatProvider->getHeadExtension();
page += htmlFormatProvider->getHeadEnd();
page += "Configuration saved. ";
if (this->_apPassword[0] == '\0')
{
page += F("You must change the default AP password to continue. Return "
"to <a href=''>configuration page</a>.");
}
else if (this->_wifiParameters._wifiSsid[0] == '\0')
{
page += F("You must provide the local wifi settings to continue. Return "
"to <a href=''>configuration page</a>.");
}
else if (this->_state == NotConfigured)
{
page += F("Please disconnect from WiFi AP to continue!");
}
else
{
page += F("Return to <a href='/'>home page</a>.");
}
page += htmlFormatProvider->getEnd();
webRequestWrapper->sendHeader("Content-Length", String(page.length()));
webRequestWrapper->send(200, "text/html; charset=UTF-8", page);
}
}
bool IotWebConf::validateForm(WebRequestWrapper* webRequestWrapper)
{
// -- Clean previous error messages.
this->_systemParameters.clearErrorMessage();
this->_customParameterGroups.clearErrorMessage();
// -- Call external validator.
bool valid = true;
if (this->_formValidator != nullptr)
{
valid = this->_formValidator(webRequestWrapper);
}
// -- Internal validation.
int l = webRequestWrapper->arg(this->_thingNameParameter.getId()).length();
if (3 > l)
{
this->_thingNameParameter.errorMessage =
"Give a name with at least 3 characters.";
valid = false;
}
l = webRequestWrapper->arg(this->_apPasswordParameter.getId()).length();
if ((0 < l) && (l < 8))
{
this->_apPasswordParameter.errorMessage =
"Password length must be at least 8 characters.";
valid = false;
}
l = webRequestWrapper->arg(this->_wifiParameters.wifiPasswordParameter.getId()).length();
if ((0 < l) && (l < 8))
{
this->_wifiParameters.wifiPasswordParameter.errorMessage =
"Password length must be at least 8 characters.";
valid = false;
}
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("Form validation result is: "));
Serial.println(valid ? "positive" : "negative");
#endif
return valid;
}
void IotWebConf::handleNotFound(WebRequestWrapper* webRequestWrapper)
{
if (this->handleCaptivePortal(webRequestWrapper))
{
// If captive portal redirect instead of displaying the error page.
return;
}
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("Requested a non-existing page '"));
Serial.print(webRequestWrapper->uri());
Serial.println("'");
#endif
String message = "Requested a non-existing page\n\n";
message += "URI: ";
message += webRequestWrapper->uri();
message += "\n";
webRequestWrapper->sendHeader(
"Cache-Control", "no-cache, no-store, must-revalidate");
webRequestWrapper->sendHeader("Pragma", "no-cache");
webRequestWrapper->sendHeader("Expires", "-1");
webRequestWrapper->sendHeader("Content-Length", String(message.length()));
webRequestWrapper->send(404, "text/plain", message);
}
/**
* Redirect to captive portal if we got a request for another domain.
* Return true in that case so the page handler do not try to handle the request
* again. (Code from WifiManager project.)
*/
bool IotWebConf::handleCaptivePortal(WebRequestWrapper* webRequestWrapper)
{
String host = webRequestWrapper->hostHeader();
String thingName = String(this->_thingName);
thingName.toLowerCase();
if (!isIp(host) && !host.startsWith(thingName))
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("Request for ");
Serial.print(host);
Serial.print(" redirected to ");
Serial.print(webRequestWrapper->localIP());
Serial.print(":");
Serial.println(webRequestWrapper->localPort());
#endif
webRequestWrapper->sendHeader(
"Location", String("http://") + toStringIp(webRequestWrapper->localIP()) + ":" + webRequestWrapper->localPort(), true);
webRequestWrapper->send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
webRequestWrapper->stop(); // Stop is needed because we sent no content length
return true;
}
return false;
}
/** Is this an IP? */
bool IotWebConf::isIp(String str)
{
for (size_t i = 0; i < str.length(); i++)
{
int c = str.charAt(i);
if (c != '.' && c != ':' && (c < '0' || c > '9'))
{
return false;
}
}
return true;
}
/** IP to String? */
String IotWebConf::toStringIp(IPAddress ip)
{
String res = "";
for (int i = 0; i < 3; i++)
{
res += String((ip >> (8 * i)) & 0xFF) + ".";
}
res += String(((ip >> 8 * 3)) & 0xFF);
return res;
}
/////////////////////////////////////////////////////////////////////////////////
void IotWebConf::delay(unsigned long m)
{
unsigned long delayStart = millis();
while (m > millis() - delayStart)
{
this->doLoop();
// -- Note: 1ms might not be enough to perform a full yield. So
// 'yield' in 'doLoop' is eventually a good idea.
delayMicroseconds(1000);
}
}
void IotWebConf::doLoop()
{
doBlink();
yield(); // -- Yield should not be necessary, but cannot hurt either.
if (this->_state == Boot)
{
// -- After boot, fall immediately to AP mode.
NetworkState startupState = ApMode;
if (this->_startupOffLine)
{
startupState = OffLine;
}
else if (this->_skipApStartup)
{
if (mustStayInApMode())
{
IOTWEBCONF_DEBUG_LINE(
F("SkipApStartup is requested, but either no WiFi was set up, or "
"configButton was pressed."));
}
else
{
// -- Startup state can be WiFi, if it is requested and also possible.
IOTWEBCONF_DEBUG_LINE(F("SkipApStartup mode was applied"));
startupState = Connecting;
}
}
this->changeState(startupState);
}
else if (
(this->_state == NotConfigured) ||
(this->_state == ApMode))
{
// -- We must only leave the AP mode, when no slaves are connected.
// -- Other than that AP mode has a timeout. E.g. after boot, or when retry
// connecting to WiFi
checkConnection();
checkApTimeout();
this->_dnsServer->processNextRequest();
this->_webServerWrapper->handleClient();
}
else if (this->_state == Connecting)
{
if (checkWifiConnection())
{
this->changeState(OnLine);
return;
}
}
else if (this->_state == OnLine)
{
// -- In server mode we provide web interface. And check whether it is time
// to run the client.
this->_webServerWrapper->handleClient();
if (WiFi.status() != WL_CONNECTED)
{
IOTWEBCONF_DEBUG_LINE(F("Not connected. Try reconnect..."));
this->changeState(Connecting);
return;
}
}
}
/**
* What happens, when a state changed...
*/
void IotWebConf::changeState(NetworkState newState)
{
switch (newState)
{
case ApMode:
{
// -- In AP mode we must override the default AP password. Otherwise we stay
// in STATE_NOT_CONFIGURED.
if (mustUseDefaultPassword())
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
if (this->_forceDefaultPassword)
{
Serial.println("AP mode forced by reset pin");
}
else
{
Serial.println("AP password was not set in configuration");
}
#endif
newState = NotConfigured;
}
break;
}
default:
break;
}
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("State changing from: ");
Serial.print(this->_state);
Serial.print(" to ");
Serial.println(newState);
#endif
NetworkState oldState = this->_state;
this->_state = newState;
this->stateChanged(oldState, newState);
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("State changed from: ");
Serial.print(oldState);
Serial.print(" to ");
Serial.println(newState);
#endif
}
/**
* What happens, when a state changed...
*/
void IotWebConf::stateChanged(NetworkState oldState, NetworkState newState)
{
// updateOutput();
switch (newState)
{
case OffLine:
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
this->blinkInternal(22000, 6);
break;
case ApMode:
case NotConfigured:
if (newState == ApMode)
{
this->blinkInternal(300, 90);
}
else
{
this->blinkInternal(300, 50);
}
if ((oldState == Connecting) ||
(oldState == OnLine))
{
WiFi.disconnect(true);
}
setupAp();
if (this->_updateServerSetupFunction != nullptr)
{
this->_updateServerSetupFunction(this->_updatePath);
}
this->_webServerWrapper->begin();
this->_apConnectionState = NoConnections;
this->_apStartTimeMs = millis();
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
if (mustStayInApMode())
{
if (this->_forceDefaultPassword)
{
Serial.println(F("Default password was forced."));
}
if (this->_apPassword[0] == '\0')
{
Serial.println(F("AP password was not set."));
}
if (this->_wifiParameters._wifiSsid[0] == '\0')
{
Serial.println(F("WiFi SSID was not set."));
}
if (this->_forceApMode)
{
Serial.println(F("AP was forced."));
}
Serial.println(F("Will stay in AP mode."));
}
else
{
Serial.print(F("AP timeout (ms): "));
Serial.println(this->_apTimeoutMs);
}
#endif
break;
case Connecting:
if ((oldState == ApMode) ||
(oldState == NotConfigured))
{
stopAp();
}
if ((oldState == Boot) && (this->_updateServerSetupFunction != nullptr))
{
// We've skipped AP mode, so update server needs to be set up now.
this->_updateServerSetupFunction(this->_updatePath);
}
this->blinkInternal(1000, 50);
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("Connecting to [");
Serial.print(this->_wifiAuthInfo.ssid);
# ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
Serial.print("] with password [");
Serial.print(this->_wifiAuthInfo.password);
Serial.println("]");
# else
Serial.println(F("] (password is hidden)"));
# endif
Serial.print(F("WiFi timeout (ms): "));
Serial.println(this->_wifiConnectionTimeoutMs);
#endif
this->_wifiConnectionStart = millis();
WiFi.mode(WIFI_STA);
this->_wifiConnectionHandler(
this->_wifiAuthInfo.ssid, this->_wifiAuthInfo.password);
break;
case OnLine:
this->blinkInternal(8000, 2);
if (this->_updateServerUpdateCredentialsFunction != nullptr)
{
this->_updateServerUpdateCredentialsFunction(
IOTWEBCONF_ADMIN_USER_NAME, this->_apPassword);
}
this->_webServerWrapper->begin();
IOTWEBCONF_DEBUG_LINE(F("Accepting connection"));
if (this->_wifiConnectionCallback != nullptr)
{
this->_wifiConnectionCallback();
}
break;
default:
break;
}
}
void IotWebConf::checkApTimeout()
{
if ( !mustStayInApMode() )
{
// -- Only move on, when we have a valid WifF and AP configured.
if ((this->_apConnectionState == Disconnected) ||
(((millis() - this->_apStartTimeMs) > this->_apTimeoutMs) &&
(this->_apConnectionState != HasConnection)))
{
this->changeState(Connecting);
}
}
}
void IotWebConf::goOnLine(bool apMode)
{
if (this->_state != OffLine)
{
IOTWEBCONF_DEBUG_LINE(F("Requested OnLine mode, but was not offline."));
return;
}
if (apMode || mustStayInApMode())
{
this->changeState(ApMode);
}
else
{
this->changeState(Connecting);
}
}
/**
* Checks whether we have anyone joined to our AP.
* If so, we must not change state. But when our guest leaved, we can
* immediately move on.
*/
void IotWebConf::checkConnection()
{
if ((this->_apConnectionState == NoConnections) &&
(WiFi.softAPgetStationNum() > 0))
{
this->_apConnectionState = HasConnection;
IOTWEBCONF_DEBUG_LINE(F("Connection to AP."));
}
else if (
(this->_apConnectionState == HasConnection) &&
(WiFi.softAPgetStationNum() == 0))
{
this->_apConnectionState = Disconnected;
IOTWEBCONF_DEBUG_LINE(F("Disconnected from AP."));
if (this->_forceDefaultPassword)
{
IOTWEBCONF_DEBUG_LINE(F("Releasing forced AP mode."));
this->_forceDefaultPassword = false;
}
}
}
bool IotWebConf::checkWifiConnection()
{
if (WiFi.status() != WL_CONNECTED)
{
if ((millis() - this->_wifiConnectionStart) > this->_wifiConnectionTimeoutMs)
{
// -- WiFi not available, fall back to AP mode.
IOTWEBCONF_DEBUG_LINE(F("Giving up."));
WiFi.disconnect(true);
WifiAuthInfo* newWifiAuthInfo = _wifiConnectionFailureHandler();
if (newWifiAuthInfo != nullptr)
{
// -- Try connecting with another connection info.
this->_wifiAuthInfo.ssid = newWifiAuthInfo->ssid;
this->_wifiAuthInfo.password = newWifiAuthInfo->password;
this->changeState(Connecting);
}
else
{
this->changeState(ApMode);
}
}
return false;
}
// -- Connected
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
#endif
return true;
}
void IotWebConf::setupAp()
{
WiFi.mode(WIFI_AP);
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("Setting up AP: ");
Serial.println(this->_thingName);
#endif
if (this->_state == NotConfigured)
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("With default password: ");
# ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
Serial.println(this->_initialApPassword);
# else
Serial.println(F("<hidden>"));
# endif
#endif
this->_apConnectionHandler(this->_thingName, this->_initialApPassword);
}
else
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print("Use password: ");
# ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
Serial.println(this->_apPassword);
# else
Serial.println(F("<hidden>"));
# endif
#endif
this->_apConnectionHandler(this->_thingName, this->_apPassword);
}
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("AP IP address: "));
Serial.println(WiFi.softAPIP());
#endif
// delay(500); // Without delay I've seen the IP address blank
// Serial.print(F("AP IP address: "));
// Serial.println(WiFi.softAPIP());
/* Setup the DNS server redirecting all the domains to the apIP */
this->_dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
this->_dnsServer->start(IOTWEBCONF_DNS_PORT, "*", WiFi.softAPIP());
}
void IotWebConf::stopAp()
{
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
}
////////////////////////////////////////////////////////////////////
void IotWebConf::blink(unsigned long repeatMs, byte dutyCyclePercent)
{
if (repeatMs == 0)
{
this->stopCustomBlink();
}
else
{
this->_blinkOnMs = repeatMs * dutyCyclePercent / 100;
this->_blinkOffMs = repeatMs * (100 - dutyCyclePercent) / 100;
}
}
void IotWebConf::fineBlink(unsigned long onMs, unsigned long offMs)
{
this->_blinkOnMs = onMs;
this->_blinkOffMs = offMs;
}
void IotWebConf::stopCustomBlink()
{
this->_blinkOnMs = this->_internalBlinkOnMs;
this->_blinkOffMs = this->_internalBlinkOffMs;
}
void IotWebConf::blinkInternal(unsigned long repeatMs, byte dutyCyclePercent)
{
this->blink(repeatMs, dutyCyclePercent);
this->_internalBlinkOnMs = this->_blinkOnMs;
this->_internalBlinkOffMs = this->_blinkOffMs;
}
void IotWebConf::doBlink()
{
if (IOTWEBCONF_STATUS_ENABLED)
{
unsigned long now = millis();
unsigned long delayMs =
this->_blinkStateOn ? this->_blinkOnMs : this->_blinkOffMs;
if (delayMs < now - this->_lastBlinkTime)
{
this->_blinkStateOn = !this->_blinkStateOn;
this->_lastBlinkTime = now;
digitalWrite(this->_statusPin, this->_blinkStateOn ? this->_statusOnLevel : !this->_statusOnLevel);
}
}
}
void IotWebConf::forceApMode(bool doForce)
{
if (this->_forceApMode == doForce)
{
// Already in the requested mode;
return;
}
this->_forceApMode = doForce;
if (doForce)
{
if (this->_state != ApMode)
{
IOTWEBCONF_DEBUG_LINE(F("Start forcing AP mode"));
this->changeState(ApMode);
}
}
else
{
if (this->_state == ApMode)
{
if (this->mustStayInApMode())
{
IOTWEBCONF_DEBUG_LINE(F("Requested stopping to force AP mode, but we cannot leave the AP mode now."));
}
else
{
IOTWEBCONF_DEBUG_LINE(F("Stopping AP mode force."));
this->changeState(Connecting);
}
}
}
}
bool IotWebConf::connectAp(const char* apName, const char* password)
{
return WiFi.softAP(apName, password);
}
void IotWebConf::connectWifi(const char* ssid, const char* password)
{
WiFi.begin(ssid, password);
}
WifiAuthInfo* IotWebConf::handleConnectWifiFailure()
{
return nullptr;
}
} // end namespace
\ No newline at end of file
/**
* IotWebConf.h -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef IotWebConf_h
#define IotWebConf_h
#include <Arduino.h>
#include <IotWebConfParameter.h>
#include <IotWebConfSettings.h>
#include <IotWebConfWebServerWrapper.h>
#ifdef ESP8266
# include <ESP8266WiFi.h>
# include <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WiFi.h>
# include <WebServer.h>
#endif
#include <DNSServer.h> // -- For captive portal
#ifdef ESP8266
# ifndef WebServer
# define WebServer ESP8266WebServer
# endif
#endif
// -- HTML page fragments
const char IOTWEBCONF_HTML_HEAD[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>\n";
const char IOTWEBCONF_HTML_STYLE_INNER[] PROGMEM = ".de{background-color:#ffaaaa;} .em{font-size:0.8em;color:#bb0000;padding-bottom:0px;} .c{text-align: center;} div,input,select{padding:5px;font-size:1em;} input{width:95%;} select{width:100%} input[type=checkbox]{width:auto;scale:1.5;margin:10px;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#16A1E7;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} fieldset{border-radius:0.3rem;margin: 0px;}\n";
const char IOTWEBCONF_HTML_SCRIPT_INNER[] PROGMEM = "function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}; function pw(id) { var x=document.getElementById(id); if(x.type==='password') {x.type='text';} else {x.type='password';} };";
const char IOTWEBCONF_HTML_HEAD_END[] PROGMEM = "</head><body>";
const char IOTWEBCONF_HTML_BODY_INNER[] PROGMEM = "<div style='text-align:left;display:inline-block;min-width:260px;'>\n";
const char IOTWEBCONF_HTML_FORM_START[] PROGMEM = "<form action='' method='post'><input type='hidden' name='iotSave' value='true'>\n";
const char IOTWEBCONF_HTML_FORM_END[] PROGMEM = "<button type='submit' style='margin-top: 10px;'>Apply</button></form>\n";
const char IOTWEBCONF_HTML_SAVED[] PROGMEM = "<div>Configuration saved<br />Return to <a href='/'>home page</a>.</div>\n";
const char IOTWEBCONF_HTML_END[] PROGMEM = "</div></body></html>";
const char IOTWEBCONF_HTML_UPDATE[] PROGMEM = "<div style='padding-top:25px;'><a href='{u}'>Firmware update</a></div>\n";
const char IOTWEBCONF_HTML_CONFIG_VER[] PROGMEM = "<div style='font-size: .6em;'>Firmware config version '{v}'</div>\n";
// -- User name on login.
#define IOTWEBCONF_ADMIN_USER_NAME "admin"
namespace iotwebconf
{
// -- AP connection state
enum ApConnectionState
{
NoConnections, // -- No connection on AP.
HasConnection, // -- Has connection on AP.
Disconnected // -- All previous connection on AP was disconnected.
};
enum NetworkState
{
Boot,
NotConfigured,
ApMode,
Connecting,
OnLine,
OffLine
};
class IotWebConf;
typedef struct WifiAuthInfo
{
const char* ssid;
const char* password;
} WifiAuthInfo;
/**
* Class for providing HTML format segments.
*/
class HtmlFormatProvider
{
public:
virtual String getHead() { return FPSTR(IOTWEBCONF_HTML_HEAD); }
virtual String getStyle() { return "<style>" + getStyleInner() + "</style>"; }
virtual String getScript() { return "<script>" + getScriptInner() + "</script>"; }
virtual String getHeadExtension() { return ""; }
virtual String getHeadEnd() { return String(FPSTR(IOTWEBCONF_HTML_HEAD_END)) + getBodyInner(); }
virtual String getFormStart() { return FPSTR(IOTWEBCONF_HTML_FORM_START); }
virtual String getFormEnd() { return FPSTR(IOTWEBCONF_HTML_FORM_END); }
virtual String getFormSaved() { return FPSTR(IOTWEBCONF_HTML_SAVED); }
virtual String getEnd() { return FPSTR(IOTWEBCONF_HTML_END); }
virtual String getUpdate() { return FPSTR(IOTWEBCONF_HTML_UPDATE); }
virtual String getConfigVer() { return FPSTR(IOTWEBCONF_HTML_CONFIG_VER); }
protected:
virtual String getStyleInner() { return FPSTR(IOTWEBCONF_HTML_STYLE_INNER); }
virtual String getScriptInner() { return FPSTR(IOTWEBCONF_HTML_SCRIPT_INNER); }
virtual String getBodyInner() { return FPSTR(IOTWEBCONF_HTML_BODY_INNER); }
};
class StandardWebRequestWrapper : public WebRequestWrapper
{
public:
StandardWebRequestWrapper(WebServer* server) { this->_server = server; };
const String hostHeader() const override { return this->_server->hostHeader(); };
IPAddress localIP() override { return this->_server->client().localIP(); };
uint16_t localPort() override { return this->_server->client().localPort(); };
const String uri() const { return this->_server->uri(); };
bool authenticate(const char * username, const char * password) override
{ return this->_server->authenticate(username, password); };
void requestAuthentication() override
{ this->_server->requestAuthentication(); };
bool hasArg(const String& name) override { return this->_server->hasArg(name); };
String arg(const String name) override { return this->_server->arg(name); };
void sendHeader(const String& name, const String& value, bool first = false) override
{ this->_server->sendHeader(name, value, first); };
void setContentLength(const size_t contentLength) override
{ this->_server->setContentLength(contentLength); };
void send(int code, const char* content_type = nullptr, const String& content = String("")) override
{ this->_server->send(code, content_type, content); };
void sendContent(const String& content) override { this->_server->sendContent(content); };
void stop() override { this->_server->client().stop(); };
private:
WebServer* _server;
friend IotWebConf;
};
class StandardWebServerWrapper : public WebServerWrapper
{
public:
StandardWebServerWrapper(WebServer* server) { this->_server = server; };
void handleClient() override { this->_server->handleClient(); };
void begin() override { this->_server->begin(); };
private:
StandardWebServerWrapper() { };
WebServer* _server;
friend IotWebConf;
};
class WifiParameterGroup : public ParameterGroup
{
public:
WifiParameterGroup(const char* id, const char* label = nullptr) : ParameterGroup(id, label)
{
this->addItem(&this->wifiSsidParameter);
this->addItem(&this->wifiPasswordParameter);
}
TextParameter wifiSsidParameter =
TextParameter("WiFi SSID", "iwcWifiSsid", this->_wifiSsid, IOTWEBCONF_WORD_LEN);
PasswordParameter wifiPasswordParameter =
PasswordParameter("WiFi password", "iwcWifiPassword", this->_wifiPassword, IOTWEBCONF_PASSWORD_LEN);
char _wifiSsid[IOTWEBCONF_WORD_LEN];
char _wifiPassword[IOTWEBCONF_PASSWORD_LEN];
};
/**
* Main class of the module.
*/
class IotWebConf
{
public:
/**
* Create a new configuration handler.
* @thingName - Initial value for the thing name. Used in many places like AP name, can be changed by the user.
* @dnsServer - A created DNSServer, that can be configured for captive portal.
* @server - A created web server. Will be started upon connection success.
* @initialApPassword - Initial value for AP mode. Can be changed by the user.
* @configVersion - When the software is updated and the configuration is changing, this key should also be changed,
* so that the config portal will force the user to reenter all the configuration values.
*/
IotWebConf(
const char* thingName, DNSServer* dnsServer, WebServer* server,
const char* initialApPassword, const char* configVersion = "init") :
IotWebConf(thingName, dnsServer, &this->_standardWebServerWrapper, initialApPassword, configVersion)
{
this->_standardWebServerWrapper._server = server;
}
IotWebConf(
const char* thingName, DNSServer* dnsServer, WebServerWrapper* server,
const char* initialApPassword, const char* configVersion = "init");
/**
* Provide an Arduino pin here, that has a button connected to it with the other end of the pin is connected to GND.
* The button pin is queried at for input on boot time (init time).
* If the button was pressed, the thing will enter AP mode with the initial password.
* Must be called before init()!
* @configPin - An Arduino pin. Will be configured as INPUT_PULLUP!
*/
void setConfigPin(int configPin);
/**
* Provide an Arduino pin for status indicator (LOW = on). Blink codes:
* - Rapid blinks - The thing is in AP mode with default password.
* - Rapid blinks, but mostly on - AP mode, waiting for configuration changes.
* - Normal blinks - Connecting to WiFi.
* - Mostly off with rare rapid blinks - WiFi is connected performing normal operation.
* User can also apply custom blinks. See blink() method!
* Must be called before init()!
* @statusPin - An Arduino pin. Will be configured as OUTPUT!
* @statusOnLevel - Logic level of the On state of the status pin. Default is LOW.
*/
void setStatusPin(int statusPin, int statusOnLevel = LOW);
/**
* Add an UpdateServer instance to the system. The firmware update link will appear on the config portal.
* The UpdateServer will be added to the WebServer with the path provided here (or with "firmware",
* if none was provided).
* Login user will be IOTWEBCONF_ADMIN_USER_NAME, password is the password provided in the config portal.
* Should be called before init()!
* @updateServer - An uninitialized UpdateServer instance.
* @updatePath - (Optional) The path to set up the UpdateServer with. Will be also used in the config portal.
*/
void setupUpdateServer(
std::function<void(const char* _updatePath)> setup,
std::function<void(const char* userName, char* password)> updateCredentials,
const char* updatePath = "/firmware")
{
this->_updateServerSetupFunction = setup;
this->_updateServerUpdateCredentialsFunction = updateCredentials;
this->_updatePath = updatePath;
}
/**
* Start up the IotWebConf module.
* Loads all configuration from the EEPROM, and initialize the system.
* Will return false, if no configuration (with specified config version) was found in the EEPROM.
*/
bool init();
/**
* IotWebConf is a non-blocking, state controlled system. Therefor it should be
* regularly triggered from the user code.
* So call this method any time you can.
*/
void doLoop();
/**
* Each WebServer URL handler method should start with calling this method.
* If this method return true, the request was already served by it.
*/
bool handleCaptivePortal(WebRequestWrapper* webRequestWrapper);
bool handleCaptivePortal()
{
StandardWebRequestWrapper webRequestWrapper = StandardWebRequestWrapper(this->_standardWebServerWrapper._server);
return handleCaptivePortal(&webRequestWrapper);
}
/**
* Config URL web request handler. Call this method to handle config request.
*/
void handleConfig(WebRequestWrapper* webRequestWrapper);
void handleConfig()
{
StandardWebRequestWrapper webRequestWrapper = StandardWebRequestWrapper(this->_standardWebServerWrapper._server);
handleConfig(&webRequestWrapper);
}
/**
* URL-not-found web request handler. Used for handling captive portal request.
*/
void handleNotFound(WebRequestWrapper* webRequestWrapper);
void handleNotFound()
{
StandardWebRequestWrapper webRequestWrapper = StandardWebRequestWrapper(this->_standardWebServerWrapper._server);
handleNotFound(&webRequestWrapper);
}
/**
* Specify a callback method, that will be called upon WiFi connection success.
* Should be called before init()!
*/
void setWifiConnectionCallback(std::function<void()> func);
/**
* Specify a callback method, that will be called when settings is being changed.
* This is very handy if you have other routines, that are modifying the "EEPROM"
* parallel to IotWebConf, now this is the time to disable these routines.
* Should be called before init()!
*/
void setConfigSavingCallback(std::function<void(int size)> func);
/**
* Specify a callback method, that will be called when settings have been changed.
* All pending EEPROM manipulations are done by the time this method is called.
* Should be called before init()!
*/
void setConfigSavedCallback(std::function<void()> func);
/**
* Specify a callback method, that will be called when form validation is required.
* If the method will return false, the configuration will not be saved.
* Should be called before init()!
*/
void setFormValidator(std::function<bool(WebRequestWrapper* webRequestWrapper)> func);
/**
* Specify your custom Access Point connection handler. Please use IotWebConf::connectAp() as
* reference when implementing your custom solution.
*/
void setApConnectionHandler(
std::function<bool(const char* apName, const char* password)> func)
{
_apConnectionHandler = func;
}
/**
* Specify your custom WiFi connection handler. Please use IotWebConf::connectWifi() as
* reference when implementing your custom solution.
* Your method will be called when IotWebConf trying to establish
* connection to a WiFi network.
*/
void setWifiConnectionHandler(
std::function<void(const char* ssid, const char* password)> func)
{
_wifiConnectionHandler = func;
}
/**
* With this method you can specify your custom WiFi timeout handler.
* This handler can manage what should happen, when WiFi connection timed out.
* By default the handler implementation returns with nullptr, as seen on reference implementation
* IotWebConf::handleConnectWifiFailure(). This means we need to fall back to AP mode.
* If it method returns with a (new) WiFi settings, it is used as a next try.
* Note, that in case once you have returned with nullptr, you might also want to
* resetWifiAuthInfo(), that sets the auth info used for the next time to the
* one set up in the admin portal.
* Note, that this feature is provided because of the option of providing multiple
* WiFi settings utilized by the MultipleWifiAddition class. (See IotWebConfMultipleWifi.h)
*/
void setWifiConnectionFailedHandler( std::function<WifiAuthInfo*()> func )
{
_wifiConnectionFailureHandler = func;
}
/**
* Add a custom parameter group, that will be handled by the IotWebConf module.
* The parameters in this group will be saved to/loaded from EEPROM automatically,
* and will appear on the config portal.
* Must be called before init()!
*/
void addParameterGroup(ParameterGroup* group);
/**
* Add a custom parameter group, that will be handled by the IotWebConf module.
* The parameters in this group will be saved to/loaded from EEPROM automatically,
* but will NOT appear on the config portal.
* Must be called before init()!
*/
void addHiddenParameter(ConfigItem* parameter);
/**
* Add a custom parameter group, that will be handled by the IotWebConf module.
* The parameters in this group will be saved to/loaded from EEPROM automatically,
* but will NOT appear on the config portal.
* Must be called before init()!
*/
void addSystemParameter(ConfigItem* parameter);
/**
* Getter for the actually configured thing name.
*/
char* getThingName();
/**
* Use this delay, to prevent blocking IotWebConf.
*/
void delay(unsigned long millis);
/**
* IotWebConf tries to connect to the local network for an amount of time before falling back to AP mode.
* The default amount can be updated with this setter.
* Should be called before init()!
*/
void setWifiConnectionTimeoutMs(unsigned long millis);
/**
* Interrupts internal blinking cycle and applies new values for
* blinking the status LED (if one configured with setStatusPin() prior init()
* ).
* @repeatMs - Defines the the period of one on-off cycle in milliseconds.
* @dutyCyclePercent - LED on/off percent. 100 means always on, 0 means
* always off. When called with repeatMs = 0, then internal blink cycle will
* be continued.
*/
void blink(unsigned long repeatMs, byte dutyCyclePercent);
/**
* Similar to blink, but here we define exact on and off times for more
* precise timings.
* @onMs - Milliseconds for the LED turned on.
* @offMs - Milliseconds for the LED turned off.
*/
void fineBlink(unsigned long onMs, unsigned long offMs);
/**
* Stop custom blinking defined by blink() or fineBlink() and continues with
* the internal blink cycle.
*/
void stopCustomBlink();
/**
* Disables blinking, so allows user code to control same LED.
*/
void disableBlink() { this->_blinkEnabled = false; }
/**
* Enables blinking if it has been disabled by disableBlink().
*/
void enableBlink() { this->_blinkEnabled = true; }
/**
* Returns blink enabled state modified by disableBlink() and enableBlink().
*/
bool isBlinkEnabled() { return this->_blinkEnabled; }
/**
* Return the current state.
*/
NetworkState getState() { return this->_state; };
/**
* This method can be used to set the AP timeout directly without modifying the apTimeoutParameter.
* Note, that apTimeoutMs value will be reset to the value of apTimeoutParameter on init and on config save.
*/
void setApTimeoutMs(unsigned long apTimeoutMs)
{
this->_apTimeoutMs = apTimeoutMs;
};
/**
* Returns the actual value of the AP timeout in use.
*/
unsigned long getApTimeoutMs() { return this->_apTimeoutMs; };
/**
* Returns the current WiFi authentication credentials. These are usually the configured ones,
* but might be overwritten by setWifiConnectionFailedHandler().
*/
WifiAuthInfo getWifiAuthInfo() { return _wifiAuthInfo; };
/**
* Resets the authentication credentials for WiFi connection to the configured one.
* With the return value of setWifiConnectionFailedHandler() one can provide alternative connection settings,
* that can be reset with resetWifiAuthInfo().
*/
void resetWifiAuthInfo()
{
_wifiAuthInfo = {this->_wifiParameters._wifiSsid, this->_wifiParameters._wifiPassword};
};
/**
*
*/
void startupOffLine() { this->_startupOffLine = true; }
/**
* By default IotWebConf starts up in AP mode. Calling this method before the init will force IotWebConf
* to connect immediately to the configured WiFi network.
* Note, this method only takes effect, when WiFi mode is enabled, thus when a valid WiFi connection is
* set up, and AP mode is not forced by ConfigPin (see setConfigPin() for details).
*/
void skipApStartup() { this->_skipApStartup = true; }
/**
* By default IotWebConf will continue startup in WiFi mode, when no configuration request arrived
* in AP mode. With this method holding the AP mode can be forced.
* Further more, instant AP mode can forced even when we are currently in WiFi mode.
* @value - When TRUE, AP mode is forced/entered.
* When FALSE, AP mode is released, normal operation will continue.
*/
void forceApMode(bool value);
/**
*
*/
void goOffLine() { this->changeState(OffLine); }
/**
*
*/
void goOnLine(bool apMode = true);
/**
*
*/
unsigned long getApStartTimeMs() { return this->_apStartTimeMs; }
/**
* Get internal parameters, for manual handling.
* Normally you don't need to access these parameters directly.
* Note, that changing valueBuffer of these parameters should be followed by saveConfig()!
*/
ParameterGroup* getRootParameterGroup()
{
return &this->_allParameters;
};
ParameterGroup* getSystemParameterGroup()
{
return &this->_systemParameters;
};
Parameter* getThingNameParameter()
{
return &this->_thingNameParameter;
};
Parameter* getApPasswordParameter()
{
return &this->_apPasswordParameter;
};
WifiParameterGroup* getWifiParameterGroup()
{
return &this->_wifiParameters;
};
Parameter* getWifiSsidParameter()
{
return &this->_wifiParameters.wifiSsidParameter;
};
Parameter* getWifiPasswordParameter()
{
return &this->_wifiParameters.wifiPasswordParameter;
};
Parameter* getApTimeoutParameter()
{
return &this->_apTimeoutParameter;
};
/**
* If config parameters are modified directly, the new values can be saved by this method.
* Note, that init() must pretend saveConfig()!
* Also note, that saveConfig writes to EEPROM, and EEPROM can be written only some thousand times
* in the lifetime of an ESP8266 module.
*/
void saveConfig();
/**
* Loads all configuration from the EEPROM without initializing the system.
* Will return false, if no configuration (with specified config version) was found in the EEPROM.
*/
bool loadConfig();
/**
* With this method you can override the default HTML format provider to
* provide custom HTML segments.
*/
void
setHtmlFormatProvider(HtmlFormatProvider* customHtmlFormatProvider)
{
this->htmlFormatProvider = customHtmlFormatProvider;
}
HtmlFormatProvider* getHtmlFormatProvider()
{
return this->htmlFormatProvider;
}
private:
const char* _initialApPassword = nullptr;
const char* _configVersion;
DNSServer* _dnsServer;
WebServerWrapper* _webServerWrapper;
StandardWebServerWrapper _standardWebServerWrapper = StandardWebServerWrapper();
std::function<void(const char* _updatePath)>
_updateServerSetupFunction = nullptr;
std::function<void(const char* userName, char* password)>
_updateServerUpdateCredentialsFunction = nullptr;
int _configPin = -1;
int _statusPin = -1;
int _statusOnLevel = LOW;
const char* _updatePath = nullptr;
bool _forceDefaultPassword = false;
bool _startupOffLine = false;
bool _skipApStartup = false;
bool _forceApMode = false;
ParameterGroup _allParameters = ParameterGroup("iwcAll");
ParameterGroup _systemParameters = ParameterGroup("iwcSys", "System configuration");
ParameterGroup _customParameterGroups = ParameterGroup("iwcCustom");
ParameterGroup _hiddenParameters = ParameterGroup("hidden");
WifiParameterGroup _wifiParameters = WifiParameterGroup("iwcWifi0");
TextParameter _thingNameParameter =
TextParameter("Thing name", "iwcThingName", this->_thingName, IOTWEBCONF_WORD_LEN);
PasswordParameter _apPasswordParameter =
PasswordParameter("AP password", "iwcApPassword", this->_apPassword, IOTWEBCONF_PASSWORD_LEN);
NumberParameter _apTimeoutParameter =
NumberParameter("Startup delay (seconds)", "iwcApTimeout", this->_apTimeoutStr, IOTWEBCONF_WORD_LEN, IOTWEBCONF_DEFAULT_AP_MODE_TIMEOUT_SECS, nullptr, "min='1' max='600'");
char _thingName[IOTWEBCONF_WORD_LEN];
char _apPassword[IOTWEBCONF_PASSWORD_LEN];
char _apTimeoutStr[IOTWEBCONF_WORD_LEN];
unsigned long _apTimeoutMs;
// TODO: Add to WifiParameterGroup
unsigned long _wifiConnectionTimeoutMs =
IOTWEBCONF_DEFAULT_WIFI_CONNECTION_TIMEOUT_MS;
NetworkState _state = Boot;
unsigned long _apStartTimeMs = 0;
ApConnectionState _apConnectionState = NoConnections;
std::function<void()> _wifiConnectionCallback = nullptr;
std::function<void(int)> _configSavingCallback = nullptr;
std::function<void()> _configSavedCallback = nullptr;
std::function<bool(WebRequestWrapper* webRequestWrapper)> _formValidator = nullptr;
std::function<void(const char*, const char*)> _apConnectionHandler =
&(IotWebConf::connectAp);
std::function<void(const char*, const char*)> _wifiConnectionHandler =
&(IotWebConf::connectWifi);
std::function<WifiAuthInfo*()> _wifiConnectionFailureHandler =
&(IotWebConf::handleConnectWifiFailure);
unsigned long _internalBlinkOnMs = 500;
unsigned long _internalBlinkOffMs = 500;
unsigned long _blinkOnMs = 500;
unsigned long _blinkOffMs = 500;
bool _blinkEnabled = true;
bool _blinkStateOn = false;
unsigned long _lastBlinkTime = 0;
unsigned long _wifiConnectionStart = 0;
// TODO: authinfo
WifiAuthInfo _wifiAuthInfo;
HtmlFormatProvider htmlFormatProviderInstance;
HtmlFormatProvider* htmlFormatProvider = &htmlFormatProviderInstance;
int initConfig();
bool testConfigVersion();
void saveConfigVersion();
void readEepromValue(int start, byte* valueBuffer, int length);
void writeEepromValue(int start, byte* valueBuffer, int length);
bool validateForm(WebRequestWrapper* webRequestWrapper);
void changeState(NetworkState newState);
void stateChanged(NetworkState oldState, NetworkState newState);
bool mustUseDefaultPassword()
{
return this->_forceDefaultPassword || (this->_apPassword[0] == '\0');
}
bool mustStayInApMode()
{
return this->_forceDefaultPassword || (this->_apPassword[0] == '\0') ||
(this->_wifiParameters._wifiSsid[0] == '\0') || this->_forceApMode;
}
bool isIp(String str);
String toStringIp(IPAddress ip);
void doBlink();
void blinkInternal(unsigned long repeatMs, byte dutyCyclePercent);
void checkApTimeout();
void checkConnection();
bool checkWifiConnection();
void setupAp();
void stopAp();
static bool connectAp(const char* apName, const char* password);
static void connectWifi(const char* ssid, const char* password);
static WifiAuthInfo* handleConnectWifiFailure();
};
} // end namespace
using iotwebconf::IotWebConf;
#endif
/**
* IotWebConfESP32HTTPUpdateServer.h -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
* Notes on IotWebConfESP32HTTPUpdateServer:
* ESP32 doesn't implement a HTTPUpdateServer. However it seams, that to code
* from ESP8266 covers nearly all the same functionality.
* So we need to implement our own HTTPUpdateServer for ESP32, and code is
* reused from
* https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPUpdateServer/src/
* version: 41de43a26381d7c9d29ce879dd5d7c027528371b
*/
#ifdef ESP32
#ifndef __HTTP_UPDATE_SERVER_H
#define __HTTP_UPDATE_SERVER_H
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <StreamString.h>
#include <Update.h>
#define emptyString F("")
class WebServer;
class HTTPUpdateServer
{
public:
HTTPUpdateServer(bool serial_debug=false)
{
_serial_output = serial_debug;
_server = nullptr;
_username = emptyString;
_password = emptyString;
_authenticated = false;
}
void setup(WebServer *server)
{
setup(server, emptyString, emptyString);
}
void setup(WebServer *server, const String& path)
{
setup(server, path, emptyString, emptyString);
}
void setup(WebServer *server, const String& username, const String& password)
{
setup(server, "/update", username, password);
}
void setup(WebServer *server, const String& path, const String& username, const String& password)
{
_server = server;
_username = username;
_password = password;
// handler for the /update form page
_server->on(path.c_str(), HTTP_GET, [&](){
if(_username != emptyString && _password != emptyString && !_server->authenticate(_username.c_str(), _password.c_str()))
return _server->requestAuthentication();
_server->send_P(200, PSTR("text/html"), serverIndex);
});
// handler for the /update form POST (once file upload finishes)
_server->on(path.c_str(), HTTP_POST, [&](){
if(!_authenticated)
return _server->requestAuthentication();
if (Update.hasError()) {
_server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError);
} else {
_server->client().setNoDelay(true);
_server->send_P(200, PSTR("text/html"), successResponse);
delay(100);
_server->client().stop();
ESP.restart();
}
},[&](){
// handler for the file upload, get's the sketch bytes, and writes
// them through the Update object
HTTPUpload& upload = _server->upload();
if(upload.status == UPLOAD_FILE_START){
_updaterError = String();
if (_serial_output)
Serial.setDebugOutput(true);
_authenticated = (_username == emptyString || _password == emptyString || _server->authenticate(_username.c_str(), _password.c_str()));
if(!_authenticated){
if (_serial_output)
Serial.printf("Unauthenticated Update\n");
return;
}
/// WiFiUDP::stopAll();
if (_serial_output)
Serial.printf("Update: %s\n", upload.filename.c_str());
/// uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
/// if(!Update.begin(maxSketchSpace)){//start with max available size
if(!Update.begin(UPDATE_SIZE_UNKNOWN)){//start with max available size
_setUpdaterError();
}
} else if(_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()){
if (_serial_output) Serial.printf(".");
if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
_setUpdaterError();
}
} else if(_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()){
if(Update.end(true)){ //true to set the size to the current progress
if (_serial_output) Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
_setUpdaterError();
}
if (_serial_output) Serial.setDebugOutput(false);
} else if(_authenticated && upload.status == UPLOAD_FILE_ABORTED){
Update.end();
if (_serial_output) Serial.println("Update was aborted");
}
delay(0);
});
}
void updateCredentials(const String& username, const String& password)
{
_username = username;
_password = password;
}
protected:
void _setUpdaterError()
{
if (_serial_output) Update.printError(Serial);
StreamString str;
Update.printError(str);
_updaterError = str.c_str();
}
private:
bool _serial_output;
WebServer *_server;
String _username;
String _password;
bool _authenticated;
String _updaterError;
const char* serverIndex PROGMEM =
R"(<html><body><form method='POST' action='' enctype='multipart/form-data'>
<input type='file' name='update'>
<input type='submit' value='Update'>
</form>
</body></html>)";
const char* successResponse PROGMEM =
"<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...\n";
};
/////////////////////////////////////////////////////////////////////////////////
#endif
#endif
/**
* IotWebConfMultipleWifi.cpp -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2021 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "IotWebConfMultipleWifi.h"
namespace iotwebconf
{
MultipleWifiAddition::MultipleWifiAddition(
IotWebConf* iotWebConf,
ChainedWifiParameterGroup sets[],
size_t setsSize)
{
this->_iotWebConf = iotWebConf;
this->_firstSet = &sets[0];
this->_currentSet = &sets[0];
ChainedWifiParameterGroup* set = &sets[0];
for(size_t i=1; i<setsSize; i++)
{
set->setNext(&sets[i]);
set = &sets[i];
}
}
void MultipleWifiAddition::init()
{
// -- Add parameter groups.
ChainedWifiParameterGroup* set = this->_firstSet;
while(set != nullptr)
{
this->_iotWebConf->addSystemParameter(set);
set = (ChainedWifiParameterGroup*)set->getNext();
}
// -- Add custom format provider.
this->_iotWebConf->setHtmlFormatProvider(
&this->_optionalGroupHtmlFormatProvider);
// -- Set up handler, that will selects next connection info to use.
this->_iotWebConf->setFormValidator([&](WebRequestWrapper* webRequestWrapper)
{
return this->formValidator(webRequestWrapper);
});
// -- Set up handler, that will selects next connection info to use.
this->_iotWebConf->setWifiConnectionFailedHandler([&]()
{
WifiAuthInfo* result;
while (true)
{
if (this->_currentSet == nullptr)
{
this->_currentSet = this->_firstSet;
this->_iotWebConf->resetWifiAuthInfo();
result = nullptr;
break;
}
else
{
if (this->_currentSet->isActive())
{
result = &this->_currentSet->wifiAuthInfo;
this->_currentSet =
(iotwebconf::ChainedWifiParameterGroup*)this->_currentSet->getNext();
break;
}
else
{
this->_currentSet =
(iotwebconf::ChainedWifiParameterGroup*)this->_currentSet->getNext();
}
}
}
return result;
});
};
bool MultipleWifiAddition::formValidator(
WebRequestWrapper* webRequestWrapper)
{
ChainedWifiParameterGroup* set = this->_firstSet;
bool valid = true;
while(set != nullptr)
{
if (set->isActive())
{
PasswordParameter* pwdParam = &set->wifiPasswordParameter;
int l = webRequestWrapper->arg(pwdParam->getId()).length();
if ((0 < l) && (l < 8))
{
pwdParam->errorMessage = "Password length must be at least 8 characters.";
valid = false;
}
}
set = (ChainedWifiParameterGroup*)set->getNext();
}
return valid;
};
}
/**
* IotWebConfMultipleWifi.h -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2021 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef IotWebConfMultipleWifi_h
#define IotWebConfMultipleWifi_h
#include "IotWebConfOptionalGroup.h"
#include "IotWebConf.h" // for WebRequestWrapper
namespace iotwebconf
{
class ChainedWifiParameterGroup : public ChainedParameterGroup
{
public:
ChainedWifiParameterGroup(const char* id) : ChainedParameterGroup(id, "WiFi connection")
{
// -- Update parameter Ids to have unique ID for all parameters within the application.
snprintf(this->_wifiSsidParameterId, IOTWEBCONF_WORD_LEN, "%s-ssid", this->getId());
snprintf(this->_wifiPasswordParameterId, IOTWEBCONF_WORD_LEN, "%s-pwd", this->getId());
this->addItem(&this->wifiSsidParameter);
this->addItem(&this->wifiPasswordParameter);
}
TextParameter wifiSsidParameter =
TextParameter("WiFi SSID", this->_wifiSsidParameterId, this->wifiSsid, IOTWEBCONF_WORD_LEN);
PasswordParameter wifiPasswordParameter =
PasswordParameter("WiFi password", this->_wifiPasswordParameterId, this->wifiPassword, IOTWEBCONF_PASSWORD_LEN);
char wifiSsid[IOTWEBCONF_WORD_LEN];
char wifiPassword[IOTWEBCONF_PASSWORD_LEN];
WifiAuthInfo wifiAuthInfo = { wifiSsid, wifiPassword};
protected:
private:
char _wifiSsidParameterId[IOTWEBCONF_WORD_LEN];
char _wifiPasswordParameterId[IOTWEBCONF_WORD_LEN];
};
class MultipleWifiAddition
{
public:
MultipleWifiAddition(
IotWebConf* iotWebConf,
ChainedWifiParameterGroup sets[],
size_t setsSize);
/**
* Note, that init() calls setFormValidator, that overwrites existing
* formValidator setup. Thus your setFormValidator should be called
* _after_ multipleWifiAddition.init() .
*/
virtual void init();
virtual bool formValidator(
WebRequestWrapper* webRequestWrapper);
protected:
IotWebConf* _iotWebConf;
ChainedWifiParameterGroup* _firstSet;
ChainedWifiParameterGroup* _currentSet;
iotwebconf::OptionalGroupHtmlFormatProvider _optionalGroupHtmlFormatProvider;
};
}
#endif
\ No newline at end of file
/**
* IotWebConfOptionalGroup.cpp -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*s
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "IotWebConfOptionalGroup.h"
namespace iotwebconf
{
OptionalParameterGroup::OptionalParameterGroup(const char* id, const char* label, bool defaultVisible)
: ParameterGroup(id, label)
{
this->_defaultActive = defaultVisible;
}
int OptionalParameterGroup::getStorageSize()
{
return ParameterGroup::getStorageSize() + 1;
}
void OptionalParameterGroup::applyDefaultValue()
{
this->_active = this->_defaultActive;
ParameterGroup::applyDefaultValue();
}
void OptionalParameterGroup::storeValue(
std::function<void(SerializationData* serializationData)> doStore)
{
// -- Store active flag.
byte data[1];
data[0] = (byte)this->_active;
SerializationData serializationData;
serializationData.length = 1;
serializationData.data = data;
doStore(&serializationData);
// -- Store other items.
ParameterGroup::storeValue(doStore);
}
void OptionalParameterGroup::loadValue(
std::function<void(SerializationData* serializationData)> doLoad)
{
// -- Load activity.
byte data[1];
SerializationData serializationData;
serializationData.length = 1;
serializationData.data = data;
doLoad(&serializationData);
this->_active = (bool)data[0];
// -- Load other items.
ParameterGroup::loadValue(doLoad);
}
void OptionalParameterGroup::renderHtml(
bool dataArrived, WebRequestWrapper* webRequestWrapper)
{
if (this->label != nullptr)
{
String content = getStartTemplate();
content.replace("{b}", this->label);
content.replace("{i}", this->getId());
content.replace("{v}", this->_active ? "active" : "inactive");
if (this->_active)
{
content.replace("{cb}", "hide");
content.replace("{cf}", "");
}
else
{
content.replace("{cb}", "");
content.replace("{cf}", "hide");
}
webRequestWrapper->sendContent(content);
}
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
if (current->visible)
{
current->renderHtml(dataArrived, webRequestWrapper);
}
current = this->getNextItemOf(current);
}
if (this->label != nullptr)
{
String content = getEndTemplate();
content.replace("{b}", this->label);
content.replace("{i}", this->getId());
webRequestWrapper->sendContent(content);
}
}
void OptionalParameterGroup::update(WebRequestWrapper* webRequestWrapper)
{
// -- Get active variable
String activeId = String(this->getId());
activeId += 'v';
if (webRequestWrapper->hasArg(activeId))
{
String activeStr = webRequestWrapper->arg(activeId);
this->_active = activeStr.equals("active");
}
// Update other items.
ParameterGroup::update(webRequestWrapper);
}
void OptionalParameterGroup::debugTo(Stream* out)
{
out->print('(');
out->print(this->_active ? "active" : "inactive");
out->print(')');
// Print rest.
ParameterGroup::debugTo(out);
}
///////////////////////////////////////////////////////////////////////////////
String ChainedParameterGroup::getStartTemplate()
{
String result = OptionalParameterGroup::getStartTemplate();
if ((this->_prevGroup != nullptr) && (!this->_prevGroup->isActive()))
{
result.replace("{cb}", "hide");
}
return result;
};
String ChainedParameterGroup::getEndTemplate()
{
String result;
if (this->_nextGroup == nullptr)
{
result = OptionalParameterGroup::getEndTemplate();
}
else
{
result = FPSTR(IOTWEBCONF_HTML_FORM_CHAINED_GROUP_NEXTID);
result.replace("{in}", this->_nextGroup->getId());
result += OptionalParameterGroup::getEndTemplate();
}
return result;
};
}
\ No newline at end of file
/**
* IotWebConfOptionalGroup.h -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef IotWebConfOptionalGroup_h
#define IotWebConfOptionalGroup_h
#include "IotWebConf.h" // For HtmlFormatProvider ... TODO: should be reorganized
#include "IotWebConfParameter.h"
const char IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_JAVASCRIPT[] PROGMEM =
" function show(id) { var x=document.getElementById(id); x.classList.remove('hide'); }\n"
" function hide(id) { var x=document.getElementById(id); x.classList.add('hide'); }\n"
" function val(id) { var x=document.getElementById(id); return x.value; }\n"
" function setVal(id, val) { var x=document.getElementById(id); x.value = val; }\n"
" function showFs(id) {\n"
" show(id); hide(id + 'b'); setVal(id + 'v', 'active'); var n=document.getElementById(id + 'next');\n"
" if (n) { var nId = n.value; if (val(nId + 'v') == 'inactive') { show(nId + 'b'); }}\n"
" }\n"
" function hideFs(id) {\n"
" hide(id); show(id + 'b'); setVal(id + 'v', 'inactive'); var n=document.getElementById(id + 'next');\n"
" if (n) { var nId = n.value; if (val(nId + 'v') == 'inactive') { hide(nId + 'b'); }}\n"
" }\n";
const char IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_CSS[] PROGMEM =
".hide{display: none;}\n";
const char IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_START[] PROGMEM =
"<button id='{i}b' class='{cb}' onclick=\"showFs('{i}'); return false;\">+ {b}</button>\n"
"<fieldset id='{i}' class='{cf}'><legend>{b}</legend>\n"
"<button onclick=\"hideFs('{i}'); return false;\">Remove this set</button>\n"
"<input id='{i}v' name='{i}v' type='hidden' value='{v}'/>\n"
"\n";
const char IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_END[] PROGMEM =
"</fieldset>\n";
const char IOTWEBCONF_HTML_FORM_CHAINED_GROUP_NEXTID[] PROGMEM =
"<input type='hidden' id='{i}next' value='{in}'/>\n";
namespace iotwebconf
{
class OptionalGroupHtmlFormatProvider : public HtmlFormatProvider
{
protected:
String getScriptInner() override
{
return
HtmlFormatProvider::getScriptInner() +
String(FPSTR(IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_JAVASCRIPT));
}
String getStyleInner() override
{
return
HtmlFormatProvider::getStyleInner() +
String(FPSTR(IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_CSS));
}
};
/**
* With OptionalParameterGroup buttons will appear in the GUI,
* to show and hide this specific group of parameters. The idea
* behind this feature to add/remove optional parameter set
* in the config portal.
*/
class OptionalParameterGroup : public ParameterGroup
{
public:
OptionalParameterGroup(const char* id, const char* label, bool defaultActive);
bool isActive() { return this->_active; }
void setActive(bool active) { this->_active = active; }
protected:
int getStorageSize() override;
void applyDefaultValue() override;
void storeValue(std::function<void(
SerializationData* serializationData)> doStore) override;
void loadValue(std::function<void(
SerializationData* serializationData)> doLoad) override;
void renderHtml(bool dataArrived, WebRequestWrapper* webRequestWrapper) override;
virtual String getStartTemplate() { return FPSTR(IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_START); };
virtual String getEndTemplate() { return FPSTR(IOTWEBCONF_HTML_FORM_OPTIONAL_GROUP_END); };
void update(WebRequestWrapper* webRequestWrapper) override;
void debugTo(Stream* out) override;
private:
bool _defaultActive;
bool _active;
};
class ChainedParameterGroup;
class ChainedParameterGroup : public OptionalParameterGroup
{
public:
ChainedParameterGroup(const char* id, const char* label, bool defaultActive = false) :
OptionalParameterGroup(id, label, defaultActive) { };
void setNext(ChainedParameterGroup* nextGroup) { this->_nextGroup = nextGroup; nextGroup->_prevGroup = this; };
ChainedParameterGroup* getNext() { return this->_nextGroup; };
protected:
virtual String getStartTemplate() override;
virtual String getEndTemplate() override;
protected:
ChainedParameterGroup* _prevGroup = nullptr;
ChainedParameterGroup* _nextGroup = nullptr;
};
}
#endif
\ No newline at end of file
/**
* IotWebConfParameter.cpp -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <IotWebConfParameter.h>
namespace iotwebconf
{
ParameterGroup::ParameterGroup(
const char* id, const char* label) :
ConfigItem(id)
{
this->label = label;
}
void ParameterGroup::addItem(ConfigItem* configItem)
{
if (configItem->_parentItem != nullptr)
{
return; // Item must not be added two times.
}
if (this->_firstItem == nullptr)
{
this->_firstItem = configItem;
return;
}
ConfigItem* current = this->_firstItem;
while (current->_nextItem != nullptr)
{
current = current->_nextItem;
}
current->_nextItem = configItem;
configItem->_parentItem = this;
}
int ParameterGroup::getStorageSize()
{
int size = 0;
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
size += current->getStorageSize();
current = current->_nextItem;
}
return size;
}
void ParameterGroup::applyDefaultValue()
{
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
current->applyDefaultValue();
current = current->_nextItem;
}
}
void ParameterGroup::storeValue(
std::function<void(SerializationData* serializationData)> doStore)
{
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
current->storeValue(doStore);
current = current->_nextItem;
}
}
void ParameterGroup::loadValue(
std::function<void(SerializationData* serializationData)> doLoad)
{
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
current->loadValue(doLoad);
current = current->_nextItem;
}
}
void ParameterGroup::renderHtml(
bool dataArrived, WebRequestWrapper* webRequestWrapper)
{
if (this->label != nullptr)
{
String content = getStartTemplate();
content.replace("{b}", this->label);
content.replace("{i}", this->getId());
webRequestWrapper->sendContent(content);
}
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
if (current->visible)
{
current->renderHtml(dataArrived, webRequestWrapper);
}
current = current->_nextItem;
}
if (this->label != nullptr)
{
String content = getEndTemplate();
content.replace("{b}", this->label);
content.replace("{i}", this->getId());
webRequestWrapper->sendContent(content);
}
}
void ParameterGroup::update(WebRequestWrapper* webRequestWrapper)
{
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
current->update(webRequestWrapper);
current = current->_nextItem;
}
}
void ParameterGroup::clearErrorMessage()
{
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
current->clearErrorMessage();
current = current->_nextItem;
}
}
void ParameterGroup::debugTo(Stream* out)
{
out->print('[');
out->print(this->getId());
out->println(']');
// -- Here is some overcomplicated logic to have nice debug output.
bool ownItem = false;
bool lastItem = false;
PrefixStreamWrapper stream =
PrefixStreamWrapper(
out,
[&](Stream* out1)
{
if (ownItem)
{
ownItem = false;
return (size_t)0;
}
if (lastItem)
{
return out1->print(" ");
}
else
{
return out1->print("| ");
}
});
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
if (current->_nextItem == nullptr)
{
out->print("\\-- ");
}
else
{
out->print("|-- ");
}
ownItem = true;
lastItem = (current->_nextItem == nullptr);
current->debugTo(&stream);
current = current->_nextItem;
}
}
#ifdef IOTWEBCONF_ENABLE_JSON
void ParameterGroup::loadFromJson(JsonObject jsonObject)
{
if (jsonObject.containsKey(this->getId()))
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("Applying values from JSON for groupId: "));
Serial.println(this->getId());
#endif
JsonObject myObject = jsonObject[this->getId()];
ConfigItem* current = this->_firstItem;
while (current != nullptr)
{
current->loadFromJson(myObject);
current = current->_nextItem;
}
}
else
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("Group data not found in JSON. Skipping groupId: "));
Serial.println(this->getId());
#endif
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
Parameter::Parameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue) :
ConfigItem(id)
{
this->label = label;
this->valueBuffer = valueBuffer;
this->_length = length;
this->defaultValue = defaultValue;
this->errorMessage = nullptr;
}
int Parameter::getStorageSize()
{
return this->_length;
}
void Parameter::applyDefaultValue()
{
if (defaultValue != nullptr)
{
strncpy(this->valueBuffer, this->defaultValue, this->getLength());
}
else
{
this->valueBuffer[0] = '\0';
}
}
void Parameter::storeValue(
std::function<void(SerializationData* serializationData)> doStore)
{
SerializationData serializationData;
serializationData.length = this->_length;
serializationData.data = (byte*)this->valueBuffer;
doStore(&serializationData);
}
void Parameter::loadValue(
std::function<void(SerializationData* serializationData)> doLoad)
{
SerializationData serializationData;
serializationData.length = this->_length;
serializationData.data = (byte*)this->valueBuffer;
doLoad(&serializationData);
}
void Parameter::update(WebRequestWrapper* webRequestWrapper)
{
if (webRequestWrapper->hasArg(this->getId()))
{
String newValue = webRequestWrapper->arg(this->getId());
this->update(newValue);
}
}
void Parameter::clearErrorMessage()
{
this->errorMessage = nullptr;
}
#ifdef IOTWEBCONF_ENABLE_JSON
void Parameter::loadFromJson(JsonObject jsonObject)
{
if (jsonObject.containsKey(this->getId()))
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("Applying value from JSON for parameterId: "));
Serial.println(this->getId());
#endif
const char* value = jsonObject[this->getId()];
this->update(String(value));
}
else
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(F("No value found in JSON for parameterId: "));
Serial.println(this->getId());
#endif
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
TextParameter::TextParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue,
const char* placeholder,
const char* customHtml)
: Parameter(label, id, valueBuffer, length, defaultValue)
{
this->placeholder = placeholder;
this->customHtml = customHtml;
}
void TextParameter::renderHtml(
bool dataArrived, WebRequestWrapper* webRequestWrapper)
{
String content = this->renderHtml(
dataArrived,
webRequestWrapper->hasArg(this->getId()),
webRequestWrapper->arg(this->getId()));
webRequestWrapper->sendContent(content);
}
String TextParameter::renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost)
{
return this->renderHtml("text", hasValueFromPost, valueFromPost);
}
String TextParameter::renderHtml(
const char* type, bool hasValueFromPost, String valueFromPost)
{
TextParameter* current = this;
char parLength[12];
String pitem = getHtmlTemplate();
pitem.replace("{b}", current->label);
pitem.replace("{t}", type);
pitem.replace("{i}", current->getId());
pitem.replace("{p}", current->placeholder == nullptr ? "" : current->placeholder);
snprintf(parLength, 12, "%d", current->getLength()-1);
pitem.replace("{l}", parLength);
if (hasValueFromPost)
{
// -- Value from previous submit
pitem.replace("{v}", valueFromPost);
}
else
{
// -- Value from config
pitem.replace("{v}", current->valueBuffer);
}
pitem.replace(
"{c}", current->customHtml == nullptr ? "" : current->customHtml);
pitem.replace(
"{s}",
current->errorMessage == nullptr ? "" : "de"); // Div style class.
pitem.replace(
"{e}",
current->errorMessage == nullptr ? "" : current->errorMessage);
return pitem;
}
void TextParameter::update(String newValue)
{
newValue.toCharArray(this->valueBuffer, this->getLength());
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(": ");
Serial.println(this->valueBuffer);
#endif
}
void TextParameter::debugTo(Stream* out)
{
Parameter* current = this;
out->print("'");
out->print(current->getId());
out->print("' with value: '");
out->print(current->valueBuffer);
out->println("'");
}
///////////////////////////////////////////////////////////////////////////////
NumberParameter::NumberParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue,
const char* placeholder,
const char* customHtml)
: TextParameter(label, id, valueBuffer, length, defaultValue,
placeholder, customHtml)
{
}
String NumberParameter::renderHtml(
bool dataArrived,
bool hasValueFromPost, String valueFromPost)
{
return TextParameter::renderHtml("number", hasValueFromPost, valueFromPost);
}
///////////////////////////////////////////////////////////////////////////////
PasswordParameter::PasswordParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue,
const char* placeholder,
const char* customHtml)
: TextParameter(label, id, valueBuffer, length, defaultValue,
placeholder, customHtml)
{
}
String PasswordParameter::renderHtml(
bool dataArrived,
bool hasValueFromPost, String valueFromPost)
{
return TextParameter::renderHtml("password", true, String(""));
}
void PasswordParameter::debugTo(Stream* out)
{
Parameter* current = this;
out->print("'");
out->print(current->getId());
out->print("' with value: ");
#ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
out->print("'");
out->print(current->valueBuffer);
out->println("'");
#else
out->println(F("<hidden>"));
#endif
}
void PasswordParameter::update(String newValue)
{
Parameter* current = this;
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(": ");
#endif
if (newValue.length() > 0)
{
// -- Value was set.
newValue.toCharArray(current->valueBuffer, current->getLength());
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
# ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
Serial.println(current->valueBuffer);
# else
Serial.println("<updated>");
# endif
#endif
}
else
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.println("<was not changed>");
#endif
}
}
///////////////////////////////////////////////////////////////////////////////
CheckboxParameter::CheckboxParameter(
const char* label, const char* id, char* valueBuffer, int length,
bool defaultValue)
: TextParameter(label, id, valueBuffer, length, defaultValue ? "selected" : nullptr,
nullptr, nullptr)
{
}
String CheckboxParameter::renderHtml(
bool dataArrived,
bool hasValueFromPost, String valueFromPost)
{
bool checkSelected = false;
if (dataArrived)
{
if (hasValueFromPost && valueFromPost.equals("selected"))
{
checkSelected = true;
}
}
else
{
if (this->isChecked())
{
checkSelected = true;
}
}
if (checkSelected)
{
this->customHtml = CheckboxParameter::_checkedStr;
}
else
{
this->customHtml = nullptr;
}
return TextParameter::renderHtml("checkbox", true, "selected");
}
void CheckboxParameter::update(WebRequestWrapper* webRequestWrapper)
{
if (webRequestWrapper->hasArg(this->getId()))
{
String newValue = webRequestWrapper->arg(this->getId());
return TextParameter::update(newValue);
}
else if (this->visible)
{
// HTML will not post back unchecked checkboxes.
return TextParameter::update("");
}
}
///////////////////////////////////////////////////////////////////////////////
OptionsParameter::OptionsParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* optionValues, const char* optionNames, size_t optionCount, size_t nameLength,
const char* defaultValue)
: TextParameter(label, id, valueBuffer, length, defaultValue,
nullptr, nullptr)
{
this->_optionValues = optionValues;
this->_optionNames = optionNames;
this->_optionCount = optionCount;
this->_nameLength = nameLength;
}
///////////////////////////////////////////////////////////////////////////////
SelectParameter::SelectParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* optionValues, const char* optionNames, size_t optionCount, size_t nameLength,
const char* defaultValue)
: OptionsParameter(label, id, valueBuffer, length, optionValues, optionNames,
optionCount, nameLength, defaultValue)
{
}
String SelectParameter::renderHtml(
bool dataArrived,
bool hasValueFromPost, String valueFromPost)
{
TextParameter* current = this;
String options = "";
for (size_t i=0; i<this->_optionCount; i++)
{
const char *optionValue = (this->_optionValues + (i*this->getLength()) );
const char *optionName = (this->_optionNames + (i*this->_nameLength) );
String oitem = FPSTR(IOTWEBCONF_HTML_FORM_OPTION);
oitem.replace("{v}", optionValue);
// if (sizeof(this->_optionNames) > i)
{
oitem.replace("{n}", optionName);
}
// else
// {
// oitem.replace("{n}", "?");
// }
if ((hasValueFromPost && (valueFromPost == optionValue)) ||
(strncmp(current->valueBuffer, optionValue, this->getLength()) == 0))
{
// -- Value from previous submit
oitem.replace("{s}", " selected");
}
else
{
// -- Value from config
oitem.replace("{s}", "");
}
options += oitem;
}
String pitem = FPSTR(IOTWEBCONF_HTML_FORM_SELECT_PARAM);
pitem.replace("{b}", current->label);
pitem.replace("{i}", current->getId());
pitem.replace(
"{c}", current->customHtml == nullptr ? "" : current->customHtml);
pitem.replace(
"{s}",
current->errorMessage == nullptr ? "" : "de"); // Div style class.
pitem.replace(
"{e}",
current->errorMessage == nullptr ? "" : current->errorMessage);
pitem.replace("{o}", options);
return pitem;
}
///////////////////////////////////////////////////////////////////////////////
PrefixStreamWrapper::PrefixStreamWrapper(
Stream* originalStream,
std::function<size_t(Stream* stream)> prefixWriter)
{
this->_originalStream = originalStream;
this->_prefixWriter = prefixWriter;
}
size_t PrefixStreamWrapper::write(uint8_t data)
{
size_t sizeOut = checkNewLine();
sizeOut += this->_originalStream->write(data);
if (data == 10) // NewLine
{
this->_newLine = true;
}
return sizeOut;
}
size_t PrefixStreamWrapper::write(const uint8_t *buffer, size_t size)
{
size_t sizeOut = checkNewLine();
sizeOut += this->_originalStream->write(buffer, size);
if (*(buffer + size-1) == 10) // Ends with new line
{
this->_newLine = true;
}
return sizeOut;
}
size_t PrefixStreamWrapper::checkNewLine()
{
if (this->_newLine)
{
this->_newLine = false;
return this->_prefixWriter(this->_originalStream);
}
return 0;
}
} // end namespace
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment