Updated on 2020-11-05
A walkthrough and source code for automatically configuring your ESP8266 IoT devices
Recently, I made a device that needed to sit in a small shed in the middle of the woods and report sensor readings. Due to the fact that it was isolated, it was not appropriate for the device to have a user interface, not even a button. It needed to broadcast a sensor reading to any listening machine and it needed to provide a small website at a known address. In the process of creating this, I wrote some code for automatically configuring an ESP8266 and publishing it on the network. This article endeavors to explain the process and provide code for implementing this in your own IoT projects. While it's designed for the ESP8266, the concepts are transferable to other platforms. It should work on any ESP8266 module, even an ESP-01.
The first task we have is connecting to the WiFi. We will read a configuration file from the internal flash memory. This will contain an SSID and a network password.
If we time out in the process, we will start listening for a WPS signal. If that times out, we will try to connect to WiFi again and the process will repeat.
If we successfully used WPS, we write the SSID and network password to the flash and reset the device. Otherwise, we will simply continue.
Finally, we will use Multicast DNS (mDNS) to publish our device such that others can locate it using a well known local domain name.
If we need to push data, ideally we will use UDP multicast so that any device can listen in on a well known local multicast address. UDP is often ideal for transmitting sensor readings. Otherwise, if the device is to listen, it can do so and other devices can find it using its well known domain.
Be sure to set your ESP8266 module into program mode. I haven't factored this code because I wanted to keep it easy to copy and paste and having a bunch of function prototypes in there undermines it. I may make this into a library at some point.
First, we'll cover our includes and globals:
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>
//
// Your includes follow
//
// BEGIN only needed if using UDP:
#include <WiFiUdp.h>
WiFiUDP Udp;
#define UDPPORT 11011
#define UDPMULTICASTIP IPAddress(239,0,0,10)
// END only needed if using UDP
#define HOSTNAME "test"
char cfgssid[256];
char cfgpassword[256];
//
// Your globals follow
//
Above has three necessary header files. After that, you can insert your own includes. Then, we have a UDP section that is optional. You only need it if you will be using UDP.
After that, we define the host name we'll publish and we have buffers for the SSID and network password.
The setup() method is pretty complicated. You may want to factor it some for your own code. We're going to cover it in sections:
void setup() {
// Initialize the serial port
Serial.begin(115200);
// commit 512 bytes of ESP8266 flash
// this step actually loads the content (512 bytes) of flash into
// a 512-byte-array cache in RAM
EEPROM.begin(512);
int i = 0;
// Read the settings
for(i = 0;i<256;++i) {
cfgssid[i]=EEPROM.read(i);
if(!cfgssid[i])
break;
}
cfgssid[i]=0;
i=0;
for(i = 0;i<256;++i) {
cfgpassword[i]=(char)EEPROM.read(i+256);
if(!cfgpassword[i])
break;
}
cfgpassword[i]=0;
...
Above, we initialize the EEPROM library and allocate 512 bytes of storage. Then, we read our SSID - a string that's less than 256 characters. Next, we read our password at offset 256 but otherwise it's pretty much the same.
Now on to the WiFi handling which is somewhat complicated:
// Initialize the WiFi and connect
WiFi.mode(WIFI_STA);
bool done = false;
while (!done) {
// Connect to Wi-Fi
WiFi.begin(cfgssid, cfgpassword);
Serial.print("Connecting to WiFi");
// try this for 10 seconds, then check for WPS
for (int i = 0; i < 20 && WL_CONNECTED != WiFi.status(); ++i) {
Serial.print(".");
delay(500);
}
Serial.println("");
// If we're not connected, wait for a WPS signal
if (WL_CONNECTED != WiFi.status()) {
Serial.print("Connection to ");
Serial.print(cfgssid);
Serial.println(" failed. Entering auto-config mode");
Serial.println("Press the WPS button on your router");
bool ret = WiFi.beginWPSConfig();
if (ret) {
String newSSID = WiFi.SSID();
if (0 < newSSID.length()) {
Serial.println("Auto-configuration successful. Saving.");
strcpy(cfgssid, newSSID.c_str());
strcpy(cfgpassword, WiFi.psk().c_str());
int c = strlen(cfgssid);
for(int i = 0;i<c;++i)
EEPROM.write(i,cfgssid[i]);
EEPROM.write(c,0);
c = strlen(cfgpassword);
for(int i = 0;i<c;++i)
EEPROM.write(i+256,cfgpassword[i]);
EEPROM.write(c+256,0);
EEPROM.end();
Serial.println("Restarting...");
ESP.restart();
} else {
ret = false;
}
}
} else
done = true;
// if we didn't get connected, loop
}
// Display the status
Serial.println("");
Serial.print("Connected to ");
Serial.println(WiFi.SSID());
Serial.print("Host name: ");
Serial.print(HOSTNAME);
Serial.println(".local");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
...
The logic for this is somewhat awkward. The first thing we try to do is connect. If we can't connect within 10 seconds, we start looking for a WPS signal from the router. If that times out, we go back to trying to connect and the cycle repeats. If we manage to connect via WPS, then we write the new SSID and password to flash and reboot the device. Rebooting is more reliable than trying to just get the WiFi gadget to reconnect.
Now a simple but important step:
// start the multicast DNS publishing
if (MDNS.begin(HOSTNAME)) {
Serial.println("MDNS responder started");
}
...
Above, we initialize the mDNS responder with our hostname. This works such that if HOSTNAME is "test" the domain will be "test.local".
The following only needs to be done if there is UDP. After that comes your own setup code.
// initialize the UDP
// only needed if using UDP:
Udp.begin(UDPPORT);
//
// Your setup code follows
//
...
Finally, we get to our loop() method. First we try to reconnect if our WiFi connection got dropped and we restart if we couldn't reconnect. We also refresh our mDNS registration periodically by calling MDNS.update(). Everything below that code is optional and provided as a sample, which multicasts "Hello World!" once ever quarter of a second:
void loop() {
// reconnect to the WiFi if we
// got disconnected
if (WL_CONNECTED != WiFi.status()) {
// Connect to Wi-Fi
WiFi.begin(cfgssid, cfgpassword);
Serial.print("Connecting to WiFi");
for (int i = 0; i < 20 && WiFi.status() != WL_CONNECTED; ++i) {
Serial.print(".");
delay(500);
}
if (WL_CONNECTED != WiFi.status()) {
Serial.println("Could not reconnect. Restarting.");
ESP.restart();
}
}
// update the DNS information
MDNS.update();
// BEGIN only applicable if using UDP:
Udp.beginPacketMulticast(UDPMULTICASTIP, UDPPORT, WiFi.localIP());
Udp.print("Hello World!");
Udp.endPacket();
// END only applicable if using UDP
//
// Your code goes here
//
// we only want to do this every
// quarter second for the example
delay(250);
}
The ESP-01 modules are very cheap and I've had problems with the flash not working properly on some of them.