define(`TITLE', `Configuration-Webserver for ESP8266 Projects') define(`DATE', `2018-01-26') define(`CONTENT', ` In my previous ESP8266 based weekend projects I always hardcoded configuration data. For the ESP8266 these are at least the WiFi credentials SSID and WPA key. Moving it into a different WiFi network requires re-flashing it. There must be a better way I thought and found that's a single line of code to run the ESP8266 as an accesspoint, which opens its own WiFi network. So, I first hardcoded a web page with a form to enter configuration data, a data structure to hold it and some code to store it into the EEPROM or load it there. It appears that this was an error-prone process with a lot of redudancy in the code. For that reason I wrote an approach with a small template-based generator script: [code language="python" title="configGen.py"] #!/usr/bin/python from Cheetah.Template import Template configItems = [ {"label":"_", "key":"magic", "type":"I", "default": ""}, {"label":"Wifi SSID", "key":"wifiSsid", "type":"C", "length":32, "default":"test"}, {"label":"Wifi Key", "key":"wifiKey", "type":"C", "length":64, "default":"geheim"}, {"label":"MQTT Broker", "key":"mqttBroker", "type":"C", "length":64, "default":"broker.hottis.de"}, {"label":"MQTT Username", "key":"mqttUser", "type":"C", "length":32, "default":"esp1"}, {"label":"MQTT Password", "key":"mqttPass", "type":"C", "length":32, "default":"geheim"}, {"label":"MQTT ClientId", "key":"mqttClientId", "type":"C", "length":32, "default":"changeThis"}, {"label":"MQTT Topic", "key":"mqttTopic", "type":"C", "length":64, "default":"IoT/espThermometer2/location/measurement"}, {"label":"MQTT Port", "key":"mqttPort", "type":"I", "default":8883}, {"label":"Measure Period", "key":"measurePeriod", "type":"I", "default":300} ] h_file = Template(file="configuration_h.tmpl", searchList=[{"configItems":configItems}]) open('configuration.h','w').write(str(h_file)) c_file = Template(file="configuration_c.tmpl", searchList=[{"configItems":configItems}]) open('configuration.cpp','w').write(str(c_file)) [/code] The two templates are: [code language="C" title="configuration_h.tmpl"] typedef struct { #for $configItem in $configItems #if $configItem.type == 'C' char ${configItem.key}[$configItem.length]; #else if $configItem.type == 'I' uint32_t $configItem.key; #end if #end for } tConfigBlock; extern const uint32_t MAGIC; extern tConfigBlock configBlock; void configServeIndex(); void configServeGetConfiguration(); void showConfiguration(); [/code] And: [code language="C" title="configuration_c.tmpl"] #raw #include <Arduino.h> #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <EEPROM.h> #include "defines.h" #include "configuration.h" #end raw tConfigBlock configBlock; const uint32_t MAGIC = 0xC0DE0001; extern ESP8266WebServer webServer; bool configSaved = false; void configServeIndex() { bool configValid = (configBlock.magic == MAGIC); if (! configValid) { #for $configItem in $configItems #if $configItem.label != "_" #if $configItem.type == "C" strcpy(configBlock.$configItem.key, "$configItem.default"); #else if $configItem.type == "I" configBlock.$configItem.key = $configItem.default; #end if #end if #end for } String buffer = "<!doctype html" "<html lang=\"en\">" " <head>" " <title>ESP8266 Thermometer Configuration Page</title>" " </head>" " <body>" " <h1>ESP8266 Configuration Page</h1>"; if (configSaved) { configSaved = false; buffer += "<h2>Configuration saved</h2>"; } buffer += " <form action=\"/config\" method=\"GET\">" " <table>" #for $configItem in $configItems #if $configItem.label != "_" " <tr>" " <td>" " <label for\"$configItem.key\">$configItem.label</label>" " </td><td>" " <input type=\"text\" name=\"$configItem.key\" id=\"$configItem.key\" "; #if $configItem.type == "C" buffer += " size=\"$configItem.length\" "; buffer += " value=\""; buffer += configBlock.$configItem.key; buffer += "\""; #else if $configItem.type == "I" buffer += " value=\""; buffer += configBlock.$configItem.key; buffer += "\""; #end if buffer += " />" " </td>" " </tr>" #end if #end for " <tr>" " <td colspan=\"2\">" " <button type=\"submit\">Save</button>" " </td>" " </tr>" " </table>" " </form>" " </body>" "</html>"; webServer.send(200, "text/html", buffer); #ifdef DEBUG Serial.println("indexHtml request served"); #endif } void configServeGetConfiguration() { String arg; #for $configItem in $configItems #if $configItem.label != "_" arg = webServer.arg("$configItem.key"); #if $configItem.type == "C" strcpy(configBlock.$configItem.key, arg.c_str()); #else if $configItem.type == "I" configBlock.$configItem.key = atoi(arg.c_str()); #end if #end if #end for configBlock.magic = MAGIC; showConfiguration(); EEPROM.begin(512); EEPROM.put(EEPROM_ADDR, configBlock); EEPROM.commit(); Serial.println("EEPROM saved"); configSaved = true; webServer.sendHeader("Location", String("/"), true); webServer.send(302, "text/plain", ""); //webServer.send(200, "text/html", "configuration saved"); } void showConfiguration() { Serial.println("Configuration is"); #for $configItem in $configItems Serial.print("$configItem.key = "); Serial.println(configBlock.$configItem.key); #end for Serial.println("---"); } [/code] Besides these generated files I also needed a framework to distinguish between "configuration mode" and "production mode" and in configuration mode to start the WiFi accesspoint. Since I'm programming the ESP8266 in an Eclipse-based Arduino-environment, this is all done in the `setup()` and `loop()` function: [code language="C"] void setup() { startTime = millis(); #ifdef DEBUG Serial.begin(115200); Serial.println("Starting ..."); #endif pinMode(CONFIG_SWITCH, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); EEPROM.begin(512); EEPROM.get(EEPROM_ADDR, configBlock); Serial.print("Magic: "); Serial.println(configBlock.magic); configMode = ((LOW == digitalRead(CONFIG_SWITCH)) || (configBlock.magic != MAGIC)); if (configMode) { #ifdef DEBUG Serial.println("Configuration mode"); #endif digitalWrite(LED_PIN, LOW); setupConfiguration(); } else { #ifdef DEBUG Serial.println("Production mode"); Serial.println(); Serial.println(); showConfiguration(); #endif digitalWrite(LED_PIN, HIGH); setupProduction(); } #ifdef DEBUG Serial.println("Started."); #endif } void loop() { if (configMode) { loopConfiguration(); } else { loopProduction(); } } void setupConfiguration() { WiFi.mode(WIFI_AP); WiFi.softAP(CONFIG_SSID); #ifdef DEBUG Serial.println("AP started"); #endif webServer.on("/", configServeIndex); webServer.on("/config", configServeGetConfiguration); webServer.onNotFound(configServeNotFound); webServer.begin(); #ifdef DEBUG Serial.println("Webserver started"); #endif } void loopConfiguration() { webServer.handleClient(); } [/code] This code can be also found embedded into two of my projects. Find them at https://gitlab.com/wolutator/EspThermometer2 and https://gitlab.com/wolutator/TouchSwitch. ')