This article presents a technique for creating IoT devices with no physical interface or devices that can otherwise automatically hop on a network and publish their services without user intervention. All that the device needs is for the user to press the WPS button on the router.
Introduction
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.
Prerequisites
- This article assumes you are already familiar with programming the ESP8266.
- This article assumes you are using the Arduino IDE.
- This article assumes the IDE is configured for programming ESP8266 based modules.
Conceptualizing this Mess
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.
Coding this Mess
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>
#include <WiFiUdp.h>
WiFiUDP Udp;
#define UDPPORT 11011
#define UDPMULTICASTIP IPAddress(239,0,0,10)
#define HOSTNAME "test"
char cfgssid[256];
char cfgpassword[256];
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() {
Serial.begin(115200);
EEPROM.begin(512);
int i = 0;
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:
WiFi.mode(WIFI_STA);
bool done = false;
while (!done) {
WiFi.begin(cfgssid, cfgpassword);
Serial.print("Connecting to WiFi");
for (int i = 0; i < 20 && WL_CONNECTED != WiFi.status(); ++i) {
Serial.print(".");
delay(500);
}
Serial.println("");
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;
}
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:
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.
Udp.begin(UDPPORT);
...
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() {
if (WL_CONNECTED != WiFi.status()) {
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();
}
}
MDNS.update();
Udp.beginPacketMulticast(UDPMULTICASTIP, UDPPORT, WiFi.localIP());
Udp.print("Hello World!");
Udp.endPacket();
delay(250);
}
Points of Interest
The ESP-01 modules are very cheap and I've had problems with the flash not working properly on some of them.
History
- 5th November, 2020 - Initial submission