April 2020
WiFi enabled Feather / FeatherWing products need to be able to easily switch the WiFi hotspot they are connected to, without forcing the developer to upload revised code to the device with the new WiFi credentials. This project shows you how to program your ESP8266 based Feather or FeatherWing to present a local WiFi access point to the user, from which they can connect to with a smartphone / PC, and then submit new WiFi credentials for a new WiFi connection through an internet browser.
The WiFi status and Access Point information are presented to the user on the Adafruit 128x32 mono OLED FeatherWing display. When powered, the ESP8266 based Feather or FeatherWing will automatically connect to the last WiFi hotspot using the credentials saved in FLASH. If that WiFi is not available, it will automatically go into Access Point mode and proceed to acquire new WiFi credentials from the user. The user may force the Access Point mode by pressing the center "B" button on the Adafruit 128x32 mono OLED FeatherWing display.
Adafruit 128x32 mono OLED FeatherWing display
Adafruit Feather HUZZAH with ESP8266
/*
This WiFi device automatically connects to the last WiFi hotspot
(with internet connection) using WiFi credentials stored in
FLASH memory. If the device cannot connect to the last WiFi hotspot,
then it will acquire the WiFi connection credentials from the
user by creating a temporary WiFi access point. The temporary
WiFi access point SSID and password are presented on the OLED
display, and the user then connects using a phone or PC.
Next the user opens a browser and enters the IP address also
presented on the OLED display. A web form loads, asking for the
WiFi credentials (SSID & password) to be used for future connections,
and then after connecting successfully, those credentials are saved
to FLASH memory.
Program flow:
Setup()
- Read WiFi connection credentials from FLASH
- IF credentials found THEN ConnectToWiFi()
- ELSE (no credentials) THEN SoftAccessPoint()
Loop()
- IF OLED button B pressed THEN SoftAccessPoint()
- IF bSoftAccessPointMode == true THEN server.handleClient()
ELSE CheckGetWiFiConnection();
*/
/////////////////////////////////////////////////////////////////////////
// NOTE: The red LED on #3 is reversed wired. LOW = on; HIGH = OFF
const byte pinBuiltInRedLED = 0; // NO internal pullup
const byte pinBuiltInBlueLED = 2; // Has internal pullup
/////////////////////////////////////////////////////////////////////////
// Adafruit 128x32 mono OLED FeatherWing
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// send –1 to the constructor so that none of the ESP8266 pins is used as a reset for the display.
//Adafruit_SSD1306 display(128, 32, &Wire, -1);
Adafruit_SSD1306 display(128, 32, &Wire);
#define BUTTON_B 16 // also ESP8266 wake up pin
#define BUTTON_C 2 // also blue LED. Has internal pullup
/////////////////////////////////////////////////////////////////////////
// ESP8266 WiFi
// Library: https://github.com/adafruit/Adafruit_ESP8266
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
// ESP8266 WiFi Access Point connection credentials
#ifndef APSSID
#define APSSID "Feather-AP" // max 21-6=15 chars
#define APPSK "password"
#endif
const char *ssid = APSSID;
const char *password = APPSK;
// WiFi connection credentials (normal ESP8266 connection to WiFi/internet).
String sSSID = "";
String sPSWD = "";
/////////////////////////////////////////////////////////////////////////
// EEPROM library to reading/writing FLASH
#include <EEPROM.h>
//////////////////////////////////////////////////////////////////////////////
// TimerA
const unsigned long timerA = 5000;
unsigned long timerAlap = millis();
/////////////////////////////////////////////////////////////////////////
boolean bButtonBPressed = false;
boolean bSoftAccessPointMode = false;
boolean bWiFiConnected = false;
void setup() {
pinMode(pinBuiltInRedLED, OUTPUT);
digitalWrite(pinBuiltInRedLED, HIGH);
//pinMode(pinBuiltInBlueLED, OUTPUT);
Serial.begin(115200);
while (!Serial) {
delay(1);
}
Serial.println("\nSerial ready");
/////////////////////////////////////////////////////////////////////////
// Adafruit 128x32 mono OLED FeatherWing
delay(100); // Give the OLED driver time to bring it's internal charge up to voltage.
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
//Serial.println(F("SSD1306 allocation failed"));
while(1) {blinkERR(pinBuiltInRedLED);}
}
display.clearDisplay();
display.display();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setTextWrap(false);
display.setCursor(0,0);
//display.print("0123456789");
display.print("HUZZAH");
display.setCursor(0,16);
display.print("ESP8266");
display.display();
// Configure the buttons on the OLED FeatherWing
//OLED button A cannot be used.
//pinMode(BUTTON_B, INPUT_PULLUP); // Button B has a 100k pullup on it from the OLED so it will work with the ESP8266. Therefore do NOT set the internal pullup.
pinMode(BUTTON_C, INPUT_PULLUP); // Also blue LED. Has internal pullup
display.setTextSize(1);
/////////////////////////////////////////////////////////////////////////
EEPROM.begin(512);
// Connect to the last WiFi using the credentials stored in FLASH.
sSSID = ReadFromFLASH(0, 30);
sPSWD = ReadFromFLASH(50, 30);
if (sSSID.length() == 0) {
// FLASH has not been written to previously
bButtonBPressed = true;
bSoftAccessPointMode = true;
SoftAccessPoint();
} else {
ConnecToLastWiFi();
}
} // setup()
void loop() {
// Button B & C only, Button A cannot be used
if (digitalRead(BUTTON_B) != HIGH && bButtonBPressed == false) {
bButtonBPressed = true;
bSoftAccessPointMode = true;
SoftAccessPoint();
}
if (digitalRead(BUTTON_C) != HIGH) {
Serial.println("Button C pressed");
display.clearDisplay();
display.setCursor(0,16);
display.print("Button C");
display.display();
}
// bSoftAccessPointMode indicates if we are in Soft Access Point mode
// for collection of new WiFi credentials from a connected user.
// Otherwise a WiFi connection is assumed and verified.
if (bSoftAccessPointMode == true) {
//digitalWrite(pinBuiltInRedLED, LOW);
server.handleClient();
} else {
// Station Mode (STA) - connects ESP8266 to WiFi using the
// credentials saved in FLASH.
if (timerAlap > millis()) timerAlap = millis();
if (millis() - timerAlap > timerA) {
blinkLED(pinBuiltInRedLED);
if (WiFi.status() != WL_CONNECTED) {
ConnecToLastWiFi();
}
timerAlap = millis(); // reset the timer
}
}
// The yield() function allows ESP8266 microcontroller to run a
// number of utility functions in the background, without causing
// the ESP8266 to crash or reset. Include it within any
// while() + digitalRead() and other loops;
yield();
} // loop()
////////////////////////////////////////////////////////////////
// FLASH
boolean WriteToFLASH(String sSSID, String sPSWD) {
// Write sSSID and sPSWD to ESP8266 FLASH memory
if (sSSID.length() > 49 || sPSWD.length() > 511) {
return false;
}
sSSID += ";";
byte pos = 0;
for(int n=pos;n<sSSID.length()+pos;n++){
EEPROM.write(n,sSSID[n-pos]);
}
sPSWD += ";";
pos = 50;
for(int n=pos;n<sPSWD.length()+pos;n++){
EEPROM.write(n,sPSWD[n-pos]);
}
EEPROM.commit();
return true;
} // WriteToFLASH()
String ReadFromFLASH(int iPos, int iLen) {
// Read the string beginning from FLASH at
// iPos with length iLen.
// Returns a zero length string if the FLASH
// has not been written to previously at the
// specified address.
String sTmp;
int iCountYuml = 0;
for (int n = iPos; n < iLen+iPos; ++n) {
if(char(EEPROM.read(n))!=';'){
if(EEPROM.read(n) == 0xFF) {
// DEC = 255; HEX = 0xFF when FLASH has not been previously written to.
iCountYuml++;
}
// space, formfeed ('\f'), newline ('\n'), carriage return ('\r'), horizontal tab ('\t'), and vertical tab ('\v')
if(isWhitespace(char(EEPROM.read(n)))){
//do nothing
} else {
sTmp += String(char(EEPROM.read(n)));
}
} else {
n=iLen+iPos;
}
}
if (iCountYuml == iLen) {
// FLASH has not been previously written to.
sTmp = "";
}
return sTmp;
} // ReadFromFLASH()
////////////////////////////////////////////////////////////////
void SoftAccessPoint() {
digitalWrite(pinBuiltInRedLED, LOW);
WiFi.softAP(ssid, password);
IPAddress myIP = WiFi.softAPIP();
// server.on(uri, HTTPMethod, handlerFn)
server.on("/", handleRoot);
server.on("/PathPost", HTTP_POST, handlePost);
server.begin();
Serial.println("Connect your phone or PC to:");
Serial.print("\tSSID: "); Serial.println(ssid);
Serial.print("\tPassword: "); Serial.println(password);
Serial.print("Then use browser to connect to IP address:");
Serial.println(myIP);
//server.handleClient();
// Connect to WiFi:
// SSID:
// Pswd:
// URL: 192.168.4.1
display.clearDisplay();
display.setCursor(0,0);
//display.print("012345678901234567890");
display.print("Connect to WiFi:");
display.setCursor(0,8);
display.print("SSID: ");
display.setCursor(strlen("SSID: ")*6,8);
display.print(ssid);
display.setCursor(0,16);
display.print("PSWD: ");
display.setCursor(strlen("PSWD: ")*6,16);
display.print(password);
display.setCursor(0,24);
display.print("URL: ");
display.setCursor(strlen("URL: ")*6,24);
display.print(myIP);
display.display();
yield();
} // SoftAccessPoint()
void handleRoot() {
// Present web form to client with input for SSID and password for the
// Wi-Fi this device should connect to in the future.
// Web URL is typically: http://192.168.4.1
String htmlPage = "<h1>Feather Web Server</h1>";
htmlPage += "<form action=\"PathPost\" method=\"post\">";
htmlPage += "<p>Wi-Fi you want this device to connect to in the future</p>";
htmlPage += "<p><label>SSID: </label><input type=\"text\" name=\"ssid\" value=\"\"/></p>";
htmlPage += "<p><label>Password: </label><input type=\"text\" name=\"pswd\" value=\"\"/></p>";
htmlPage += "<p><input type=\"submit\" name=\"btnSend\" value=\"Submit\" /></p>";
htmlPage += "</form>";
// Note that const char htmlPage[] = ""; will NOT work. Must be String.
server.send(200, "text/html", htmlPage);
} // handleRoot()
void handlePost() {
digitalWrite(pinBuiltInRedLED, HIGH);
sSSID = server.arg("ssid");
sPSWD = server.arg("pswd");
String htmlPage = "<h1>Configuring new WiFi connection to '" + sSSID + "'</h1>";
htmlPage += "<p>You may now close this browser & the WiFi connection to '" + String(ssid) + "'</p>";
server.send(200, "text/html", htmlPage);
// Connect the ESP8266 to the new WiFi as a Client
if (ConnectToWiFi(sSSID, sPSWD, pinBuiltInRedLED) == false) {
display.clearDisplay();
display.setCursor(0,0);
//display.print("012345678901234567890");
display.print("ERR connect to WiFi:");
display.setCursor(0,16);
display.print("SSID: ");
display.setCursor(strlen("SSID: ")*6,16);
display.print(sSSID);
display.display();
while(1) {
yield();
blinkERR(pinBuiltInRedLED);
}
}
// In the future, connect this device to server.arg("ssid")
// using password server.arg("pswd").
// Save ssid & pswd to FLASH memory..
if (WriteToFLASH(sSSID, sPSWD) == false) {
Serial.println("FLASH write error!");
} else {
Serial.println("SSID & PSWD written to FLASH");
}
digitalWrite(pinBuiltInRedLED, HIGH);
bSoftAccessPointMode = false;
bButtonBPressed = false;
} // handlePost()
void ConnecToLastWiFi() {
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Already connected to "); Serial.println(sSSID);
WriteWiFiToOLED();
bWiFiConnected = true;
return;
} else {
if (ConnectToWiFi(sSSID, sPSWD, pinBuiltInRedLED) == false) {
display.clearDisplay();
display.setCursor(0,0);
//display.print("012345678901234567890");
display.print("ERR connect to WiFi:");
display.setCursor(0,16);
display.print("SSID: ");
display.setCursor(strlen("SSID: ")*6,16);
display.print(sSSID);
display.display();
bButtonBPressed = true;
bSoftAccessPointMode = true;
SoftAccessPoint();
} else {
bWiFiConnected = true;
}
}
} // ConnecToLastWiFi()
boolean ConnectToWiFi(String sSSID, String sPSWD, byte pinLED) {
Serial.print("Connecting to: "); Serial.println(sSSID);
WiFi.begin(sSSID, sPSWD);
byte iLastStatus = 3;
while (WiFi.status() != WL_CONNECTED) {
if (digitalRead(BUTTON_B) != HIGH) {return false;}
if (iLastStatus != WiFi.status()) {
display.clearDisplay();
display.setCursor(0,0);
display.print("WiFI Disconnected!");
display.setCursor(0,8);
switch (WiFi.status()) {
case 0: // WL_IDLE_STATUS
Serial.println("\nWL_IDLE_STATUS");
display.print("WL_IDLE_STATUS");
display.setCursor(0,16);
display.print("Btn B to connect to");
display.setCursor(0,24);
display.print("new WiFi");
display.display(); yield();
break;
case 1: // WL_NO_SSID_AVAIL
Serial.println("\nWL_NO_SSID_AVAIL");
display.print("WL_NO_SSID_AVAIL");
display.setCursor(0,16);
display.print("Btn B to connect to");
display.setCursor(0,24);
display.print("new WiFi");
display.display(); yield();
break;
case 4: // WL_CONNECT_FAILED
Serial.println("\nWL_CONNECT_FAILED");
display.print("WL_CONNECT_FAILED");
display.setCursor(0,16);
display.print("Btn B to connect to");
display.setCursor(0,24);
display.print("new WiFi");
display.display(); yield();
break;
case 6: // WL_DISCONNECTED
Serial.println("\nWL_DISCONNECTED");
display.print("WL_DISCONNECTED");
display.setCursor(0,16);
display.print("Btn B to connect to");
display.setCursor(0,24);
display.print("new WiFi");
display.display(); yield();
break;
default:
Serial.print(".");
break;
}
iLastStatus = WiFi.status();
}
digitalWrite(pinLED, HIGH);
delay(500);
digitalWrite(pinLED, LOW);
}
bWiFiConnected = true;
WriteWiFiToOLED();
} // ConnectToWiFi()
void WriteWiFiToOLED() {
// Write the currently connected WiFi SSID to the OLED.
Serial.println();
Serial.print("Connected to WiFi ");
Serial.print(sSSID); Serial.print(" via IP address ");
Serial.println(WiFi.localIP());
// Update OLED
display.clearDisplay();
display.setCursor(0,0);
//display.print("012345678901234567890");
display.print("Connected to WiFi:");
display.setCursor(0,16);
display.print("SSID: ");
display.setCursor(strlen("SSID: ")*6,16);
display.print(sSSID);
//display.setCursor(0,24);
//display.print("IP: ");
//display.setCursor(strlen("IP: ")*6,24);
//display.print(WiFi.localIP());
display.display();
yield();
} // WriteWiFiToOLED()
////////////////////////////////////////////////////////////////
void blinkLED(byte ledPIN){
// consumes 300 ms.
for(int i = 5; i>0; i--){
digitalWrite(ledPIN, LOW);
delay(30);
digitalWrite(ledPIN, HIGH);
delay(30);
}
} //blinkLED()
void blinkERR(byte ledPIN){
// S-O-S
const int S = 150, O = 300;
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, LOW);
delay(S);
digitalWrite(ledPIN, HIGH);
delay(S);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, LOW);
delay(O);
digitalWrite(ledPIN, HIGH);
delay(O);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, LOW);
delay(S);
digitalWrite(ledPIN, HIGH);
delay(S);
}
delay(200);
} // blinkERR()
////////////////////////////////////////////////////////////////
Creating an Access Point + Webserver
Sparkfun ESP8266 AP Web Server Hookup Guide
ESP8266 NodeMCU Web Server In Arduino IDE
Do you need help developing or customizing a IoT product for your needs? Send me an email requesting a free one hour phone / web share consultation.
The information presented on this website is for the author's use only. Use of this information by anyone other than the author is offered as guidelines and non-professional advice only. No liability is assumed by the author or this web site.