Files
homepage/posts/2018-01-26.01/article.html

277 lines
9.4 KiB
HTML

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:<!--more-->
[code language="python" title="configGen.py"]
#!/usr/bin/python
from Cheetah.Template import Template
configItems = [
{&quot;label&quot;:&quot;_&quot;, &quot;key&quot;:&quot;magic&quot;, &quot;type&quot;:&quot;I&quot;, &quot;default&quot;: &quot;&quot;},
{&quot;label&quot;:&quot;Wifi SSID&quot;, &quot;key&quot;:&quot;wifiSsid&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:32, &quot;default&quot;:&quot;test&quot;},
{&quot;label&quot;:&quot;Wifi Key&quot;, &quot;key&quot;:&quot;wifiKey&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:64, &quot;default&quot;:&quot;geheim&quot;},
{&quot;label&quot;:&quot;MQTT Broker&quot;, &quot;key&quot;:&quot;mqttBroker&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:64, &quot;default&quot;:&quot;broker.hottis.de&quot;},
{&quot;label&quot;:&quot;MQTT Username&quot;, &quot;key&quot;:&quot;mqttUser&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:32, &quot;default&quot;:&quot;esp1&quot;},
{&quot;label&quot;:&quot;MQTT Password&quot;, &quot;key&quot;:&quot;mqttPass&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:32, &quot;default&quot;:&quot;geheim&quot;},
{&quot;label&quot;:&quot;MQTT ClientId&quot;, &quot;key&quot;:&quot;mqttClientId&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:32, &quot;default&quot;:&quot;changeThis&quot;},
{&quot;label&quot;:&quot;MQTT Topic&quot;, &quot;key&quot;:&quot;mqttTopic&quot;, &quot;type&quot;:&quot;C&quot;, &quot;length&quot;:64, &quot;default&quot;:&quot;IoT/espThermometer2/location/measurement&quot;},
{&quot;label&quot;:&quot;MQTT Port&quot;, &quot;key&quot;:&quot;mqttPort&quot;, &quot;type&quot;:&quot;I&quot;, &quot;default&quot;:8883},
{&quot;label&quot;:&quot;Measure Period&quot;, &quot;key&quot;:&quot;measurePeriod&quot;, &quot;type&quot;:&quot;I&quot;, &quot;default&quot;:300}
]
h_file = Template(file=&quot;configuration_h.tmpl&quot;, searchList=[{&quot;configItems&quot;:configItems}])
open('configuration.h','w').write(str(h_file))
c_file = Template(file=&quot;configuration_c.tmpl&quot;, searchList=[{&quot;configItems&quot;: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 &lt;Arduino.h&gt;
#include &lt;ESP8266WiFi.h&gt;
#include &lt;ESP8266WebServer.h&gt;
#include &lt;EEPROM.h&gt;
#include &quot;defines.h&quot;
#include &quot;configuration.h&quot;
#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 != &quot;_&quot;
#if $configItem.type == &quot;C&quot;
strcpy(configBlock.$configItem.key, &quot;$configItem.default&quot;);
#else if $configItem.type == &quot;I&quot;
configBlock.$configItem.key = $configItem.default;
#end if
#end if
#end for
}
String buffer =
&quot;&lt;!doctype html&quot;
&quot;&lt;html lang=\&quot;en\&quot;&gt;&quot;
&quot; &lt;head&gt;&quot;
&quot; &lt;title&gt;ESP8266 Thermometer Configuration Page&lt;/title&gt;&quot;
&quot; &lt;/head&gt;&quot;
&quot; &lt;body&gt;&quot;
&quot; &lt;h1&gt;ESP8266 Configuration Page&lt;/h1&gt;&quot;;
if (configSaved) {
configSaved = false;
buffer += &quot;&lt;h2&gt;Configuration saved&lt;/h2&gt;&quot;;
}
buffer +=
&quot; &lt;form action=\&quot;/config\&quot; method=\&quot;GET\&quot;&gt;&quot;
&quot; &lt;table&gt;&quot;
#for $configItem in $configItems
#if $configItem.label != &quot;_&quot;
&quot; &lt;tr&gt;&quot;
&quot; &lt;td&gt;&quot;
&quot; &lt;label for\&quot;$configItem.key\&quot;&gt;$configItem.label&lt;/label&gt;&quot;
&quot; &lt;/td&gt;&lt;td&gt;&quot;
&quot; &lt;input type=\&quot;text\&quot; name=\&quot;$configItem.key\&quot; id=\&quot;$configItem.key\&quot; &quot;;
#if $configItem.type == &quot;C&quot;
buffer += &quot; size=\&quot;$configItem.length\&quot; &quot;;
buffer += &quot; value=\&quot;&quot;;
buffer += configBlock.$configItem.key;
buffer += &quot;\&quot;&quot;;
#else if $configItem.type == &quot;I&quot;
buffer += &quot; value=\&quot;&quot;;
buffer += configBlock.$configItem.key;
buffer += &quot;\&quot;&quot;;
#end if
buffer +=
&quot; /&gt;&quot;
&quot; &lt;/td&gt;&quot;
&quot; &lt;/tr&gt;&quot;
#end if
#end for
&quot; &lt;tr&gt;&quot;
&quot; &lt;td colspan=\&quot;2\&quot;&gt;&quot;
&quot; &lt;button type=\&quot;submit\&quot;&gt;Save&lt;/button&gt;&quot;
&quot; &lt;/td&gt;&quot;
&quot; &lt;/tr&gt;&quot;
&quot; &lt;/table&gt;&quot;
&quot; &lt;/form&gt;&quot;
&quot; &lt;/body&gt;&quot;
&quot;&lt;/html&gt;&quot;;
webServer.send(200, &quot;text/html&quot;, buffer);
#ifdef DEBUG
Serial.println(&quot;indexHtml request served&quot;);
#endif
}
void configServeGetConfiguration() {
String arg;
#for $configItem in $configItems
#if $configItem.label != &quot;_&quot;
arg = webServer.arg(&quot;$configItem.key&quot;);
#if $configItem.type == &quot;C&quot;
strcpy(configBlock.$configItem.key, arg.c_str());
#else if $configItem.type == &quot;I&quot;
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(&quot;EEPROM saved&quot;);
configSaved = true;
webServer.sendHeader(&quot;Location&quot;, String(&quot;/&quot;), true);
webServer.send(302, &quot;text/plain&quot;, &quot;&quot;);
//webServer.send(200, &quot;text/html&quot;, &quot;configuration saved&quot;);
}
void showConfiguration() {
Serial.println(&quot;Configuration is&quot;);
#for $configItem in $configItems
Serial.print(&quot;$configItem.key = &quot;);
Serial.println(configBlock.$configItem.key);
#end for
Serial.println(&quot;---&quot;);
}
[/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 <code>setup()</code> and <code>loop()</code> function:
[code language="C"]
void setup() {
startTime = millis();
#ifdef DEBUG
Serial.begin(115200);
Serial.println(&quot;Starting ...&quot;);
#endif
pinMode(CONFIG_SWITCH, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
EEPROM.begin(512);
EEPROM.get(EEPROM_ADDR, configBlock);
Serial.print(&quot;Magic: &quot;);
Serial.println(configBlock.magic);
configMode = ((LOW == digitalRead(CONFIG_SWITCH)) || (configBlock.magic != MAGIC));
if (configMode) {
#ifdef DEBUG
Serial.println(&quot;Configuration mode&quot;);
#endif
digitalWrite(LED_PIN, LOW);
setupConfiguration();
} else {
#ifdef DEBUG
Serial.println(&quot;Production mode&quot;);
Serial.println();
Serial.println();
showConfiguration();
#endif
digitalWrite(LED_PIN, HIGH);
setupProduction();
}
#ifdef DEBUG
Serial.println(&quot;Started.&quot;);
#endif
}
void loop() {
if (configMode) {
loopConfiguration();
} else {
loopProduction();
}
}
void setupConfiguration() {
WiFi.mode(WIFI_AP);
WiFi.softAP(CONFIG_SSID);
#ifdef DEBUG
Serial.println(&quot;AP started&quot;);
#endif
webServer.on(&quot;/&quot;, configServeIndex);
webServer.on(&quot;/config&quot;, configServeGetConfiguration);
webServer.onNotFound(configServeNotFound);
webServer.begin();
#ifdef DEBUG
Serial.println(&quot;Webserver started&quot;);
#endif
}
void loopConfiguration() {
webServer.handleClient();
}
[/code]
This code can be also found embedded into two of my projects. Find them at <a href="https://gitlab.com/wolutator/EspThermometer2" rel="noopener" target="_blank">https://gitlab.com/wolutator/EspThermometer2</a> and <a href="https://gitlab.com/wolutator/TouchSwitch" rel="noopener" target="_blank">https://gitlab.com/wolutator/TouchSwitch</a>.
')