diff --git a/IotWebConf.code-workspace b/IotWebConf.code-workspace index fadc025..3cd0ab8 100644 --- a/IotWebConf.code-workspace +++ b/IotWebConf.code-workspace @@ -4,56 +4,61 @@ "path": "." }, { - "path": "examples-pio/IotWebConf01Minimal" + "path": "../IotWebConf-examples/IotWebConf01Minimal" }, { - "path": "examples-pio/IotWebConf02StatusAndReset" + "path": "../IotWebConf-examples/IotWebConf02StatusAndReset" }, { - "path": "examples-pio/IotWebConf03CustomParameters" + "path": "../IotWebConf-examples/IotWebConf03CustomParameters" }, - { - "path": "examples-pio/IotWebConf03TypedParameters" - }, { - "path": "examples-pio/IotWebConf04UpdateServer" + "path": "../IotWebConf-examples/IotWebConf03TypedParameters" }, { - "path": "examples-pio/IotWebConf05Callbacks" + "path": "../IotWebConf-examples/IotWebConf04UpdateServer" }, { - "path": "examples-pio/IotWebConf06MqttApp" + "path": "../IotWebConf-examples/IotWebConf05Callbacks" }, { - "path": "examples-pio/IotWebConf07MqttRelay" + "path": "../IotWebConf-examples/IotWebConf06MqttApp" }, { - "path": "examples-pio/IotWebConf08WebRelay" + "path": "../IotWebConf-examples/IotWebConf07MqttRelay" }, { - "path": "examples-pio/IotWebConf09CustomConnection" + "path": "../IotWebConf-examples/IotWebConf08WebRelay" }, { - "path": "examples-pio/IotWebConf10CustomHtml" + "path": "../IotWebConf-examples/IotWebConf09CustomConnection" }, { - "path": "examples-pio/IotWebConf11AdvancedRuntime" + "path": "../IotWebConf-examples/IotWebConf10CustomHtml" }, { - "path": "examples-pio/IotWebConf12CustomParameterType" + "path": "../IotWebConf-examples/IotWebConf11AdvancedRuntime" }, { - "path": "examples-pio/IotWebConf13OptionalGroup" + "path": "../IotWebConf-examples/IotWebConf12CustomParameterType" }, { - "path": "examples-pio/IotWebConf14GroupChain" + "path": "../IotWebConf-examples/IotWebConf13OptionalGroup" }, { - "path": "examples-pio/IotWebConf15MultipleWifi" + "path": "../IotWebConf-examples/IotWebConf14GroupChain" }, { - "path": "examples-pio/IotWebConf16OffLineMode" + "path": "../IotWebConf-examples/IotWebConf15MultipleWifi" }, + { + "path": "../IotWebConf-examples/IotWebConf16OffLineMode" + }, + { + "path": "../IotWebConf-examples/IotWebConf17JsonConfig" + } ], - "settings": {} -} \ No newline at end of file + "settings": { + "workbench.tree.indent": 16 + } +} diff --git a/examples/IotWebConf17JsonConfig/README.txt b/examples/IotWebConf17JsonConfig/README.txt new file mode 100644 index 0000000..198e136 --- /dev/null +++ b/examples/IotWebConf17JsonConfig/README.txt @@ -0,0 +1,31 @@ +Example: Load config from JSON file on FS +Description: + This example is based on the "03 Custom Parameters" example. + The idea is that we prepare a configuration file located + in the flash filesystem. When this file exists, it is loaded + into the IotWebConf parameters and the changes are saved. + The used config file is then deleted from the flash FS. + NOTE! JSON support is not available by default, you need to + enable it with the IOTWEBCONF_ENABLE_JSON compile time directive. + Please read further sections for more details! + Please compare this source code with "03 Custom Parameters"! + +Using this example: + This example is intended to be used under PlatformIO, as PIO + provides compile time configurations and FS upload capabilities. + With PlatformIO you must use "build_flags = -DIOTWEBCONF_ENABLE_JSON" + as seen in the "platformio.ini". + +Preparing configuration file: + You can find a "config.json" file in the "data" subfolder + containing the example values. Your project must contains + this "data" folder. Use PlatformIOs "Build file system image" + and "Upload file system image" to upload the config.json to the + flash area. The file is deleted after it has been processed, so + you might want to re-upload it several times while testing this + example. + +Hardware setup for this example: + - An LED is attached to LED_BUILTIN pin with setup On=LOW. + - [Optional] A push button is attached to pin D2, the other leg of the + button should be attached to GND. diff --git a/examples/IotWebConf17JsonConfig/data/config.json b/examples/IotWebConf17JsonConfig/data/config.json new file mode 100644 index 0000000..cb5f5ec --- /dev/null +++ b/examples/IotWebConf17JsonConfig/data/config.json @@ -0,0 +1,22 @@ +{ + "iwcAll": { + "iwcSys": { + "iwcThingName": "nameFromJSON", + "iwcApPassword": "smrtTHNG8266", + "iwcApTimeout": "40", + "iwcWifi0": { + "iwcWifiSsid": "myNet", + "iwcWifiPassword": "myNetPwd" + }, + "stringParam": "Value from JSON" + }, + "iwcCustom": { + "c_factor": { + "checkParam": "", + "chooseParam": "blue" + } + }, + "hidden": { + } + } +} \ No newline at end of file diff --git a/examples/IotWebConf17JsonConfig/platformio.ini b/examples/IotWebConf17JsonConfig/platformio.ini new file mode 100644 index 0000000..ca62865 --- /dev/null +++ b/examples/IotWebConf17JsonConfig/platformio.ini @@ -0,0 +1,32 @@ +[common] +build_flags = + -DIOTWEBCONF_ENABLE_JSON +lib_deps = + bblanchon/ArduinoJson + IotWebConf + +[env:d1_mini] +platform = espressif8266 +board = d1_mini +framework = arduino +board_build.filesystem = littlefs +monitor_speed = 115200 +build_flags = + ${common.build_flags} +;upload_port = /dev/cu.SLAB_USBtoUART +lib_deps = + ${common.lib_deps} + +[env:d1_mini32] +platform = espressif32 +board = wemos_d1_mini32 +framework = arduino +board_build.filesystem = littlefs +monitor_speed = 115200 +build_flags = + ${common.build_flags} +;monitor_port = /dev/cu.SLAB_USBtoUART +;upload_port = /dev/cu.SLAB_USBtoUART +lib_deps = + ${common.lib_deps} + lorol/LittleFS_esp32 diff --git a/examples/IotWebConf17JsonConfig/src/main.cpp b/examples/IotWebConf17JsonConfig/src/main.cpp new file mode 100644 index 0000000..9580250 --- /dev/null +++ b/examples/IotWebConf17JsonConfig/src/main.cpp @@ -0,0 +1,209 @@ +/** + * IotWebConf17JsonConfig.ino -- IotWebConf is an ESP8266/ESP32 + * non blocking WiFi/AP web configuration library for Arduino. + * https://github.com/prampec/IotWebConf + * + * Copyright (C) 2021 Balazs Kelemen + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +/** + * Please read description in README.txt! + */ + +#include +#include // This loads aliases for easier class names. +#ifndef ESP32 +# include +#else +# include +# define LittleFS LITTLEFS +#endif +#include + +#ifndef IOTWEBCONF_ENABLE_JSON +# error platformio.ini must contain "build_flags = -DIOTWEBCONF_ENABLE_JSON" +#endif + +// -- Initial name of the Thing. Used e.g. as SSID of the own Access Point. +const char thingName[] = "testThing"; + +// -- Initial password to connect to the Thing, when it creates an own Access Point. +const char wifiInitialApPassword[] = "smrtTHNG8266"; + +#define STRING_LEN 128 +#define NUMBER_LEN 32 + +// -- Configuration specific key. The value should be modified if config structure was changed. +#define CONFIG_VERSION "dem2" + +// -- When CONFIG_PIN is pulled to ground on startup, the Thing will use the initial +// password to buld an AP. (E.g. in case of lost password) +#define CONFIG_PIN D2 + +// -- Status indicator pin. +// First it will light up (kept LOW), on Wifi connection it will blink, +// when connected to the Wifi it will turn off (kept HIGH). +#define STATUS_PIN LED_BUILTIN + +// -- File name on the filesystem containing the configuration in JSON format. +#define CONFIG_FILE_NAME "config.json" + +// -- Method declarations. +void handleRoot(); +void readConfigFile(); +// -- Callback methods. +void configSaved(); +bool formValidator(iotwebconf::WebRequestWrapper* webRequestWrapper); + +DNSServer dnsServer; +WebServer server(80); + +char stringParamValue[STRING_LEN]; +char intParamValue[NUMBER_LEN]; +char floatParamValue[NUMBER_LEN]; +char checkboxParamValue[STRING_LEN]; +char chooserParamValue[STRING_LEN]; + +static char chooserValues[][STRING_LEN] = { "red", "blue", "darkYellow" }; +static char chooserNames[][STRING_LEN] = { "Red", "Blue", "Dark yellow" }; + +IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword, CONFIG_VERSION); +// -- You can also use namespace formats e.g.: iotwebconf::TextParameter +IotWebConfTextParameter stringParam = IotWebConfTextParameter("String param", "stringParam", stringParamValue, STRING_LEN); +IotWebConfParameterGroup group1 = IotWebConfParameterGroup("group1", ""); +IotWebConfNumberParameter intParam = IotWebConfNumberParameter("Int param", "intParam", intParamValue, NUMBER_LEN, "20", "1..100", "min='1' max='100' step='1'"); +// -- We can add a legend to the separator +IotWebConfParameterGroup group2 = IotWebConfParameterGroup("c_factor", "Calibration factor"); +IotWebConfNumberParameter floatParam = IotWebConfNumberParameter("Float param", "floatParam", floatParamValue, NUMBER_LEN, nullptr, "e.g. 23.4", "step='0.1'"); +IotWebConfCheckboxParameter checkboxParam = IotWebConfCheckboxParameter("Check param", "checkParam", checkboxParamValue, STRING_LEN, true); +IotWebConfSelectParameter chooserParam = IotWebConfSelectParameter("Choose param", "chooseParam", chooserParamValue, STRING_LEN, (char*)chooserValues, (char*)chooserNames, sizeof(chooserValues) / STRING_LEN, STRING_LEN); + +void setup() +{ + Serial.begin(115200); + Serial.println(); + Serial.println("Starting up..."); + + group1.addItem(&intParam); + group2.addItem(&floatParam); + group2.addItem(&checkboxParam); + group2.addItem(&chooserParam); + + iotWebConf.setStatusPin(STATUS_PIN); + iotWebConf.setConfigPin(CONFIG_PIN); + iotWebConf.addSystemParameter(&stringParam); + iotWebConf.addParameterGroup(&group1); + iotWebConf.addParameterGroup(&group2); + iotWebConf.setConfigSavedCallback(&configSaved); + iotWebConf.setFormValidator(&formValidator); + iotWebConf.getApTimeoutParameter()->visible = true; + + // -- Initializing the configuration. + bool validConfig = iotWebConf.init(); + // -- Reading the config file. You might want to limit this when + // no there is no validConfig in the EEPROM. + readConfigFile(); + + // -- Set up required URL handlers on the web server. + server.on("/", handleRoot); + server.on("/config", []{ iotWebConf.handleConfig(); }); + server.onNotFound([](){ iotWebConf.handleNotFound(); }); + + Serial.println("Ready."); +} + +void loop() +{ + // -- doLoop should be called as frequently as possible. + iotWebConf.doLoop(); +} + +/** + * Handle web requests to "/" path. + */ +void handleRoot() +{ + // -- Let IotWebConf test and handle captive portal requests. + if (iotWebConf.handleCaptivePortal()) + { + // -- Captive portal request were already served. + return; + } + String s = ""; + s += "IotWebConf 17 Json ConfigHello world!"; + s += "
    "; + s += "
  • String param value: "; + s += stringParamValue; + s += "
  • Int param value: "; + s += atoi(intParamValue); + s += "
  • Float param value: "; + s += atof(floatParamValue); + s += "
  • CheckBox selected: "; +// s += checkboxParam.isChecked(); + s += "
  • Option selected: "; + s += chooserParamValue; + s += "
