diff --git a/.gitignore b/.gitignore index 3c4efe2..aa42daa 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,7 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc + + +tmp/* diff --git a/LICENSE b/LICENSE index e69e113..760aeba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Tim van den Essen +Copyright (c) 2018 Tim van den Essen, 2019 Gerd Langer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index dcd2e6f..180611c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # ConfigTool +ESP32 and ESP8266 Arduino library for easy saving and storing config variables. -Arduino or ESP8266 libraryy for easy saving and storing config variables. -Also has a handler to edit them via a webserver. - -See [this example](https://github.com/Tvde1/ConfigTool/blob/master/examples/ConfigTool/ConfigTool.ino). +Handler to edit config via a webserver added in simple layout. +Each config variables can be hidden fully or its value. diff --git a/examples/WebConfig/WebConfig.ino b/examples/WebConfig/WebConfig.ino new file mode 100644 index 0000000..e7d3d10 --- /dev/null +++ b/examples/WebConfig/WebConfig.ino @@ -0,0 +1,106 @@ +/** + * WebServer for ConfigTool. + */ + +#include + +#if defined(ARDUINO_ARCH_ESP8266) //ESP8266 + #include + #include + #include + #define MDNS_NAME "esp8266" +#elif defined(ARDUINO_ARCH_ESP32) //ESP32 + #include + #include + #include + #include + #define MDNS_NAME "esp32" +#endif + +const char* ssid = ""; +const char* password = ""; + +String config_String_1 = "Default"; +String config_String_2 = "Test"; +int config_int_1 = 100; +int config_int_2 = 200; +bool config_bool_F = false; +bool config_bool_T = true; +IPAddress config_IP_1 = IPAddress(192,168,1,99); + +ConfigTool configTool; +WebServer server(80); + +void initVariables() { + configTool.addVariable("String1", &config_String_1); + configTool.addVariable("String2", &config_String_2, false, true); + configTool.addVariable("int___1", &config_int_1); + configTool.addVariable("int___2", &config_int_2, true); + configTool.addVariable("bool__F", &config_bool_F); + configTool.addVariable("bool__T", &config_bool_T); + configTool.addVariable("bool__X", &config_bool_T, false, true); + configTool.addVariable("IP____1", &config_IP_1); +} + + +void handleRoot() { + server.send(200, "text/plain", "hello from esp!"); +} + +void handleNotFound() { + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); +} + +void setup(){ + Serial.begin(115200); + Serial.println("Setup begin"); + + initVariables(); + configTool.load(); + + WiFi.mode(WIFI_STA); + Serial.print("MAC: "); + Serial.println(WiFi.macAddress()); + + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + if (MDNS.begin(MDNS_NAME)) { + Serial.println("MDNS responder started"); + } + + server.on("/", handleRoot); + + server.on("/config", configTool.getWebHandler(&server)); + + server.onNotFound(handleNotFound); + + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + server.handleClient(); +} diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..b87e924 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,13 @@ +# main class +ConfigTool KEYWORD1 + +# structs for variables +BaseVar KEYWORD3 +ConfigVar KEYWORD3 + +# public functions of ConfigTool +load KEYWORD2 +save KEYWORD2 +reset KEYWORD2 +addVariable KEYWORD2 +getWebHandler KEYWORD2 diff --git a/library.properties b/library.properties index f6a6231..ce166be 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,10 @@ name=ConfigTool -version=1.0 -author=Tvde1 -maintainer=Tvde1 +version=1.1 +author=Tvde1,gerdlanger +maintainer=gerdlanger sentence=Save config variable and edit them online. paragraph=No more hardcoding. This library will save and load config variables and you can edit them at an endpoint you choose. category=Data Storage -url=https://github.com/Tvde1/ConfigTool -includes=ConfigTool.h \ No newline at end of file +url=https://github.com/gerdlanger/ConfigTool +includes=ConfigTool.h +architectures=esp32,esp8266 \ No newline at end of file diff --git a/src/ConfigTool.cpp b/src/ConfigTool.cpp index 4cc2a63..8e1e710 100644 --- a/src/ConfigTool.cpp +++ b/src/ConfigTool.cpp @@ -3,22 +3,27 @@ Author: Tvde1 */ -#include "ConfigTool.h"; -#include "FS.h"; -#include "ArduinoJson.h"; -#include "ESP8266WebServer.h"; -#include ; +#include "ConfigTool.h" +#include +#if defined(ARDUINO_ARCH_ESP32) //ESP32 + #include +#endif void ConfigTool::load() { - SPIFFS.begin(); +#if defined(ARDUINO_ARCH_ESP32) //ESP32 + if (!SPIFFS.begin(true)) { +#else + if (!SPIFFS.begin()) { +#endif + Serial.println("SPIFFS Failed"); + } File f = SPIFFS.open("/config.json", "r"); if (!f) { return; } - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.parseObject(f.readStringUntil('\n')); - + DynamicJsonDocument root(ConfigSize); + deserializeJson(root, f.readStringUntil('\n')); for (auto item : variables_) { item.second->deserialize(&root); } @@ -27,51 +32,151 @@ void ConfigTool::load() { } void ConfigTool::save() { - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); + DynamicJsonDocument root(ConfigSize); for (auto item : variables_) { item.second->serialize(&root); } - + SPIFFS.begin(); File f = SPIFFS.open("/config.json", "w"); + serializeJson(root, f); + f.close(); +} - String output = ""; - root.printTo(f); +void ConfigTool::reset() { + SPIFFS.begin(); + SPIFFS.remove("/config.json"); - f.close(); + for (auto item : variables_) { + item.second->reset(); + } } -String ConfigTool::createWebPage(bool updated) { - const String beginHtml = "Config Tool

