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:


#!/usr/bin/python

from Cheetah.Template import Template

configItems = [
    { "label" : "_" ,  "key" : "magic" ,  "type" : "I" ,  "default" : 0 },
    { "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))

The two templates are:


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();


And:


#raw
#include 

#include 
#include 
#include 

#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 =
  ""
  "  "
  "    ESP8266 Thermometer Configuration Page"
  "  "
  "  "
  "    

ESP8266 Configuration Page

"; if (configSaved) { configSaved = false; buffer += "

Configuration saved

"; } buffer += "
" " " #for $configItem in $configItems #if $configItem.label != "_" " " " " #end if #end for " " " " " " "
" " " " " " " "
" " " "
" "
" " " ""; 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("---"); }

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:


// Do not remove the include below
#include "EspThermometer2.h"
#include "defines.h"

// #define ESP8266


#include 

#include "configuration.h"
#include "productionMode.h"
#include "configurationMode.h"



ADC_MODE(ADC_VCC);



bool configMode = false;

uint32_t startTime = 0;


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);
    setupConfigurationNetwork();
    setupConfigurationServer();
  } else {
#ifdef DEBUG
  Serial.println("Production mode");
  Serial.println();
  Serial.println();
  showConfiguration();
#endif

	digitalWrite(LED_PIN, HIGH);
    setupProduction();
    setupConfigurationServer();
  }

#ifdef DEBUG
  Serial.println("Started.");
#endif
}

void loop() {
  if (configMode) {
    loopConfiguration();
  } else {
    loopProduction();
  }
}


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.