Commit 6db31581 authored by Eric Duminil's avatar Eric Duminil
Browse files

Removed everything. S8 not recognized

parent 2fe7a4e7
Pipeline #6018 failed with stage
in 21 seconds
/**
* 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();
this->_apTimeoutMs = atoi(this->_apTimeoutStr) * 1000;
// -- Setup mdns
#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::setStateChangedCallback(std::function<void(
NetworkState oldState, NetworkState newState)> func)
{
this->_stateChangedCallback = 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);
if (this->_stateChangedCallback != nullptr)
{
this->_stateChangedCallback(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);
// Hostname must be set right before WiFi.begin in order to work reliably,
// and will be set only once WiFi.begin has been called.
WiFi.setHostname(this->_thingName);
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
/**
* 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 ever the state is changed.
* See NetworkState enum for possible values
*/
void setStateChangedCallback(std::function<void(
NetworkState oldState, NetworkState newState)> 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<void(NetworkState oldState, NetworkState newState)>
_stateChangedCallback = 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;\">Disable {b}</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
/**
* 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(this->valueBuffer));
}
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 != current->valueBuffer)
{
// -- 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
/**
* IotWebConfParameter.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 IotWebConfParameter_h
#define IotWebConfParameter_h
#include <Arduino.h>
#include <functional>
#include "IotWebConfSettings.h"
#include "IotWebConfWebServerWrapper.h"
#ifdef IOTWEBCONF_ENABLE_JSON
# include <ArduinoJson.h>
#endif
const char IOTWEBCONF_HTML_FORM_GROUP_START[] PROGMEM =
"<fieldset id='{i}'><legend>{b}</legend>\n";
const char IOTWEBCONF_HTML_FORM_GROUP_END[] PROGMEM =
"</fieldset>\n";
const char IOTWEBCONF_HTML_FORM_PARAM[] PROGMEM =
"<div class='{s}'><label for='{i}'>{b}</label><input type='{t}' id='{i}' "
"name='{i}' {l} placeholder='{p}' value='{v}' {c}/>"
"<div class='em'>{e}</div></div>\n";
const char IOTWEBCONF_HTML_FORM_SELECT_PARAM[] PROGMEM =
"<div class='{s}'><label for='{i}'>{b}</label><select id='{i}' "
"name='{i}' {c}/>\n{o}"
"</select><div class='em'>{e}</div></div>\n";
const char IOTWEBCONF_HTML_FORM_OPTION[] PROGMEM =
"<option value='{v}'{s}>{n}</option>\n";
namespace iotwebconf
{
typedef struct SerializationData
{
byte* data;
int length;
} SerializationData;
class ConfigItem
{
public:
bool visible = true;
const char* getId() { return this->_id; }
/**
* Calculate the size of bytes should be stored in the EEPROM.
*/
virtual int getStorageSize() = 0;
/**
* On initial startup (when no data was saved), it may be required to apply a default value
* to the parameter.
*/
virtual void applyDefaultValue() = 0;
/**
* Save data.
* @doStore - A method is passed as a parameter, that will performs the actual EEPROM access.
* The argument 'serializationData' of this referenced method should be pre-filled with
* the size and the serialized data before calling the method.
*/
virtual void storeValue(std::function<void(SerializationData* serializationData)> doStore) = 0;
/**
* Load data.
* @doLoad - A method is passed as a parameter, that will performs the actual EEPROM access.
* The argument 'serializationData' of this referenced method should be pre-filled with
* the size of the expected data, and the data buffer should be allocated with this size.
* The doLoad will fill the data from the EEPROM.
*/
virtual void loadValue(std::function<void(SerializationData* serializationData)> doLoad) = 0;
/**
* This method will create the HTML form item for the config portal.
*
* @dataArrived - True if there was a form post, where (some) form
* data arrived from the client.
* @webRequestWrapper - The webRequestWrapper, that will send the rendered content to the client.
* The webRequestWrapper->sendContent() method should be used in the implementations.
*/
virtual void renderHtml(bool dataArrived, WebRequestWrapper* webRequestWrapper) = 0;
/**
* New value arrived from the form post. The value should be stored in the
* in this config item.
*
* @webRequestWrapper - The webRequestWrapper, that will send the rendered content to the client.
* The webRequestWrapper->hasArg() and webRequestWrapper->arg() methods should be used in the
* implementations.
*/
virtual void update(WebRequestWrapper* webRequestWrapper) = 0;
/**
* Before validating the form post, it is required to clear previous error messages.
*/
virtual void clearErrorMessage() = 0;
/**
* This method should display information to Serial containing the parameter
* ID and the current value of the parameter (if it is confidential).
* Will only be called if debug is enabled.
*/
virtual void debugTo(Stream* out) = 0;
#ifdef IOTWEBCONF_ENABLE_JSON
/**
*
*/
virtual void loadFromJson(JsonObject jsonObject) = 0;
#endif
protected:
ConfigItem(const char* id) { this->_id = id; };
private:
const char* _id = 0;
ConfigItem* _parentItem = nullptr;
ConfigItem* _nextItem = nullptr;
friend class ParameterGroup; // Allow ParameterGroup to access _nextItem.
};
class ParameterGroup : public ConfigItem
{
public:
ParameterGroup(const char* id, const char* label = nullptr);
void addItem(ConfigItem* configItem);
const char *label;
void applyDefaultValue() override;
#ifdef IOTWEBCONF_ENABLE_JSON
virtual void loadFromJson(JsonObject jsonObject) override;
#endif
protected:
int getStorageSize() 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;
void update(WebRequestWrapper* webRequestWrapper) override;
void clearErrorMessage() override;
void debugTo(Stream* out) override;
/**
* One can override this method in case a specific HTML template is required
* for a group.
*/
virtual String getStartTemplate() { return FPSTR(IOTWEBCONF_HTML_FORM_GROUP_START); };
/**
* One can override this method in case a specific HTML template is required
* for a group.
*/
virtual String getEndTemplate() { return FPSTR(IOTWEBCONF_HTML_FORM_GROUP_END); };
ConfigItem* _firstItem = nullptr;
ConfigItem* getNextItemOf(ConfigItem* parent) { return parent->_nextItem; };
friend class IotWebConf; // Allow IotWebConf to access protected members.
private:
};
/**
* Parameters is a configuration item of the config portal.
* The parameter will have its input field on the configuration page,
* and the provided value will be saved to the EEPROM.
*/
class Parameter : public ConfigItem
{
public:
/**
* Create a parameter for the config portal.
*
* @label - Displayable label at the config portal.
* @id - Identifier used for HTTP queries and as configuration key. Must not
* contain spaces nor other special characters.
* @valueBuffer - Configuration value will be loaded to this buffer from the
* EEPROM.
* @length - The buffer should have a length provided here.
* @defaultValue - Defalt value set on startup, when no configuration ever saved
* with the current config-version.
*/
Parameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue = nullptr);
const char* label;
char* valueBuffer;
const char* defaultValue;
const char* errorMessage;
int getLength() { return this->_length; }
void applyDefaultValue() override;
#ifdef IOTWEBCONF_ENABLE_JSON
virtual void loadFromJson(JsonObject jsonObject) override;
#endif
protected:
// Overrides
int getStorageSize() override;
void storeValue(std::function<void(SerializationData* serializationData)> doStore) override;
void loadValue(std::function<void(SerializationData* serializationData)> doLoad) override;
virtual void update(WebRequestWrapper* webRequestWrapper) override;
virtual void update(String newValue) = 0;
void clearErrorMessage() override;
private:
int _length;
};
///////////////////////////////////////////////////////////////////////////////
/**
* TexParameters is to store text based parameters.
*/
class TextParameter : public Parameter
{
public:
/**
* Create a text parameter for the config portal.
*
* @placeholder (optional) - Text appear in an empty input box.
* @customHtml (optional) - The text of this parameter will be added into
* the HTML INPUT field.
* (See Parameter for arguments!)
*/
TextParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue = nullptr,
const char* placeholder = nullptr,
const char* customHtml = nullptr);
/**
* This variable is meant to store a value that is displayed in an empty
* (not filled) field.
*/
const char* placeholder;
/**
* Usually this variable is used when rendering the form input field
* so one can customize the rendered outcome of this particular item.
*/
const char* customHtml;
protected:
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost);
// Overrides
virtual void renderHtml(bool dataArrived, WebRequestWrapper* webRequestWrapper) override;
virtual void update(String newValue) override;
virtual void debugTo(Stream* out) override;
/**
* One can override this method in case a specific HTML template is required
* for a parameter.
*/
virtual String getHtmlTemplate() { return FPSTR(IOTWEBCONF_HTML_FORM_PARAM); };
/**
* Renders a standard HTML form INPUT.
* @type - The type attribute of the html input field.
*/
virtual String renderHtml(const char* type, bool hasValueFromPost, String valueFromPost);
private:
friend class IotWebConf;
friend class WifiParameterGroup;
};
///////////////////////////////////////////////////////////////////////////////
/**
* The Password parameter has a special handling, as the value will be
* overwritten in the EEPROM only if value was provided on the config portal.
* Because of this logic, "password" type field with length more then
* IOTWEBCONF_PASSWORD_LEN characters are not supported.
*/
class PasswordParameter : public TextParameter
{
public:
/**
* Create a password parameter for the config portal.
*
* (See TextParameter for arguments!)
*/
PasswordParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue = nullptr,
const char* placeholder = nullptr,
const char* customHtml = "ondblclick=\"pw(this.id)\"");
protected:
// Overrides
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override;
virtual void update(String newValue) override;
virtual void debugTo(Stream* out) override;
private:
friend class IotWebConf;
friend class WifiParameterGroup;
};
///////////////////////////////////////////////////////////////////////////////
/**
* This is just a text parameter, that is rendered with type 'number'.
*/
class NumberParameter : public TextParameter
{
public:
/**
* Create a numeric parameter for the config portal.
*
* (See TextParameter for arguments!)
*/
NumberParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* defaultValue = nullptr,
const char* placeholder = nullptr,
const char* customHtml = nullptr);
protected:
// Overrides
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override;
private:
friend class IotWebConf;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Checkbox parameter is represended as a text parameter but has a special
* handling. As the value is either empty or has the word "selected".
* Note, that form post will not send value if checkbox was not selected.
*/
class CheckboxParameter : public TextParameter
{
public:
/**
* Create a checkbox parameter for the config portal.
*
* (See TextParameter for arguments!)
*/
CheckboxParameter(
const char* label, const char* id, char* valueBuffer, int length,
bool defaultValue = false);
bool isChecked() { return strncmp(this->valueBuffer, "selected", this->getLength()) == 0; }
protected:
// Overrides
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override;
virtual void update(WebRequestWrapper* webRequestWrapper) override;
private:
friend class IotWebConf;
bool _checked;
const char* _checkedStr = "checked='checked'";
};
///////////////////////////////////////////////////////////////////////////////
/**
* Options parameter is a structure, that handles multiple values when redering
* the HTML representation.
*/
class OptionsParameter : public TextParameter
{
public:
/**
* @optionValues - List of values to choose from with, where each value
* can have a maximal size of 'length'. Contains 'optionCount' items.
* @optionNames - List of names to render for the values, where each
* name can have a maximal size of 'nameLength'. Contains 'optionCount'
* items.
* @optionCount - Size of both 'optionValues' and 'optionNames' lists.
* @nameLength - Size of any item in optionNames list.
* (See TextParameter for arguments!)
*/
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 = nullptr);
protected:
const char* _optionValues;
const char* _optionNames;
size_t _optionCount;
size_t _nameLength;
private:
friend class IotWebConf;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Select parameter is an option parameter, that rendered as HTML SELECT.
* Basically it is a dropdown combobox.
*/
class SelectParameter : public OptionsParameter
{
public:
/**
* Create a select parameter for the config portal.
*
* (See OptionsParameter for arguments!)
*/
SelectParameter(
const char* label, const char* id, char* valueBuffer, int length,
const char* optionValues, const char* optionNames, size_t optionCount, size_t namesLenth,
const char* defaultValue = nullptr);
protected:
// Overrides
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override;
private:
friend class IotWebConf;
};
/**
* This class is here just to make some nice indents on debug output
* for group tree.
*/
class PrefixStreamWrapper : public Stream
{
public:
PrefixStreamWrapper(
Stream* originalStream,
std::function<size_t(Stream* stream)> prefixWriter);
size_t write(uint8_t) override;
size_t write(const uint8_t *buffer, size_t size) override;
int available() override { return _originalStream->available(); };
int read() override { return _originalStream->read(); };
int peek() override { return _originalStream->peek(); };
void flush() override { return _originalStream->flush(); };
private:
Stream* _originalStream;
std::function<size_t(Stream* stream)> _prefixWriter;
bool _newLine = true;
size_t checkNewLine();
};
} // end namespace
#endif
/**
* IotWebConfSettings.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 IotWebConfSettings_h
#define IotWebConfSettings_h
#if __has_include("custom_ampel_iotwebconf.h")
#include "custom_ampel_iotwebconf.h"
#endif
// -- We might want to place the config in the EEPROM in an offset.
#ifndef IOTWEBCONF_CONFIG_START
# define IOTWEBCONF_CONFIG_START 0
#endif
// -- Maximal length of any string used in IotWebConfig configuration (e.g.
// ssid).
#ifndef IOTWEBCONF_WORD_LEN
# define IOTWEBCONF_WORD_LEN 33
#endif
// -- Maximal length of password used in IotWebConfig configuration.
#ifndef IOTWEBCONF_PASSWORD_LEN
# define IOTWEBCONF_PASSWORD_LEN 33
#endif
// -- IotWebConf tries to connect to the local network for an amount of time
// before falling back to AP mode.
#ifndef IOTWEBCONF_DEFAULT_WIFI_CONNECTION_TIMEOUT_MS
# define IOTWEBCONF_DEFAULT_WIFI_CONNECTION_TIMEOUT_MS 30000
#endif
// -- Thing will stay in AP mode for an amount of time on boot, before retrying
// to connect to a WiFi network.
#ifndef IOTWEBCONF_DEFAULT_AP_MODE_TIMEOUT_SECS
# define IOTWEBCONF_DEFAULT_AP_MODE_TIMEOUT_SECS "30"
#endif
// -- mDNS should allow you to connect to this device with a hostname provided
// by the device. E.g. mything.local
// (This is not very likely to work, and MDNS is not very well documented.)
#ifndef IOTWEBCONF_CONFIG_DONT_USE_MDNS
# define IOTWEBCONF_CONFIG_USE_MDNS 80
#endif
// -- Logs progress information to Serial if enabled.
#ifndef IOTWEBCONF_DEBUG_DISABLED
# define IOTWEBCONF_DEBUG_TO_SERIAL
#endif
// -- Logs passwords to Serial if enabled.
//#define IOTWEBCONF_DEBUG_PWD_TO_SERIAL
// -- Helper define for serial debug
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
# define IOTWEBCONF_DEBUG_LINE(MSG) Serial.println(MSG)
#else
# define IOTWEBCONF_DEBUG_LINE(MSG)
#endif
// -- EEPROM config starts with a special prefix of length defined here.
#ifndef IOTWEBCONF_CONFIG_VERSION_LENGTH
# define IOTWEBCONF_CONFIG_VERSION_LENGTH 4
#endif
#ifndef IOTWEBCONF_DNS_PORT
# define IOTWEBCONF_DNS_PORT 53
#endif
#endif
\ No newline at end of file
/**
* IotWebConfTParameter.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>
* rovo89
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef IotWebConfTParameter_h
#define IotWebConfTParameter_h
// TODO: This file is a mess. Help wanted to organize thing!
#include "IotWebConfParameter.h"
#include <Arduino.h>
#include <IPAddress.h>
#include <errno.h>
// At least in PlatformIO, strtoimax/strtoumax are defined, but not implemented.
#if 1
#define strtoimax strtoll
#define strtoumax strtoull
#endif
namespace iotwebconf
{
/**
* This class is to hide web related properties from the
* data manipulation.
*/
class ConfigItemBridge : public ConfigItem
{
public:
virtual void update(WebRequestWrapper* webRequestWrapper) override
{
if (webRequestWrapper->hasArg(this->getId()))
{
String newValue = webRequestWrapper->arg(this->getId());
this->update(newValue);
}
}
void debugTo(Stream* out) override
{
out->print("'");
out->print(this->getId());
out->print("' with value: '");
out->print(this->toString());
out->println("'");
}
protected:
ConfigItemBridge(const char* id) : ConfigItem(id) { }
virtual int getInputLength() { return 0; };
virtual bool update(String newValue, bool validateOnly = false) = 0;
virtual String toString() = 0;
};
///////////////////////////////////////////////////////////////////////////
/**
* DataType is the data related part of the parameter.
* It does not care about web and visualization, but takes care of the
* data validation and storing.
*/
template <typename ValueType, typename _DefaultValueType = ValueType>
class DataType : virtual public ConfigItemBridge
{
public:
using DefaultValueType = _DefaultValueType;
DataType(const char* id, DefaultValueType defaultValue) :
ConfigItemBridge(id),
_defaultValue(defaultValue)
{
}
/**
* value() can be used to get the value, but it can also
* be used set it like this: p.value() = newValue
*/
ValueType& value() { return this->_value; }
ValueType& operator*() { return this->_value; }
protected:
int getStorageSize() override
{
return sizeof(ValueType);
}
virtual bool update(String newValue, bool validateOnly = false) = 0;
bool validate(String newValue) { return update(newValue, true); }
virtual String toString() override { return String(this->_value); }
ValueType _value;
const DefaultValueType _defaultValue;
};
///////////////////////////////////////////////////////////////////////////
class StringDataType : public DataType<String>
{
public:
using DataType<String>::DataType;
protected:
virtual bool update(String newValue, bool validateOnly) override {
if (!validateOnly)
{
this->_value = newValue;
}
return true;
}
virtual String toString() override { return this->_value; }
};
///////////////////////////////////////////////////////////////////////////
template <size_t len>
class CharArrayDataType : public DataType<char[len], const char*>
{
public:
using DataType<char[len], const char*>::DataType;
CharArrayDataType(const char* id, const char* defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
DataType<char[len], const char*>::DataType(id, defaultValue) { };
virtual void applyDefaultValue() override
{
strncpy(this->_value, this->_defaultValue, len);
}
protected:
virtual bool update(String newValue, bool validateOnly) override
{
if (newValue.length() + 1 > len)
{
return false;
}
if (!validateOnly)
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(": ");
Serial.println(newValue);
#endif
strncpy(this->_value, newValue.c_str(), len);
}
return true;
}
void storeValue(std::function<void(
SerializationData* serializationData)> doStore) override
{
SerializationData serializationData;
serializationData.length = len;
serializationData.data = (byte*)this->_value;
doStore(&serializationData);
}
void loadValue(std::function<void(
SerializationData* serializationData)> doLoad) override
{
SerializationData serializationData;
serializationData.length = len;
serializationData.data = (byte*)this->_value;
doLoad(&serializationData);
}
virtual int getInputLength() override { return len; };
};
///////////////////////////////////////////////////////////////////////////
/**
* All non-complex types should be inherited from this base class.
*/
template <typename ValueType>
class PrimitiveDataType : public DataType<ValueType>
{
public:
using DataType<ValueType>::DataType;
PrimitiveDataType(const char* id, ValueType defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
DataType<ValueType>::DataType(id, defaultValue) { };
void setMax(ValueType val) { this->_max = val; this->_maxDefined = true; }
void setMin(ValueType val) { this->_min = val; this->_minDefined = true; }
virtual void applyDefaultValue() override
{
this->_value = this->_defaultValue;
}
protected:
virtual bool update(String newValue, bool validateOnly) override
{
errno = 0;
ValueType val = fromString(newValue);
if ((errno == ERANGE)
|| (this->_minDefined && (val < this->_min))
|| (this->_maxDefined && (val > this->_max)))
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(" value not accepted: ");
Serial.println(val);
#endif
return false;
}
if (!validateOnly)
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(": ");
Serial.println((ValueType)val);
#endif
this->_value = (ValueType) val;
}
return true;
}
void storeValue(std::function<void(
SerializationData* serializationData)> doStore) override
{
SerializationData serializationData;
serializationData.length = this->getStorageSize();
serializationData.data =
reinterpret_cast<byte*>(&this->_value);
doStore(&serializationData);
}
void loadValue(std::function<void(
SerializationData* serializationData)> doLoad) override
{
byte buf[this->getStorageSize()];
SerializationData serializationData;
serializationData.length = this->getStorageSize();
serializationData.data = buf;
doLoad(&serializationData);
ValueType* valuePointer = reinterpret_cast<ValueType*>(buf);
this->_value = *valuePointer;
}
virtual ValueType fromString(String stringValue) = 0;
ValueType getMax() { return this->_max; }
ValueType getMin() { return this->_min; }
ValueType isMaxDefined() { return this->_maxDefined; }
ValueType isMinDefined() { return this->_minDefined; }
private:
ValueType _min;
ValueType _max;
bool _minDefined = false;
bool _maxDefined = false;
};
///////////////////////////////////////////////////////////////////////////
template <typename ValueType, int base = 10>
class SignedIntDataType : public PrimitiveDataType<ValueType>
{
public:
SignedIntDataType(const char* id, ValueType defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
PrimitiveDataType<ValueType>::PrimitiveDataType(id, defaultValue) { };
protected:
virtual ValueType fromString(String stringValue)
{
return (ValueType)strtoimax(stringValue.c_str(), nullptr, base);
}
};
template <typename ValueType, int base = 10>
class UnsignedIntDataType : public PrimitiveDataType<ValueType>
{
public:
UnsignedIntDataType(const char* id, ValueType defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
PrimitiveDataType<ValueType>::PrimitiveDataType(id, defaultValue) { };
protected:
virtual ValueType fromString(String stringValue)
{
return (ValueType)strtoumax(stringValue.c_str(), nullptr, base);
}
};
class BoolDataType : public PrimitiveDataType<bool>
{
public:
BoolDataType(const char* id, bool defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
PrimitiveDataType<bool>::PrimitiveDataType(id, defaultValue) { };
protected:
virtual bool fromString(String stringValue)
{
return stringValue.c_str()[0] == 1;
}
};
class FloatDataType : public PrimitiveDataType<float>
{
public:
FloatDataType(const char* id, float defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
PrimitiveDataType<float>::PrimitiveDataType(id, defaultValue) { };
protected:
virtual float fromString(String stringValue)
{
return atof(stringValue.c_str());
}
};
class DoubleDataType : public PrimitiveDataType<double>
{
public:
DoubleDataType(const char* id, double defaultValue) :
ConfigItemBridge::ConfigItemBridge(id),
PrimitiveDataType<double>::PrimitiveDataType(id, defaultValue) { };
protected:
virtual double fromString(String stringValue)
{
return strtod(stringValue.c_str(), nullptr);
}
};
/////////////////////////////////////////////////////////////////////////
class IpDataType : public DataType<IPAddress>
{
using DataType<IPAddress>::DataType;
protected:
virtual bool update(String newValue, bool validateOnly) override
{
if (validateOnly)
{
IPAddress ip;
return ip.fromString(newValue);
}
else
{
return this->_value.fromString(newValue);
}
}
virtual String toString() override { return this->_value.toString(); }
};
///////////////////////////////////////////////////////////////////////////
/**
* Input parameter is the part of the parameter that is responsible
* for the appearance of the parameter in HTML.
*/
class InputParameter : virtual public ConfigItemBridge
{
public:
InputParameter(const char* id, const char* label) :
ConfigItemBridge::ConfigItemBridge(id),
label(label) { }
virtual void renderHtml(
bool dataArrived, WebRequestWrapper* webRequestWrapper) override
{
String content = this->renderHtml(
dataArrived,
webRequestWrapper->hasArg(this->getId()),
webRequestWrapper->arg(this->getId()));
webRequestWrapper->sendContent(content);
}
const char* label;
/**
* This variable is meant to store a value that is displayed in an empty
* (not filled) field.
*/
const char* placeholder = nullptr;
virtual void setPlaceholder(const char* placeholder) { this->placeholder = placeholder; }
/**
* Usually this variable is used when rendering the form input field
* so one can customize the rendered outcome of this particular item.
*/
const char* customHtml = nullptr;
/**
* Used when rendering the input field. Is is overridden by different
* implementations.
*/
virtual String getCustomHtml()
{
return String(customHtml == nullptr ? "" : customHtml);
}
const char* errorMessage = nullptr;
protected:
void clearErrorMessage() override
{
this->errorMessage = nullptr;
}
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost)
{
String pitem = String(this->getHtmlTemplate());
pitem.replace("{b}", this->label);
pitem.replace("{t}", this->getInputType());
pitem.replace("{i}", this->getId());
pitem.replace(
"{p}", this->placeholder == nullptr ? "" : this->placeholder);
int length = this->getInputLength();
if (length > 0)
{
char parLength[11];
snprintf(parLength, 11, "%d", length - 1); // To allow "\0" at the end of the string.
String maxLength = String("maxlength=") + parLength;
pitem.replace("{l}", maxLength);
}
else
{
pitem.replace("{l}", "");
}
if (hasValueFromPost)
{
// -- Value from previous submit
pitem.replace("{v}", valueFromPost);
}
else
{
// -- Value from config
pitem.replace("{v}", this->toString());
}
pitem.replace("{c}", this->getCustomHtml());
pitem.replace(
"{s}",
this->errorMessage == nullptr ? "" : "de"); // Div style class.
pitem.replace(
"{e}",
this->errorMessage == nullptr ? "" : this->errorMessage);
return pitem;
}
/**
* One can override this method in case a specific HTML template is required
* for a parameter.
*/
virtual String getHtmlTemplate() { return FPSTR(IOTWEBCONF_HTML_FORM_PARAM); };
virtual const char* getInputType() = 0;
};
template <size_t len>
class TextTParameter : public CharArrayDataType<len>, public InputParameter
{
public:
using CharArrayDataType<len>::CharArrayDataType;
TextTParameter(const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
CharArrayDataType<len>::CharArrayDataType(id, defaultValue),
InputParameter::InputParameter(id, label) { }
protected:
virtual const char* getInputType() override { return "text"; }
};
class CheckboxTParameter : public BoolDataType, public InputParameter
{
public:
CheckboxTParameter(const char* id, const char* label, const bool defaultValue) :
ConfigItemBridge(id),
BoolDataType::BoolDataType(id, defaultValue),
InputParameter::InputParameter(id, label) { }
bool isChecked() { return this->value(); }
protected:
virtual const char* getInputType() override { return "checkbox"; }
virtual void update(WebRequestWrapper* webRequestWrapper) override
{
bool selected = false;
if (webRequestWrapper->hasArg(this->getId()))
{
String valueFromPost = webRequestWrapper->arg(this->getId());
selected = valueFromPost.equals("selected");
}
// this->update(String(selected ? "1" : "0"));
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(": ");
Serial.println(selected ? "selected" : "not selected");
#endif
this->_value = selected;
}
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override
{
bool checkSelected = false;
if (dataArrived)
{
if (hasValueFromPost && valueFromPost.equals("selected"))
{
checkSelected = true;
}
}
else
{
if (this->isChecked())
{
checkSelected = true;
}
}
if (checkSelected)
{
this->customHtml = CheckboxTParameter::_checkedStr;
}
else
{
this->customHtml = nullptr;
}
return InputParameter::renderHtml(dataArrived, true, String("selected"));
}
private:
const char* _checkedStr = "checked='checked'";
};
template <size_t len>
class PasswordTParameter : public CharArrayDataType<len>, public InputParameter
{
public:
using CharArrayDataType<len>::CharArrayDataType;
PasswordTParameter(const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
CharArrayDataType<len>::CharArrayDataType(id, defaultValue),
InputParameter::InputParameter(id, label)
{
this->customHtml = _customHtmlPwd;
}
void debugTo(Stream* out)
{
out->print("'");
out->print(this->getId());
out->print("' with value: ");
#ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
out->print("'");
out->print(this->_value);
out->println("'");
#else
out->println(F("<hidden>"));
#endif
}
virtual bool update(String newValue, bool validateOnly) override
{
if (newValue.length() + 1 > len)
{
return false;
}
if (validateOnly)
{
return true;
}
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.print(this->getId());
Serial.print(": ");
#endif
if (newValue != this->_value)
{
// -- Value was set.
strncpy(this->_value, newValue.c_str(), len);
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
# ifdef IOTWEBCONF_DEBUG_PWD_TO_SERIAL
Serial.println(this->_value);
# else
Serial.println("<updated>");
# endif
#endif
}
else
{
#ifdef IOTWEBCONF_DEBUG_TO_SERIAL
Serial.println("<was not changed>");
#endif
}
return true;
}
protected:
virtual const char* getInputType() override { return "password"; }
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override
{
return InputParameter::renderHtml(dataArrived, true, String(this->_value));
}
private:
const char* _customHtmlPwd = "ondblclick=\"pw(this.id)\"";
};
/**
* All non-complex type input parameters should be inherited from this
* base class.
*/
template <typename ValueType>
class PrimitiveInputParameter :
public InputParameter
{
public:
PrimitiveInputParameter(const char* id, const char* label) :
ConfigItemBridge::ConfigItemBridge(id),
InputParameter::InputParameter(id, label) { }
virtual String getCustomHtml() override
{
String modifiers = String(this->customHtml);
if (this->isMinDefined())
{
modifiers += " min='" ;
modifiers += this->getMin();
modifiers += "'";
}
if (this->isMaxDefined())
{
modifiers += " max='";
modifiers += this->getMax();
modifiers += "'";
}
if (this->step != 0)
{
modifiers += " step='";
modifiers += this->step;
modifiers += "'";
}
return modifiers;
}
ValueType step = 0;
void setStep(ValueType step) { this->step = step; }
virtual ValueType getMin() = 0;
virtual ValueType getMax() = 0;
virtual bool isMinDefined() = 0;
virtual bool isMaxDefined() = 0;
};
template <typename ValueType, int base = 10>
class IntTParameter :
public virtual SignedIntDataType<ValueType, base>,
public PrimitiveInputParameter<ValueType>
{
public:
IntTParameter(const char* id, const char* label, ValueType defaultValue) :
ConfigItemBridge(id),
SignedIntDataType<ValueType, base>::SignedIntDataType(id, defaultValue),
PrimitiveInputParameter<ValueType>::PrimitiveInputParameter(id, label) { }
// TODO: somehow organize these methods into common parent.
virtual ValueType getMin() override
{
return PrimitiveDataType<ValueType>::getMin();
}
virtual ValueType getMax() override
{
return PrimitiveDataType<ValueType>::getMax();
}
virtual bool isMinDefined() override
{
return PrimitiveDataType<ValueType>::isMinDefined();
}
virtual bool isMaxDefined() override
{
return PrimitiveDataType<ValueType>::isMaxDefined();
}
protected:
virtual const char* getInputType() override { return "number"; }
};
template <typename ValueType, int base = 10>
class UIntTParameter :
public virtual UnsignedIntDataType<ValueType, base>,
public PrimitiveInputParameter<ValueType>
{
public:
UIntTParameter(const char* id, const char* label, ValueType defaultValue) :
ConfigItemBridge(id),
UnsignedIntDataType<ValueType, base>::UnsignedIntDataType(id, defaultValue),
PrimitiveInputParameter<ValueType>::PrimitiveInputParameter(id, label) { }
// TODO: somehow organize these methods into common parent.
virtual ValueType getMin() override
{
return PrimitiveDataType<ValueType>::getMin();
}
virtual ValueType getMax() override
{
return PrimitiveDataType<ValueType>::getMax();
}
virtual bool isMinDefined() override
{
return PrimitiveDataType<ValueType>::isMinDefined();
}
virtual bool isMaxDefined() override
{
return PrimitiveDataType<ValueType>::isMaxDefined();
}
protected:
virtual const char* getInputType() override { return "number"; }
};
class FloatTParameter :
public FloatDataType,
public PrimitiveInputParameter<float>
{
public:
FloatTParameter(const char* id, const char* label, float defaultValue) :
ConfigItemBridge(id),
FloatDataType::FloatDataType(id, defaultValue),
PrimitiveInputParameter<float>::PrimitiveInputParameter(id, label) { }
virtual float getMin() override
{
return PrimitiveDataType<float>::getMin();
}
virtual float getMax() override
{
return PrimitiveDataType<float>::getMax();
}
virtual bool isMinDefined() override
{
return PrimitiveDataType<float>::isMinDefined();
}
virtual bool isMaxDefined() override
{
return PrimitiveDataType<float>::isMaxDefined();
}
protected:
virtual const char* getInputType() override { return "number"; }
};
/**
* Options parameter is a structure, that handles multiple values when redering
* the HTML representation.
*/
template <size_t len>
class OptionsTParameter : public TextTParameter<len>
{
public:
/**
* @optionValues - List of values to choose from with, where each value
* can have a maximal size of 'length'. Contains 'optionCount' items.
* @optionNames - List of names to render for the values, where each
* name can have a maximal size of 'nameLength'. Contains 'optionCount'
* items.
* @optionCount - Size of both 'optionValues' and 'optionNames' lists.
* @nameLength - Size of any item in optionNames list.
* (See TextParameter for arguments!)
*/
OptionsTParameter(
const char* id, const char* label, const char* defaultValue,
const char* optionValues, const char* optionNames,
size_t optionCount, size_t nameLength) :
ConfigItemBridge(id),
TextTParameter<len>(id, label, defaultValue)
{
this->_optionValues = optionValues;
this->_optionNames = optionNames;
this->_optionCount = optionCount;
this->_nameLength = nameLength;
}
// TODO: make these protected
void setOptionValues(const char* optionValues) { this->_optionValues = optionValues; }
void setOptionNames(const char* optionNames) { this->_optionNames = optionNames; }
void setOptionCount(size_t optionCount) { this->_optionCount = optionCount; }
void setNameLength(size_t nameLength) { this->_nameLength = nameLength; }
protected:
OptionsTParameter(
const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
TextTParameter<len>(id, label, defaultValue)
{
}
const char* _optionValues;
const char* _optionNames;
size_t _optionCount;
size_t _nameLength;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Select parameter is an option parameter, that rendered as HTML SELECT.
* Basically it is a dropdown combobox.
*/
template <size_t len>
class SelectTParameter : public OptionsTParameter<len>
{
public:
/**
* Create a select parameter for the config portal.
*
* (See OptionsParameter for arguments!)
*/
SelectTParameter(
const char* id, const char* label, const char* defaultValue,
const char* optionValues, const char* optionNames,
size_t optionCount, size_t nameLength) :
ConfigItemBridge(id),
OptionsTParameter<len>(
id, label, defaultValue, optionValues, optionNames, optionCount, nameLength)
{ }
// TODO: make this protected
SelectTParameter(
const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
OptionsTParameter<len>(id, label, defaultValue) { }
protected:
// Overrides
virtual String renderHtml(
bool dataArrived, bool hasValueFromPost, String valueFromPost) override
{
String options = "";
for (size_t i=0; i<this->_optionCount; i++)
{
const char *optionValue = (this->_optionValues + (i*len) );
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(this->value(), optionValue, len) == 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}", this->label);
pitem.replace("{i}", this->getId());
pitem.replace(
"{c}", this->customHtml == nullptr ? "" : this->customHtml);
pitem.replace(
"{s}",
this->errorMessage == nullptr ? "" : "de"); // Div style class.
pitem.replace(
"{e}",
this->errorMessage == nullptr ? "" : this->errorMessage);
pitem.replace("{o}", options);
return pitem;
}
private:
};
///////////////////////////////////////////////////////////////////////////////
/**
* Color chooser.
*/
class ColorTParameter : public CharArrayDataType<8>, public InputParameter
{
public:
using CharArrayDataType<8>::CharArrayDataType;
ColorTParameter(const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
CharArrayDataType<8>::CharArrayDataType(id, defaultValue),
InputParameter::InputParameter(id, label) { }
protected:
virtual const char* getInputType() override { return "color"; }
};
///////////////////////////////////////////////////////////////////////////////
/**
* Date chooser.
*/
class DateTParameter : public CharArrayDataType<11>, public InputParameter
{
public:
using CharArrayDataType<11>::CharArrayDataType;
DateTParameter(const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
CharArrayDataType<11>::CharArrayDataType(id, defaultValue),
InputParameter::InputParameter(id, label) { }
protected:
virtual const char* getInputType() override { return "date"; }
};
///////////////////////////////////////////////////////////////////////////////
/**
* Time chooser.
*/
class TimeTParameter : public CharArrayDataType<6>, public InputParameter
{
public:
using CharArrayDataType<6>::CharArrayDataType;
TimeTParameter(const char* id, const char* label, const char* defaultValue) :
ConfigItemBridge(id),
CharArrayDataType<6>::CharArrayDataType(id, defaultValue),
InputParameter::InputParameter(id, label) { }
protected:
virtual const char* getInputType() override { return "time"; }
};
} // end namespace
#include "IotWebConfTParameterBuilder.h"
#endif
/**
* IotWebConfTParameter.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>
* rovo89
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef IotWebConfTParameterBuilder_h
#define IotWebConfTParameterBuilder_h
#include "IotWebConfTParameter.h"
namespace iotwebconf
{
template <typename ParamType> class Builder;
template <typename ParamType>
class AbstractBuilder
{
public:
AbstractBuilder(const char* id) : _id(id) { };
virtual ParamType build() const
{
ParamType instance = std::move(
ParamType(this->_id, this->_label, this->_defaultValue));
this->apply(&instance);
return instance;
}
Builder<ParamType>& label(const char* label)
{ this->_label = label; return static_cast<Builder<ParamType>&>(*this); }
Builder<ParamType>& defaultValue(typename ParamType::DefaultValueType defaultValue)
{ this->_defaultValue = defaultValue; return static_cast<Builder<ParamType>&>(*this); }
protected:
virtual ParamType* apply(ParamType* instance) const
{
return instance;
}
const char* _label;
const char* _id;
typename ParamType::DefaultValueType _defaultValue;
};
template <typename ParamType>
class Builder : public AbstractBuilder<ParamType>
{
public:
Builder(const char* id) : AbstractBuilder<ParamType>(id) { };
};
///////////////////////////////////////////////////////////////////////////
template <typename ValueType, typename ParamType>
class PrimitiveBuilder :
public AbstractBuilder<ParamType>
{
public:
PrimitiveBuilder<ValueType, ParamType>(const char* id) :
AbstractBuilder<ParamType>(id) { };
Builder<ParamType>& min(ValueType min) { this->_minDefined = true; this->_min = min; return static_cast<Builder<ParamType>&>(*this); }
Builder<ParamType>& max(ValueType max) { this->_maxDefined = true; this->_max = max; return static_cast<Builder<ParamType>&>(*this); }
Builder<ParamType>& step(ValueType step) { this->_step = step; return static_cast<Builder<ParamType>&>(*this); }
Builder<ParamType>& placeholder(const char* placeholder) { this->_placeholder = placeholder; return static_cast<Builder<ParamType>&>(*this); }
protected:
virtual ParamType* apply(
ParamType* instance) const override
{
if (this->_minDefined)
{
instance->setMin(this->_min);
}
if (this->_maxDefined)
{
instance->setMax(this->_max);
}
instance->setStep(this->_step);
instance->setPlaceholder(this->_placeholder);
return instance;
}
bool _minDefined = false;
bool _maxDefined = false;
ValueType _min;
ValueType _max;
ValueType _step = 0;
const char* _placeholder = nullptr;
};
template <typename ValueType, int base>
class Builder<IntTParameter<ValueType, base>> :
public PrimitiveBuilder<ValueType, IntTParameter<ValueType, base>>
{
public:
Builder<IntTParameter<ValueType, base>>(const char* id) :
PrimitiveBuilder<ValueType, IntTParameter<ValueType, base>>(id) { };
};
template <typename ValueType, int base>
class Builder<UIntTParameter<ValueType, base>> :
public PrimitiveBuilder<ValueType, UIntTParameter<ValueType, base>>
{
public:
Builder<UIntTParameter<ValueType, base>>(const char* id) :
PrimitiveBuilder<ValueType, UIntTParameter<ValueType, base>>(id) { };
};
template <>
class Builder<FloatTParameter> :
public PrimitiveBuilder<float, FloatTParameter>
{
public:
Builder<FloatTParameter>(const char* id) :
PrimitiveBuilder<float, FloatTParameter>(id) { };
};
template <size_t len>
class Builder<SelectTParameter<len>> :
public AbstractBuilder<SelectTParameter<len>>
{
public:
Builder<SelectTParameter<len>>(const char* id) :
AbstractBuilder<SelectTParameter<len>>(id) { };
virtual SelectTParameter<len> build() const override
{
return SelectTParameter<len>(
this->_id, this->_label, this->_defaultValue,
this->_optionValues, this->_optionNames,
this->_optionCount, this->_nameLength);
}
Builder<SelectTParameter<len>>& optionValues(const char* optionValues)
{ this->_optionValues = optionValues; return *this; }
Builder<SelectTParameter<len>>& optionNames(const char* optionNames)
{ this->_optionNames = optionNames; return *this; }
Builder<SelectTParameter<len>>& optionCount(size_t optionCount)
{ this->_optionCount = optionCount; return *this; }
Builder<SelectTParameter<len>>& nameLength(size_t nameLength)
{ this->_nameLength = nameLength; return *this; }
protected:
virtual SelectTParameter<len>* apply(
SelectTParameter<len>* instance) const override
{
instance->setOptionValues(this->_optionValues);
instance->setOptionNames(this->_optionNames);
instance->setOptionCount(this->_optionCount);
instance->setNameLength(this->_nameLength);
return instance;
}
private:
const char* _optionValues;
const char* _optionNames;
size_t _optionCount;
size_t _nameLength;
};
} // End namespace
#endif
/**
* IotWebConfUsing.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 IotWebConfUsing_h
#define IotWebConfUsing_h
// This "using" lines are just aliases, and should avoided.
using IotWebConfParameterGroup = iotwebconf::ParameterGroup;
using IotWebConfTextParameter = iotwebconf::TextParameter;
using IotWebConfPasswordParameter = iotwebconf::PasswordParameter;
using IotWebConfNumberParameter = iotwebconf::NumberParameter;
using IotWebConfCheckboxParameter = iotwebconf::CheckboxParameter;
using IotWebConfSelectParameter = iotwebconf::SelectParameter;
#endif
\ No newline at end of file
/**
* IotWebConfWebServerWrapper.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 WebServerWrapper_h
#define WebServerWrapper_h
#include <Arduino.h>
#include <IPAddress.h>
namespace iotwebconf
{
class WebRequestWrapper
{
public:
virtual const String hostHeader() const;
virtual IPAddress localIP();
virtual uint16_t localPort();
virtual const String uri() const;
virtual bool authenticate(const char * username, const char * password);
virtual void requestAuthentication();
virtual bool hasArg(const String& name);
virtual String arg(const String name);
virtual void sendHeader(const String& name, const String& value, bool first = false);
virtual void setContentLength(const size_t contentLength);
virtual void send(int code, const char* content_type = nullptr, const String& content = String(""));
virtual void sendContent(const String& content);
virtual void stop();
};
class WebServerWrapper
{
public:
virtual void handleClient();
virtual void begin();
};
} // end namespace
#endif
\ No newline at end of file
#ifndef CustomAmpelIotWebConfSettings_h
#define CustomAmpelIotWebConfSettings_h
/***************************************/
/**** CUSTOM AMPEL CODE ****************/
// Disable DEBUG, in order to save some space, at least on ESP8266
#define IOTWEBCONF_DEBUG_DISABLED
// Change this value (between 0 & 3000) in order to force the config to be reloaded.
#define IOTWEBCONF_CONFIG_START 512
#endif
\ No newline at end of file
NTPClient 3.1.0 - 2016.05.31
* Added functions for changing the timeOffset and updateInterval later. Thanks @SirUli
NTPClient 3.0.0 - 2016.04.19
* Constructors now require UDP instance argument, to add support for non-ESP8266 boards
* Added optional begin API to override default local port
* Added end API to close UDP socket
* Changed return type of update and forceUpdate APIs to bool, and return success or failure
* Change return type of getDay, getHours, getMinutes, and getSeconds to int
Older
* Changes not recorded
/**
* The MIT License (MIT)
* Copyright (c) 2015 by Fabrice Weinberg
*
* 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.
*/
#include "NTPClient.h"
NTPClient::NTPClient(UDP& udp) {
this->_udp = &udp;
}
NTPClient::NTPClient(UDP& udp, long timeOffset) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName) {
this->_udp = &udp;
this->_poolServerName = poolServerName;
}
NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP) {
this->_udp = &udp;
this->_poolServerIP = poolServerIP;
this->_poolServerName = NULL;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName;
}
NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset){
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerIP = poolServerIP;
this->_poolServerName = NULL;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName;
this->_updateInterval = updateInterval;
}
NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerIP = poolServerIP;
this->_poolServerName = NULL;
this->_updateInterval = updateInterval;
}
void NTPClient::begin() {
this->begin(NTP_DEFAULT_LOCAL_PORT);
}
void NTPClient::begin(unsigned int port) {
this->_port = port;
this->_udp->begin(this->_port);
this->_udpSetup = true;
}
bool NTPClient::forceUpdate() {
#ifdef DEBUG_NTPClient
Serial.println("Update from NTP Server");
#endif
// flush any existing packets
while(this->_udp->parsePacket() != 0)
this->_udp->flush();
this->sendNTPPacket();
// Wait till data is there or timeout...
byte timeout = 0;
int cb = 0;
do {
delay ( 10 );
cb = this->_udp->parsePacket();
if (timeout > 100) return false; // timeout after 1000 ms
timeout++;
} while (cb == 0);
this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time
this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE);
unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]);
unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
this->_currentEpoc = secsSince1900 - SEVENZYYEARS;
return true; // return true after successful update
}
bool NTPClient::update() {
if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval
|| this->_lastUpdate == 0) { // Update if there was no update yet.
if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed
return this->forceUpdate();
}
return false; // return false if update does not occur
}
bool NTPClient::isTimeSet() const {
return (this->_lastUpdate != 0); // returns true if the time has been set, else false
}
unsigned long NTPClient::getEpochTime() const {
return this->_timeOffset + // User offset
this->_currentEpoc + // Epoch returned by the NTP server
((millis() - this->_lastUpdate) / 1000); // Time since last update
}
int NTPClient::getDay() const {
return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday
}
int NTPClient::getHours() const {
return ((this->getEpochTime() % 86400L) / 3600);
}
int NTPClient::getMinutes() const {
return ((this->getEpochTime() % 3600) / 60);
}
int NTPClient::getSeconds() const {
return (this->getEpochTime() % 60);
}
void NTPClient::end() {
this->_udp->stop();
this->_udpSetup = false;
}
void NTPClient::setTimeOffset(int timeOffset) {
this->_timeOffset = timeOffset;
}
void NTPClient::setUpdateInterval(unsigned long updateInterval) {
this->_updateInterval = updateInterval;
}
void NTPClient::setPoolServerName(const char* poolServerName) {
this->_poolServerName = poolServerName;
}
void NTPClient::sendNTPPacket() {
// set all bytes in the buffer to 0
memset(this->_packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode
this->_packetBuffer[1] = 0; // Stratum, or type of clock
this->_packetBuffer[2] = 6; // Polling Interval
this->_packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
this->_packetBuffer[12] = 49;
this->_packetBuffer[13] = 0x4E;
this->_packetBuffer[14] = 49;
this->_packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
if (this->_poolServerName) {
this->_udp->beginPacket(this->_poolServerName, 123);
} else {
this->_udp->beginPacket(this->_poolServerIP, 123);
}
this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE);
this->_udp->endPacket();
}
void NTPClient::setRandomPort(unsigned int minValue, unsigned int maxValue) {
randomSeed(analogRead(0));
this->_port = random(minValue, maxValue);
}
/*** Custom code for ampel-firmware ***/
void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) {
unsigned long rawTime = secs ? secs : this->getEpochTime();
unsigned int hours = (rawTime % 86400L) / 3600;
unsigned int minutes = (rawTime % 3600) / 60;
unsigned int seconds = rawTime % 60;
snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds);
}
#define LEAP_YEAR(Y) ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ) )
// Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp
void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) {
unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L; // in days
unsigned long days = 0;
unsigned int year = 1970;
uint8_t month;
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};
while((days += (LEAP_YEAR(year) ? 366 : 365)) <= rawTime)
year++;
rawTime -= days - (LEAP_YEAR(year) ? 366 : 365); // now it is days in this year, starting at 0
days=0;
for (month=0; month<12; month++) {
uint8_t monthLength;
if (month==1) { // february
monthLength = LEAP_YEAR(year) ? 29 : 28;
} else {
monthLength = monthDays[month];
}
if (rawTime < monthLength) break;
rawTime -= monthLength;
}
month++; // jan is month 1
rawTime++; // first day is day 1
char formatted_time[9];
this->getFormattedTime(formatted_time, secs);
snprintf(formatted_date, 23, "%4d-%02d-%02lu %s%+03ld", year, month, rawTime, formatted_time, (this->_timeOffset / 3600) % 100);
}
void NTPClient::setEpochTime(unsigned long secs) {
this->_currentEpoc = secs;
}
/**************************************************************/
\ No newline at end of file
#pragma once
#include "Arduino.h"
#include <Udp.h>
#define SEVENZYYEARS 2208988800UL
#define NTP_PACKET_SIZE 48
#define NTP_DEFAULT_LOCAL_PORT 1337
class NTPClient {
private:
UDP* _udp;
bool _udpSetup = false;
const char* _poolServerName = "pool.ntp.org"; // Default time server
IPAddress _poolServerIP;
unsigned int _port = NTP_DEFAULT_LOCAL_PORT;
long _timeOffset = 0;
unsigned long _updateInterval = 60000; // In ms
unsigned long _currentEpoc = 0; // In ms
unsigned long _lastUpdate = 0; // In ms
byte _packetBuffer[NTP_PACKET_SIZE];
void sendNTPPacket();
public:
NTPClient(UDP& udp);
NTPClient(UDP& udp, long timeOffset);
NTPClient(UDP& udp, const char* poolServerName);
NTPClient(UDP& udp, const char* poolServerName, long timeOffset);
NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval);
NTPClient(UDP& udp, IPAddress poolServerIP);
NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset);
NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval);
/**
* Set time server name
*
* @param poolServerName
*/
void setPoolServerName(const char* poolServerName);
/**
* Set random local port
*/
void setRandomPort(unsigned int minValue = 49152, unsigned int maxValue = 65535);
/**
* Starts the underlying UDP client with the default local port
*/
void begin();
/**
* Starts the underlying UDP client with the specified local port
*/
void begin(unsigned int port);
/**
* This should be called in the main loop of your application. By default an update from the NTP Server is only
* made every 60 seconds. This can be configured in the NTPClient constructor.
*
* @return true on success, false on failure
*/
bool update();
/**
* This will force the update from the NTP Server.
*
* @return true on success, false on failure
*/
bool forceUpdate();
/**
* This allows to check if the NTPClient successfully received a NTP packet and set the time.
*
* @return true if time has been set, else false
*/
bool isTimeSet() const;
int getDay() const;
int getHours() const;
int getMinutes() const;
int getSeconds() const;
/**
* Changes the time offset. Useful for changing timezones dynamically
*/
void setTimeOffset(int timeOffset);
/**
* Set the update interval to another frequency. E.g. useful when the
* timeOffset should not be set in the constructor
*/
void setUpdateInterval(unsigned long updateInterval);
/**
* @return time in seconds since Jan. 1, 1970
*/
unsigned long getEpochTime() const;
/**
* Stops the underlying UDP client
*/
void end();
/*** Custom code for ampel-firmware ***/
/**
* @return secs argument (or 0 for current time) formatted like `hh:mm:ss`
*/
void getFormattedTime(char *formatted_time, unsigned long secs = 0);
/**
* @return secs argument (or 0 for current date) formatted to ISO 8601
* like `2004-02-12T15:19:21+00:00`
*/
void getFormattedDate(char *formatted_date, unsigned long secs = 0);
/**
* Replace the NTP-fetched time with seconds since Jan. 1, 1970
*/
void setEpochTime(unsigned long secs);
/**************************************************************/
};
# NTPClient
[![Check Arduino status](https://github.com/arduino-libraries/NTPClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/NTPClient/actions/workflows/check-arduino.yml)
[![Compile Examples status](https://github.com/arduino-libraries/NTPClient/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/NTPClient/actions/workflows/compile-examples.yml)
[![Spell Check status](https://github.com/arduino-libraries/NTPClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/NTPClient/actions/workflows/spell-check.yml)
Connect to a NTP server, here is how:
```cpp
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionally you can specify the update interval (in milliseconds).
// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
}
void loop() {
timeClient.update();
Serial.println(timeClient.getFormattedTime());
delay(1000);
}
```
## Function documentation
`getEpochTime` returns the Unix epoch, which are the seconds elapsed since 00:00:00 UTC on 1 January 1970 (leap seconds are ignored, every day is treated as having 86400 seconds). **Attention**: If you have set a time offset this time offset will be added to your epoch timestamp.
#######################################
# Datatypes (KEYWORD1)
#######################################
NTPClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
update KEYWORD2
forceUpdate KEYWORD2
isTimeSet KEYWORD2
getDay KEYWORD2
getHours KEYWORD2
getMinutes KEYWORD2
getSeconds KEYWORD2
getFormattedTime KEYWORD2
getEpochTime KEYWORD2
setTimeOffset KEYWORD2
setUpdateInterval KEYWORD2
setPoolServerName KEYWORD2
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