ConfigTool Web Interface

Edit the config variables here and click save.

"; - const String continueHtml = "
"; - const String savedAlert = "
The config has been saved.
"; +String ConfigTool::createInput(BaseVar *item, String type) { + String result = "name + "\" type=\"" +type+ "\" value=\""; + + if (!item->hideValueInWeb) { + result += item->toString(); + } + result += "\" />"; + + return result; +} + +String ConfigTool::createBoolSelector(ConfigVar *item, String label, String display, bool checked) { + + String result = "name + "\" type=\"radio\" id=\"_" +label+ "_\" value=\"" +label+ "\" "; + + if (checked) { + result += "checked "; + } + result += "> "; - const String endHtml = "
"; + return result; +} + +String ConfigTool::createWebPage(bool updated) { + static const String beginHtml = + "" + "" + "Config Tool" + "" + "" + "

ConfigTool Web Interface

" + "

Edit the config variables here and click save.

"; + + static const String savedAlert = + "

The config has been saved.

"; + + static const String continueHtml = + "
" + ""; + + static const String endHtml = + "
" + "

" + "

" + " be careful!" + "

" + "
" + "" + ""; String result = beginHtml; if (updated) { result += savedAlert; } - result += continueHtml; for (auto item : variables_) { - result += "
toString() + "\" />
"; + + if (item.second->hideInWeb) continue; + + result += + "" + "" + ""; + + switch (item.second->varType()) { + + case VT_TEXT: + result += createInput(item.second, "text"); + //hideValueInWeb) result += item.second->toString(); + //result += "\" />"; + break; + + case VT_NUM: + result += createInput(item.second, "number"); + //result += "hideValueInWeb) result += item.second->toString(); + //result += "\" />"; + break; + + case VT_IP: + result += createInput(item.second, "text"); + break; + + case VT_BOOL: + bool val = *(((ConfigVar*)item.second)->pointer); + result += "
"; + result += createBoolSelector((ConfigVar*)item.second, "true", "on", val && !item.second->hideValueInWeb); + result += createBoolSelector((ConfigVar*)item.second, "false", "off", !val && !item.second->hideValueInWeb); +// result += createBoolTag(item.first, "true", "on", val && !item.second->hideValueInWeb); +// result += createBoolTag(item.first, "false", "off", !val && !item.second->hideValueInWeb); + result += "
"; + break; + + } + + result += + "" + ""; } +/* + result+=""; + File f = SPIFFS.open("/config.json", "r"); + while (f and f.available()) { + result += f.read(); + } + result+=""; +*/ + result += endHtml; + //Serial.println(result); + return result; } -std::function ConfigTool::getWebHandler(ESP8266WebServer* server) { +std::function ConfigTool::getWebHandler(WebServer* server) { return [this, server]() { bool updated = false; - if (server->args() == 1 && server->hasArg("reset") && server->arg("reset") == "true") { + if (/*server->args() == 1 &&*/ server->hasArg("reset")) { //} && server->arg("reset") == "true") { Serial.println("Reset is true."); for (auto item : variables_) { item.second->reset(); @@ -79,18 +184,25 @@ std::function ConfigTool::getWebHandler(ESP8266WebServer* server) { updated = true; } else { - Serial.write("Args hit: "); for (int i = 0; i < server->args(); i++) { String name = server->argName(i); + auto item = variables_.find(name); - Serial.print(name + " | "); if (item == variables_.end()) { continue; } + String val = server->arg(name); + + Serial.println(name + "=" + val); + + // skip empty input for values that are not displayed + if (item->second->hideValueInWeb && val == "") { + continue; + } + updated = true; - item->second->fromString(server->arg(name)); + item->second->fromString(val); } - Serial.println(); } save(); @@ -98,13 +210,4 @@ std::function ConfigTool::getWebHandler(ESP8266WebServer* server) { server->send(200, "text/html", html); return; }; -} - -void ConfigTool::reset() { - SPIFFS.begin(); - SPIFFS.remove("/config.json"); - - for (auto item : variables_) { - item.second->reset(); - } -} +} \ No newline at end of file diff --git a/src/ConfigTool.h b/src/ConfigTool.h index 2d416e8..d5febd6 100644 --- a/src/ConfigTool.h +++ b/src/ConfigTool.h @@ -2,38 +2,52 @@ Name: ConfigTool.h Author: Tvde1 */ - -#include ; -#include ; -#include "ArduinoJson.h" -#include "ESP8266WebServer.h"; -#include ; - #ifndef _ConfigTool_h #define _ConfigTool_h +#include +#include + +#if defined(ARDUINO_ARCH_ESP8266) //ESP8266 + #include + #define WebServer ESP8266WebServer +#elif defined(ARDUINO_ARCH_ESP32) //ESP32 + #include + #include +#else + #error "unsupported architecture - intended for ESP32 and ESP8266" +#endif + +#define VT_TEXT 1 +#define VT_BOOL 2 +#define VT_NUM 3 +#define VT_IP 4 + struct BaseVar { String name; - virtual void serialize(JsonObject*) = 0; - virtual void deserialize(JsonObject*) = 0; + bool hideInWeb = false; + bool hideValueInWeb = false; + + virtual void serialize(JsonDocument*) = 0; + virtual void deserialize(JsonDocument*) = 0; virtual void reset() = 0; virtual String toString() = 0; virtual void fromString(String) = 0; + + virtual int varType() = 0; }; template struct ConfigVar : BaseVar { ConfigVar(String n, T* p) {}; - void deserialize(JsonObject *json) {}; - - void serialize(JsonObject* json) {}; - + void deserialize(JsonDocument* json) {}; + void serialize(JsonDocument* json) {}; void reset() {}; - String toString() { return ""; }; - void fromString(String) {}; + + int varType() {return -1;}; }; template <> @@ -46,16 +60,12 @@ struct ConfigVar : BaseVar { defaultValue = *p; }; - void deserialize(JsonObject *json) { - if (json->containsKey(name) && json->is(name)) { - - *pointer = String{ json->get(name) }; - json->remove(name); - } + void deserialize(JsonDocument* json) { + *pointer = String{ (*json)[name] | defaultValue }; } - void serialize(JsonObject* json) { - json->set(name, *pointer); + void serialize(JsonDocument* json) { + (*json)[name] = *pointer; } void reset() { @@ -69,6 +79,10 @@ struct ConfigVar : BaseVar { void fromString(String value) { *pointer = value; } + + int varType() { + return VT_TEXT; + } }; template <> @@ -81,16 +95,14 @@ struct ConfigVar : BaseVar { defaultValue = *p; }; - void deserialize(JsonObject *json) { - if (json->containsKey(name) && json->is(name)) { - - *pointer = json->get(name); - json->remove(name); + void deserialize(JsonDocument* json) { + if (!(*json)[name].isNull()) { + *pointer = (*json)[name]; } } - void serialize(JsonObject* json) { - json->set(name, *pointer); + void serialize(JsonDocument* json) { + (*json)[name] = *pointer; } void reset() { @@ -104,6 +116,10 @@ struct ConfigVar : BaseVar { void fromString(String value) { *pointer = value == "true"; } + + int varType() { + return VT_BOOL; + } }; template <> @@ -116,15 +132,14 @@ struct ConfigVar : BaseVar { defaultValue = *p; }; - void deserialize(JsonObject *json) { - if (json->containsKey(name) && json->is(name)) { - *pointer = json->get(name); - json->remove(name); + void deserialize(JsonDocument* json) { + if (!(*json)[name].isNull()) { + *pointer = (*json)[name]; } } - void serialize(JsonObject* json) { - json->set(name, *pointer); + void serialize(JsonDocument* json) { + (*json)[name] = *pointer; } void reset() { @@ -138,20 +153,76 @@ struct ConfigVar : BaseVar { void fromString(String value) { *pointer = value.toInt(); } + + int varType() { + return VT_NUM; + } +}; + +template <> +struct ConfigVar : BaseVar { + IPAddress* pointer; + IPAddress defaultValue; + ConfigVar(String n, IPAddress* p) { + name = n; + pointer = p; + defaultValue = *p; + }; + + void deserialize(JsonDocument* json) { + if (!(*json)[name].isNull()) { + String txt = String{ (*json)[name] | "" }; + Serial.println(txt); + if (!pointer->fromString(txt)) { + reset(); + } + } + } + + void serialize(JsonDocument* json) { + (*json)[name] = toString(); + } + + void reset() { + *pointer = uint32_t(defaultValue); + } + + String toString() { + return pointer->toString(); + } + + void fromString(String value) { + pointer->fromString(value); + } + + int varType() { + return VT_IP; + } }; + struct ConfigTool { public: + int ConfigSize = 1024; + template - void addVariable(String name, T* pointer) { - variables_[name] = (new ConfigVar(name, pointer)); + void addVariable(String name, T* pointer, bool hide = false, bool hideVal = false) { + ConfigVar* var = new ConfigVar(name, pointer); + var->hideInWeb = hide; + var->hideValueInWeb = hideVal; + variables_[name] = var; }; + void load(); void save(); - std::function getWebHandler(ESP8266WebServer*); void reset(); + + std::function getWebHandler(WebServer*); + private: std::map variables_; + String createInput(BaseVar*, String); + String createBoolSelector(ConfigVar*, String, String, bool); String createWebPage(bool); }; diff --git a/test/ConfigToolTest/ConfigToolTest.ino b/test/ConfigToolTest/ConfigToolTest.ino new file mode 100644 index 0000000..016d425 --- /dev/null +++ b/test/ConfigToolTest/ConfigToolTest.ino @@ -0,0 +1,113 @@ +/** + * Test functionality of ConfigTool. + * + * TAKE CARE !!! This DELETES exisintg "/config.json" files !!! + * + */ + +#ifdef ARDUINO_ARCH_ESP8266 + #include +#elif ARDUINO_ARCH_ESP32 + #include +#endif +#include + +String config_String_1 = "Default"; +String config_String_2 = "Test"; +int config_int_1 = 100; +int config_int_2 = 200; +bool config_bool_F = false; +bool config_bool_T = true; + +ConfigTool configTool; + +bool dumpConfig(bool check){ + + File file = SPIFFS.open("/config.json", "r"); + if(!file){ + Serial.println("reading - config failed"); + return false; + } + + Serial.println("reading - dumpfile:"); + while(file.available()){ + Serial.write(file.read()); + } + Serial.println(); + // if requested, print the comparison for "zeroVariables": + if (check) Serial.println("{\"String1\":\"-1-\",\"String2\":\"-2-\",\"bool__F\":false,\"bool__T\":false,\"int___1\":0,\"int___2\":0}"); + + return true; +} + +void initVariables() { + configTool.addVariable("String1", &config_String_1); + configTool.addVariable("String2", &config_String_2); + configTool.addVariable("int___1", &config_int_1); + configTool.addVariable("int___2", &config_int_2); + configTool.addVariable("bool__F", &config_bool_F); + configTool.addVariable("bool__T", &config_bool_T); +} + +void zeroVariables() { + config_String_1 = "-1-"; + config_String_2 = "-2-"; + config_int_1 = 0; + config_int_2 = 0; + config_bool_F = false; + config_bool_T = false; +} + +void checkDefaultValues() { + Serial.printf("String1 OK=%d\n", (config_String_1 == "Default")); + Serial.printf("String2 OK=%d\n", (config_String_2 == "Test")); + Serial.printf("int1 OK=%d\n", (config_int_1 == 100)); + Serial.printf("int2 OK=%d\n", (config_int_2 == 200)); + Serial.printf("bool_F OK=%d\n", (config_bool_F == false)); + Serial.printf("bool_T OK=%d\n", (config_bool_T == true)); +} + +void setup(){ + Serial.begin(115200); + Serial.println("Setup begin"); + + if(!SPIFFS.begin()){ + Serial.println("setup - SPIFFS begin failed"); + return; + } + + Serial.println("setup - dump at start"); + dumpConfig(true); + + Serial.println("setup - init variables to default and save"); + initVariables(); + configTool.save(); + dumpConfig(false); + + Serial.println("setup - manipulate variables and load again"); + zeroVariables(); + configTool.load(); + checkDefaultValues(); + + Serial.println("setup - manipulate variables and save"); + zeroVariables(); + configTool.save(); + dumpConfig(true); + + Serial.println("setup - reset stored values"); + configTool.reset(); + dumpConfig(false); + checkDefaultValues(); + + Serial.println("setup - again manipulate variables and save"); + zeroVariables(); + configTool.save(); + dumpConfig(true); + + Serial.println( "Setup end" ); + Serial.println( "Test complete" ); +} + +void loop(){ + // NOP +} \ No newline at end of file