"; + s += "Go to configure page to change values."; + s += "\n"; + + server.send(200, "text/html", s); +} + +void configSaved() +{ + Serial.println("Configuration was updated."); +} + +bool formValidator(iotwebconf::WebRequestWrapper* webRequestWrapper) +{ + Serial.println("Validating form."); + bool valid = true; + +/* + int l = webRequestWrapper->arg(stringParam.getId()).length(); + if (l < 3) + { + stringParam.errorMessage = "Please provide at least 3 characters for this test!"; + valid = false; + } +*/ + return valid; +} + +void readConfigFile() +{ + LittleFS.begin(); + File configFile = LittleFS.open(CONFIG_FILE_NAME, "r"); + if (configFile) + { + Serial.println(F("Reading config file")); + StaticJsonDocument<512> doc; + + DeserializationError error = deserializeJson(doc, configFile); + configFile.close(); + + if (error) + { + Serial.println(F("Failed to read file, using default configuration")); + return; + } + JsonObject documentRoot = doc.as(); + + // -- Apply JSON configuration. + iotWebConf.getRootParameterGroup()->loadFromJson(documentRoot); + iotWebConf.saveConfig(); + + // -- Remove file after finished loading it. + LittleFS.remove(CONFIG_FILE_NAME); + } + else + { + Serial.println(F("Config file not found, skipping.")); + } + + LittleFS.end(); +} + diff --git a/pio/build-pioexamples.sh b/pio/build-pioexamples.sh index 5fb26f6..991e968 100644 --- a/pio/build-pioexamples.sh +++ b/pio/build-pioexamples.sh @@ -4,7 +4,11 @@ # compatible folder structure. # Build will stop on any error. # -cd ../examples-pio || exit 1 +baseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. &> /dev/null && pwd )" +examplespio="../IotWebConf-examples" +target="${baseDir}/${examplespio}" + +cd ${target} || exit 1 for example in IotWebConf*; do echo "Compiling ${example}" diff --git a/pio/clean-pioexamples.sh b/pio/clean-pioexamples.sh index f78f571..bdea68e 100644 --- a/pio/clean-pioexamples.sh +++ b/pio/clean-pioexamples.sh @@ -6,9 +6,19 @@ # platformio.ini files will be removed alog with any additional added # files. # -cd ../examples-pio || exit 1 +baseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. &> /dev/null && pwd )" +examplespio="../IotWebConf-examples" +target="${baseDir}/${examplespio}" + +echo "Removing contents of ${target}" +cd ${target} || exit 1 for example in IotWebConf*; do - echo "Removing pio example ${example}" + echo " - ${example}" + rm -rf ${example}/.pio + rm -rf ${example}/lib rm -rf ${example} || exit $? -done \ No newline at end of file +done + +cd .. +rmdir ${target} \ No newline at end of file diff --git a/pio/prepare-pioexamples.sh b/pio/prepare-pioexamples.sh index 463b5e9..fafe9e5 100644 --- a/pio/prepare-pioexamples.sh +++ b/pio/prepare-pioexamples.sh @@ -6,26 +6,33 @@ # folder. # The output of this script can be added to the workspace of Visual Studio Code. # -conf=`pwd` -examplespio="examples-pio" -target=`pwd`"/../${examplespio}" +baseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. &> /dev/null && pwd )" +examplespio="../IotWebConf-examples" +target="${baseDir}/${examplespio}" test -e ${target} || mkdir ${target} -cd ../examples +cd ${baseDir}/examples for example in IotWebConf*; do - if [ ! -e "$target/$example" ]; then + if [ ! -e "${target}/$example" ]; then mkdir "$target/$example" - mkdir "$target/$example/src" - mkdir "$target/$example/lib" - if [ -e "$example/pio/platformio.ini" ]; then - cp "$example/pio/platformio.ini" "$target/$example/platformio.ini" + if [ -e "$example/platformio.ini" ]; then + for f in ${baseDir}/examples/$example/*; do + fb="$(basename -- $f)" + ln -s "${baseDir}/examples/$example/$fb" "$target/$example/" + done else - cp "$conf/platformio-template.ini" "$target/$example/platformio.ini" + mkdir "$target/$example/src" + if [ -e "$example/pio/platformio.ini" ]; then + cp -R "$example/pio/"* "$target/$example/" + else + cp "${baseDir}/pio/platformio-template.ini" "$target/$example/platformio.ini" + fi + ln -s "${baseDir}/examples/$example/$example.ino" "$target/$example/src/main.cpp" fi - ln -s "../../../examples/$example/$example.ino" "$target/$example/src/main.cpp" - ln -s "../../.." "$target/$example/lib/IotWebConf" + mkdir "$target/$example/lib" + ln -s "${baseDir}" "$target/$example/lib/IotWebConf" echo " {" echo " \"path\": \"${examplespio}/$example\"" echo " }," fi -done \ No newline at end of file +done diff --git a/src/IotWebConf.h b/src/IotWebConf.h index 4f04301..ae7739c 100644 --- a/src/IotWebConf.h +++ b/src/IotWebConf.h @@ -497,6 +497,10 @@ class IotWebConf * 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; diff --git a/src/IotWebConfParameter.cpp b/src/IotWebConfParameter.cpp index 4305fda..83aa153 100644 --- a/src/IotWebConfParameter.cpp +++ b/src/IotWebConfParameter.cpp @@ -175,6 +175,33 @@ void ParameterGroup::debugTo(Stream* out) } } +#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( @@ -232,6 +259,28 @@ 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 + /////////////////////////////////////////////////////////////////////////////// diff --git a/src/IotWebConfParameter.h b/src/IotWebConfParameter.h index 5568d7e..1f83691 100644 --- a/src/IotWebConfParameter.h +++ b/src/IotWebConfParameter.h @@ -17,6 +17,10 @@ #include #include +#ifdef IOTWEBCONF_ENABLE_JSON +# include +#endif + const char IOTWEBCONF_HTML_FORM_GROUP_START[] PROGMEM = "
{b}\n"; const char IOTWEBCONF_HTML_FORM_GROUP_END[] PROGMEM = @@ -109,6 +113,13 @@ class ConfigItem */ 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; }; @@ -126,6 +137,9 @@ class ParameterGroup : public ConfigItem 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; @@ -187,6 +201,9 @@ class Parameter : public ConfigItem int getLength() { return this->_length; } void applyDefaultValue() override; +#ifdef IOTWEBCONF_ENABLE_JSON + virtual void loadFromJson(JsonObject jsonObject) override; +#endif protected: // Overrides