Note

This is the synchronous version of the WiFi preboot environment.

Code

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2024 - License: GNU MIT        //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
// This template is a resilient implementation of a pre-WiFi connection  //
// environment that allows the user to configure the Ssid and password   //
// for the WiFi network via a built-in web-server that is automatically  //
// started by the template when no WiFi network has been configured.     //
///////////////////////////////////////////////////////////////////////////
// Purpose ////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// One of the problems is that given the cost, ESP devices are bought in //
// bulk, programmed and then sprawled out allover a site but typically   //
// Arduino templates have little accountibility or resillience built-in  //
// that would make the templates resist ESP resets and still be able to  //
// connect to the WiFi network. Similarly, in case the site is mobile,   //
// and in case the WiFi network changes, then all the ESPs will just     //
// have to be reprogrammed manually by the user which is a daunting task //
// relative to the amount of ESP devices in use. This template addresses //
// that issue by creating a robust mechanism where the ESP device will   //
// reboot in a preboot AP mode in case the WiFi network cannot be found  //
// or connected to in order to allow the user to reconfigure the ESP.    //
///////////////////////////////////////////////////////////////////////////
// Example Usage //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//   * configure the template parameters as need be within the           //
//     "configurable parameters" section of this template, the password  //
//     defined as a "master password" will grant access to configurng    //
//     the template and will also be used as the OTA password            //
//   * add any user-code to the connectedLoop() function that will be    //
//     called by the Arduino loop with a delay of 1 millisecond (the     //
//     user must include a delay in order to not throttle the CPU)       //
//   * when booting, the template will generate an Ssid based on the ESP //
//     CPU identifier consisting of up to two digits and will blink the  //
//     built-in ESP LED in sequence in order to give away the AP         //
//   * connect to the numeric AP started by the ESP and configure the    //
//     network Ssid and password                                         //
//   * the template will now connect to the WiFi network using the       //
//     provided Ssid and password; iff. the WiFi disconnects from the    //
//     WiFi network for more than the amount of milliseconds given by:   //
//                                                                       //
//     WIFI_RETRY_TIMEOUT * WIFI_CONNECT_TRIES                           //
//                                                                       //
//     then the template will restart again in AP mode, blink the LED    //
//     of the numeric Ssid and wait to be configured for a WiFi network  //
///////////////////////////////////////////////////////////////////////////
// Libraries //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// The libraries used are minimal and the kind of libraries that have a  //
// wide-range of applications, in particular if the ESP device is WiFi   //
// enabled. Here is a complete list of libraries used by the template:   //
//   * ArduinoJson (very popular JSON library)                           //
///////////////////////////////////////////////////////////////////////////
// Credits ////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// The template is loosely inspired by the many captive portal solutions //
// out there but with some minimalism in mind and additionally exposing  //
// various configurable parameters such as the HTML webpage. Other close //
// similarities consist in the Tasmota firmware that accomplishes more   //
// or less the same switch between connected to a WiFi network and AP    //
// mode that allows the user to configure the WiFi network.              //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//  configurable parameters                                              //
///////////////////////////////////////////////////////////////////////////
 
// comment out to enable debugging
//#define DEBUG 1
 
// set the master password for OTA updates and access to the soft AP
#define PREBOOT_MASTER_PASSWORD ""
 
// the name and length of the cookie to use for authentication
#define PREBOOT_COOKIE_NAME "ArduinoPrebootCookie"
#define PREBOOT_COOKIE_MAX_LENGTH 256
 
// timeout to establish STA connection in milliseconds
#define WIFI_RETRY_TIMEOUT 1000 * 10
 
// retries as multiples of WIFI_RETRY_TIMEOUT milliseconds
#define WIFI_CONNECT_TRIES 60
 
// how much time to wait for a client to reconfigure before switching to client mode again
#define WIFI_SERVER_TIMEOUT 1000 * 60 * 3
 
// the time between blinking a single digit
#define BLINK_DIT_LENGTH 250
 
// the time between blinking the whole number
#define BLINK_DAH_LENGTH 2500
 
///////////////////////////////////////////////////////////////////////////
//  includes                                                             //
///////////////////////////////////////////////////////////////////////////
#include <DNSServer.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include "esp_mac.h"
#include <WebServer.h>
#include <ESPmDNS.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#endif
 
#include <FS.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
// Arduino OTA
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
 
// Platform specific defines.
#if defined(ARDUINO_ARCH_ESP8266)
#define GET_CHIP_ID() (ESP.getChipId())
#elif defined(ARDUINO_ARCH_ESP32)
#define GET_CHIP_ID() ((uint16_t)(ESP.getEfuseMac() >> 32))
#endif
 
#define HOSTNAME() String("esp-" + String(GET_CHIP_ID(), HEX))
#define CONFIGURATION_FILE_NAME "/config.json"
#define CONFIGURATION_MAX_LENGTH 1024
 
///////////////////////////////////////////////////////////////////////////
//  function definitions                                                 //
///////////////////////////////////////////////////////////////////////////
byte* getHardwareAddress(void);
char* getHardwareAddress(char colon);
String generateTemporarySSID(void);
void blinkDigits(int* digits, int count, void (*callback)(), int dit = 250, int dah = 2500);
void clientWifi(void);
void serverWifi(void);
void blinkDigitsIdle(void);
 
bool setConfiguration(const char* configurationFile, JsonDocument& configuration);
int getConfiguration(const char* configurationFile, JsonDocument& configuration);
 
void handleRootHttpRequest(void);
void handleRootCssRequest(void);
void handleSetupHttpRequest(void);
void handleRootHttpGet(void);
void handleSetupHttpGet(void);
void handleRootHttpPost(void);
void handleSetupHttpPost(void);
void handleHttpNotFound(void);
 
void arduinoLoop(void);
 
///////////////////////////////////////////////////////////////////////////
//  variable declarations                                                //
///////////////////////////////////////////////////////////////////////////
IPAddress softAPAddress(8, 8, 8, 8);
IPAddress softAPNetmask(255, 255, 255, 0);
 
DNSServer dnsServer;
#if defined(ARDUINO_ARCH_ESP8266)
ESP8266WebServer server(80);
#elif defined(ARDUINO_ARCH_ESP32)
WebServer server(80);
#endif
 
int connectionTries;
bool rebootPending;
 
enum bootMode : int {
  BOOT_MODE_NONE = 0, 
  BOOT_MODE_CLIENT, 
  BOOT_MODE_SERVER
};
 
unsigned long lastWifiExecuteTime;
char* authenticationCookie = NULL;
bool otaStarted;
bool otaInProgress;
bool networkConnected;
int serverTicks;
 
///////////////////////////////////////////////////////////////////////////
//  HTML & CSS templates                                                 //
///////////////////////////////////////////////////////////////////////////
const char* GENERIC_CSS_TEMPLATE = R"html(
* {
  box-sizing: border-box;
}
body {
  background-color: #3498db;
  font-family: "Arial", sans-serif;
  padding: 50px;
}
.container {
  margin: 20px auto;
  padding: 10px;
  width: 300px;
  height: 100%;
  background-color: #fff;
  border-radius: 5px;
  margin-left: auto;
  margin-right: auto;
}
h1 {
  width: 70%;
  color: #777;
  font-size: 32px;
  margin: 28px auto;
  text-align: center;
}
form {
  text-align: center;
}
input {
  padding: 12px 0;
  margin-bottom: 10px;
  border-radius: 3px;
  border: 2px solid transparent;
  text-align: center;
  width: 90%;
  font-size: 16px;
  transition: border 0.2s, background-color 0.2s;
}
form .field {
  background-color: #ecf0f1;
}
form .field:focus {
  border: 2px solid #3498db;
}
form .btn {
  background-color: #3498db;
  color: #fff;
  line-height: 25px;
  cursor: pointer;
}
form .btn:hover,
form .btn:active {
  background-color: #1f78b4;
  border: 2px solid #1f78b4;
}
.pass-link {
  text-align: center;
}
.pass-link a:link,
.pass-link a:visited {
  font-size: 12px;
  color: #777;
}
table {
  border: 1px solid #dededf;
  border-collapse: collapse;
  border-spacing: 1px;
  margin-left: auto;
  margin-right: auto;
  width: 80%;
}
td {
  border: 1px solid #dededf;
  background-color: #ffffff;
  color: #000000;
  padding: 1em;
}
)html";
 
const char* HTML_SETUP_TEMPLATE = R"html(
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>setup</title>
    <link rel="stylesheet" href="/style.css">
  </head>
  <body>
    <div class="container">
      <h1>setup</h1>
      <table>
        <tr>
          <td>AP</td>
          <td>%AP%</td>
        </tr>
        <tr>
          <td>MAC</td>
          <td>%MAC%</td>
        </tr>
      </table>
      <br>
      <form method="POST" action="/setup">
        <label for="name">Name</label>
        <input id="name" type="text" name="name" value="%NAME%" class="field">
        <label for="Ssid">SSID</label>
        <input id="Ssid" type="text" name="Ssid" class="field">
        <label for="password">Password</label>
        <input id="password" type="password" name="password" class="field">
        <input type="submit" value="login" class="btn">
      </form>
    </div>
  </body>
</html>
)html";
 
const char* HTML_AUTH_TEMPLATE = R"html(
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Preboot Access</title>
    <link rel="stylesheet" href="/style.css">
  </head>
  <body>
    <div class="container">
      <h1>admin</h1>
      <form method="POST">
        <input id="password" type="password" name="password" class="field" placeholder="password">
        <input type="submit" value="login" class="btn">
      </form>
    </div>
  </body>
</html>
)html";
 
///////////////////////////////////////////////////////////////////////////
//  begin Arduino                                                        //
///////////////////////////////////////////////////////////////////////////
void setup() {
#ifdef DEBUG
  Serial.begin(115200);
  // wait for serial
  while (!Serial) {
    delay(100);
  }
 
  Serial.println();
#else
  Serial.end();
#endif
 
#ifdef DEBUG
  Serial.println("Mounting filesystem...");
#endif
#if defined(ARDUINO_ARCH_ESP8266)
  if (!LittleFS.begin()) {
#ifdef DEBUG
    Serial.println("LittleFS mount failed, formatting and rebooting...");
#endif
    LittleFS.format();
    delay(1000);
    ESP.restart();
#elif defined(ARDUINO_ARCH_ESP32)
  if (!LittleFS.begin(true)) {
#endif
#ifdef DEBUG
    Serial.println("LittleFS mount & format failed...");
#endif
    return;
  }
 
#ifdef DEBUG
  Serial.printf("Checking if WiFi server must be started...\n");
#endif
  // check if Ssid is set and start soft AP or STA mode
  DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH);
  if(getConfiguration(CONFIGURATION_FILE_NAME, configuration) == -1) {
#ifdef DEBUG
    Serial.println("Unable to retrieve configuration.");
#endif
    delay(60000);
    ESP.restart();
    return;
  }
 
  switch(configuration["boot"].as<int>()) {
    case BOOT_MODE_CLIENT:
#ifdef DEBUG
      Serial.printf("Client connecting to WiFi...\n");
#endif
      clientWifi();
      arduinoLoop();
      delay(1);
      break;
    case BOOT_MODE_SERVER:
    case BOOT_MODE_NONE:
#ifdef DEBUG
      Serial.printf("Server AP starting...\n");
#endif
      // start soft AP
      serverWifi();
      delay(250);
      break;
  }
 
  // setup OTA
  ArduinoOTA.setHostname(configuration["name"].as<const char*>());
  // allow flashing with the master password
  ArduinoOTA.setPassword(PREBOOT_MASTER_PASSWORD);
  ArduinoOTA.onStart([]() {
    // mark OTA as started
    otaInProgress = true;
 
    // stop LittleFS as per the documentation
    LittleFS.end();
 
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else {  // U_FS
      type = "filesystem";
    }
 
    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
#ifdef DEBUG
    Serial.println("Start updating " + type);
#endif
  });
  ArduinoOTA.onEnd([]() {
    otaInProgress = false;
#ifdef DEBUG
    Serial.println("\nEnd");
#endif
    // restart the device
#ifdef DEBUG
    Serial.printf("Restarting ESP.\n");
#endif
    delay(1000);
    ESP.restart();
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
#ifdef DEBUG
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
#endif
  });
  ArduinoOTA.onError([](ota_error_t error) {
#ifdef DEBUG
    Serial.printf("Error[%u]: ", error);
#endif
    if (error == OTA_AUTH_ERROR) {
#ifdef DEBUG
      Serial.println("Auth Failed");
#endif
    } else if (error == OTA_BEGIN_ERROR) {
#ifdef DEBUG
      Serial.println("Begin Failed");
#endif
    } else if (error == OTA_CONNECT_ERROR) {
#ifdef DEBUG
      Serial.println("Connect Failed");
#endif
    } else if (error == OTA_RECEIVE_ERROR) {
#ifdef DEBUG
      Serial.println("Receive Failed");
#endif
    } else if (error == OTA_END_ERROR) {
#ifdef DEBUG
      Serial.println("End Failed");
#endif
    }
  });
}
 
void loop() {
  // check if a reboot has been scheduled.
  if(rebootPending) {
#ifdef DEBUG
    Serial.printf("Reboot pending, restarting in 1s...\n");
#endif
    delay(1000);
    ESP.restart();
  }
 
  DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH);
  configuration["boot"] = BOOT_MODE_CLIENT;
  switch(configuration["boot"].as<int>()) {
    case BOOT_MODE_CLIENT:
      break;
    case BOOT_MODE_SERVER:
      unsigned long serverTime = ++serverTicks * 250;
      if(serverTime >= WIFI_SERVER_TIMEOUT) {
    #ifdef DEBUG
        Serial.println("Server timeout, rebooting...\n");
    #endif
        DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH);
        configuration["boot"] = BOOT_MODE_CLIENT;
 
        rebootPending = true;
        return;
      }
 
      #ifdef DEBUG
      /*
        if(callbackTickTime % 1000 == 0 ) {
          Serial.printf("Time till reboot %.0fs\n", (float)(WIFI_SERVER_TIMEOUT - callbackTickTime)/1000.0);
        }
      */
      #endif
      serverWifi();
      delay(250);
      break;
  }
 
}
 
///////////////////////////////////////////////////////////////////////////
//  end Arduino                                                          //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//  user code goes here, connectedLoop invoked from Arduino loop()       //
///////////////////////////////////////////////////////////////////////////
void arduinoLoop(void) {
  // USER CODE, USER CODE, USER CODE, USER CODE, USER CODE, USER CODE, ...
  Serial.printf("User code...\n");
  ArduinoOTA.handle();
  delay(1000);
}
 
///////////////////////////////////////////////////////////////////////////
//  HTTP route handling                                                  //
///////////////////////////////////////////////////////////////////////////
void handleRootHttpPost(void) {
  String password;
  for(int i = 0; i < server.args(); ++i) {
    if(server.argName(i) == "password") {
      password = server.arg(i);
      continue;
    }
  }
 
  if(!password.equals(PREBOOT_MASTER_PASSWORD)) {
    server.sendHeader("Location", "/");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(302);
    return;
  }
 
#ifdef DEBUG
  Serial.println("Authentication succeeded, setting cookie and redirecting.");
#endif
 
  // clear old authentication cookie
  if(authenticationCookie != NULL) {
    free(authenticationCookie);
    authenticationCookie = NULL;
  }
 
  authenticationCookie = randomStringHex(8);
  char* buff = (char*) malloc(PREBOOT_COOKIE_MAX_LENGTH * sizeof(char));
  snprintf(buff, PREBOOT_COOKIE_MAX_LENGTH, "%s=%s; Max-Age=600; SameSite=Strict", PREBOOT_COOKIE_NAME, authenticationCookie);
#ifdef DEBUG
  Serial.printf("Preboot cookie set to: %s\n", buff);
#endif
  server.sendHeader("Set-Cookie", buff);
  server.sendHeader("Location", "/setup");
  server.sendHeader("Cache-Control", "no-cache");
  server.send(302);
  free(buff);
}
 
void handleSetupHttpPost(void) {
  String espName, staSsid, password;
  for(int i = 0; i < server.args(); ++i) {
    if(server.argName(i) == "name") {
      espName = server.arg(i);
      continue;
    }
 
    if(server.argName(i) == "Ssid") {
      staSsid = server.arg(i);
      continue;
    }
 
    if(server.argName(i) == "password") {
      password = server.arg(i);
      continue;
    }
  }
 
  if(espName == NULL || staSsid == NULL || password == NULL) {
      server.sendHeader("Location", "/");
      server.sendHeader("Cache-Control", "no-cache");
      server.send(302);
      return;
  }
 
#ifdef DEBUG
  Serial.printf("Ssid %s and password %s received from web application.\n", staSsid, password);
#endif
  DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH);
  configuration["name"] = espName;
  configuration["Ssid"] = staSsid;
  configuration["password"] = password;
  configuration["boot"] = BOOT_MODE_CLIENT;
  if(!setConfiguration(CONFIGURATION_FILE_NAME, configuration)) {
#ifdef DEBUG
    Serial.printf("Failed to write configuration.\n");
#endif
    server.sendHeader("Location", "/setup");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(307);
    return;
  }
 
  server.send(200, "text/plain", "Parameters applied. Scheduling reboot...");
 
#ifdef DEBUG
  Serial.printf("Configuration applied...\n");
#endif
 
  rebootPending = true;
}
 
void handleRootHttpGet(void) {  
  // send login form
#ifdef DEBUG
  Serial.printf("Sending authentication webpage.\n");
#endif
  String processTemplate = String(HTML_AUTH_TEMPLATE);
  server.send(200, "text/html", processTemplate);
}
 
void handleSetupHttpGet(void) {  
  DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH);
  if(getConfiguration(CONFIGURATION_FILE_NAME, configuration) == -1) {
#ifdef DEBUG
    Serial.println("Unable to retrieve configuration.");
#endif
    server.sendHeader("Location", "/setup");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(307);
  }
 
  String espName = HOSTNAME();
  if(configuration.containsKey("name")) {
    espName = configuration["name"].as<const char*>();
  }
  // send default boot webpage
#ifdef DEBUG
  Serial.printf("Sending configuration form webpage.\n");
#endif
  String processTemplate = String(HTML_SETUP_TEMPLATE);
  processTemplate.replace("%AP%", generateTemporarySSID());
  processTemplate.replace("%MAC%", getHardwareAddress(':'));
  processTemplate.replace("%NAME%", espName);
  server.send(200, "text/html", processTemplate);
}
 
void handleRootHttpRequest(void) {
  switch(server.method()) {
    case HTTP_GET:
      handleRootHttpGet();
      break;
    case HTTP_POST:
      handleRootHttpPost();
      break;
  }
}
 
void handleRootCssRequest(void) {
  if(server.method() != HTTP_GET) {
    handleHttpNotFound();
    return;
  }
 
#ifdef DEBUG
  Serial.println("Sending stylesheet...");
#endif  
  String rootCss = String(GENERIC_CSS_TEMPLATE);
  server.send(200, "text/css", rootCss);
}
 
void handleSetupHttpRequest(void) {
#ifdef DEBUG
  Serial.println("HTTP setup request received.");
#endif
  if(!server.hasHeader("Cookie")) {
#ifdef DEBUG
    Serial.println("No cookie header found.");
#endif
    server.sendHeader("Location", "/");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(302);
    return;
  }
 
  String cookie = server.header("Cookie");
  if(authenticationCookie == NULL || cookie.indexOf(authenticationCookie) == -1) {
#ifdef DEBUG
    Serial.println("Authentication failed.");
#endif
    server.sendHeader("Location", "/");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(302);
    return;
  }
 
  switch(server.method()) {
    case HTTP_GET:
#ifdef DEBUG
      Serial.printf("HTTP GET request received for setup.\n");
#endif
      handleSetupHttpGet();
      break;
    case HTTP_POST:
#ifdef DEBUG
      Serial.printf("HTTP POST request received for setup.\n");
#endif
      handleSetupHttpPost();
      break;
  }
}
 
void handleHttpNotFound(void) {
  server.sendHeader("Cache-Control", "no-cache");
  server.send(404);
}
 
///////////////////////////////////////////////////////////////////////////
//  set the current configuration                                        //
///////////////////////////////////////////////////////////////////////////
bool setConfiguration(const char* configurationFile, JsonDocument& configuration) {
#if defined(ARDUINO_ARCH_ESP8266)
  File file = LittleFS.open(configurationFile, "w");
#elif defined(ARDUINO_ARCH_ESP32)
  File file = LittleFS.open(configurationFile, FILE_WRITE);
#endif
  if(!file) {
#ifdef DEBUG
    Serial.println("Failed to open file for writing.");
#endif
    return false;
  }
  size_t bytesWritten = serializeJson(configuration, file);
  file.close();
#ifdef DEBUG
  Serial.printf("Written bytes %d vs. document bytes %d\n", bytesWritten, measureJson(configuration));
#endif
  return bytesWritten == measureJson(configuration);
}
 
///////////////////////////////////////////////////////////////////////////
//  get the current configuration                                        //
///////////////////////////////////////////////////////////////////////////
int getConfiguration(const char* configurationFile, JsonDocument& configuration) {
#if defined(ARDUINO_ARCH_ESP8266)
  File file = LittleFS.open(configurationFile, "r");
#elif defined(ARDUINO_ARCH_ESP32)
  File file = LittleFS.open(configurationFile);
#endif
 
  if (!file) {
#ifdef DEBUG
    Serial.println("Failed to open file for reading.");
#endif
    return false;
  }
 
  DeserializationError error = deserializeJson(configuration, file);
  file.close();
  if(error) {
#ifdef DEBUG
    Serial.printf("Deserialization failed with error %s\n", error.c_str());
#endif
    return -1;
  }
 
  return measureJson(configuration);
}
 
///////////////////////////////////////////////////////////////////////////
//  generate random string                                               //
///////////////////////////////////////////////////////////////////////////
char* randomStringHex(int length) {
  const char alphabet[] = "0123456789abcdef";
  char* payload = (char*) malloc(length * sizeof(char));
  int i;
  for (i=0; i<length; ++i) {
    payload[i] = alphabet[random(16)];
  }
  payload[i] = '\0';
  return payload;
}
 
 
///////////////////////////////////////////////////////////////////////////
//  get wireless status                                                  //
///////////////////////////////////////////////////////////////////////////
const char* wl_status_to_string(wl_status_t status) {
  switch (status) {
    case WL_NO_SHIELD: return "WL_NO_SHIELD";
    case WL_IDLE_STATUS: return "WL_IDLE_STATUS";
    case WL_NO_SSID_AVAIL: return "WL_NO_SSID_AVAIL";
    case WL_SCAN_COMPLETED: return "WL_SCAN_COMPLETED";
    case WL_CONNECTED: return "WL_CONNECTED";
    case WL_CONNECT_FAILED: return "WL_CONNECT_FAILED";
    case WL_CONNECTION_LOST: return "WL_CONNECTION_LOST";
    case WL_DISCONNECTED: return "WL_DISCONNECTED";
#if defined(ARDUINO_ARCH_ESP32)
    case WL_STOPPED: return "WL_STOPPED";
#endif
  }
 
  return "UNKNOWN";
}
 
///////////////////////////////////////////////////////////////////////////
//  get WiFi MAC address                                                 //
///////////////////////////////////////////////////////////////////////////
byte* getHardwareAddress(void) {
  // get mac address
  byte* mac = (byte *)malloc(6 * sizeof(byte));
#if defined(ARDUINO_ARCH_ESP8266)
  WiFi.macAddress(mac);
#elif defined(ARDUINO_ARCH_ESP32)
  Network.macAddress(mac);
#endif
  return mac;
}
 
///////////////////////////////////////////////////////////////////////////
//  convert MAC address to string                                        //
///////////////////////////////////////////////////////////////////////////
char* getHardwareAddress(char colon) {
  byte* mac = getHardwareAddress();
  char* buff = (char *)malloc(18 * sizeof(char));
  sprintf(buff, "%02x%c%02x%c%02x%c%02x%c%02x%c%02x", 
    mac[0], 
    colon,
    mac[1],
    colon,
    mac[2],
    colon, 
    mac[3],
    colon, 
    mac[4],
    colon, 
    mac[5]
  );
 
  free(mac);
  return buff;
}
 
 
///////////////////////////////////////////////////////////////////////////
//  get WiFi soft AP                                                     //
///////////////////////////////////////////////////////////////////////////
String generateTemporarySSID(void) {
  byte* mac = getHardwareAddress();
  String ssid = String(mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5], DEC);
  free(mac);
  return ssid;
}
 
///////////////////////////////////////////////////////////////////////////
//  serve WiFi AP                                                        //
///////////////////////////////////////////////////////////////////////////
void serverWifi(void) {
  if(rebootPending) {
    return;
  }
 
  // create the boot Ssid
  String temporarySsid = generateTemporarySSID();
  if(WiFi.softAPSSID().equals(temporarySsid)) {
    // run WiFi server loops
    server.handleClient();
#ifdef DEBUG
    Serial.printf("ESP not configured, blinking soft AP SSID %s\n", temporarySsid.c_str());
#endif
    int lengthSsid = temporarySsid.length();
    int buff[lengthSsid];
    for(int i = 0; i < lengthSsid; ++i) {
        buff[i] = temporarySsid[i] - '0';
    }
    blinkDigits(buff, lengthSsid, blinkDigitsIdle);
    return;
  }
 
#ifdef DEBUG
  Serial.println("Starting HTTP server for Wifi server.");
#endif
  // handle HTTP REST requests
  server.on("/", handleRootHttpRequest);
  server.on("/setup", handleSetupHttpRequest);
  server.onNotFound(handleHttpNotFound);
 
#ifdef DEBUG
  Serial.println("Ensure HTTP headers are collected by the HTTP server.");
#endif
#if defined(ARDUINO_ARCH_ESP8266)
  server.collectHeaders("Cookie");
#elif defined(ARDUINO_ARCH_ESP32)
  const char* collectHeaders[] = { "Cookie" };
  size_t headerkeyssize = sizeof(collectHeaders) / sizeof(char *);
  server.collectHeaders(collectHeaders, headerkeyssize);
#endif
 
  // the soft AP (or WiFi) must be started before the HTTP server or it will result in a crash on ESP32
#ifdef DEBUG
  Serial.println("Starting temporary AP.");
#endif
  WiFi.softAP(temporarySsid, String(), 1, false, 1);
 
#ifdef DEBUG
  Serial.println("Starting HTTP server.");
#endif
  server.begin();
}
 
///////////////////////////////////////////////////////////////////////////
//  connect to WiFi                                                      //
///////////////////////////////////////////////////////////////////////////
void clientWifi(void) {
  if(rebootPending || otaInProgress) {
    return;
  }
 
  // only execute the check every WIFI_RETRY_TIMEOUT milliseconds
  const unsigned long currentTime = millis();
  if(currentTime - lastWifiExecuteTime < WIFI_RETRY_TIMEOUT) {
    return;
  }
 
  // if WiFi is already connected or a reboot is pending just bail out
  wl_status_t wifiStatus = WiFi.status();
  if(wifiStatus == WL_CONNECTED) {
#ifdef DEBUG
    Serial.println("--MARK--");
#endif
    if(!otaStarted) {
      ArduinoOTA.begin();
      otaStarted = true;
    }
    lastWifiExecuteTime = millis();
    return;
  }
 
#ifdef DEBUG
  Serial.printf("Client WiFi not connected: %d\n", wl_status_to_string(wifiStatus));
#endif
 
  DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH);
  if(getConfiguration(CONFIGURATION_FILE_NAME, configuration) == -1) {
#ifdef DEBUG
    Serial.println("Unable to retrieve configuration.");
#endif
    return;
  }
 
  // too many retries so reboot to soft AP
  if(++connectionTries > WIFI_CONNECT_TRIES) {
    configuration["boot"] = BOOT_MODE_SERVER;
    if(!setConfiguration(CONFIGURATION_FILE_NAME, configuration)) {
#ifdef DEBUG
      Serial.printf("Failed to write configuration.\n");
#endif
    }
 
#ifdef DEBUG
    Serial.printf("Restarting in 1 second...\n");
#endif
 
    rebootPending = true;
    return;
  }
 
#ifdef DEBUG
  Serial.printf("Attempting to establish WiFi STA connecton [%d/%d]\n", (WIFI_CONNECT_TRIES - clientConnectionTries) + 1, WIFI_CONNECT_TRIES);
#endif
#if defined(ARDUINO_ARCH_ESP8266)
  WiFi.hostname(configuration["name"].as<String>());
#elif defined(ARDUINO_ARCH_ESP32)
  WiFi.setHostname(configuration["name"].as<const char*>());
#endif
  if (!MDNS.begin(configuration["name"].as<const char*>())) {
#ifdef DEBUG
    Serial.println("Error setting up MDNS responder.");
#endif
  }
  String Ssid = configuration["Ssid"].as<String>();
  String password = configuration["password"].as<String>();
#ifdef DEBUG
  Serial.printf("Trying connection to %s with %s...\n", Ssid, password);
#endif
  //WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
  WiFi.begin(Ssid, password);
 
  lastWifiExecuteTime = millis();
}
 
///////////////////////////////////////////////////////////////////////////
//  blink a string of numbers                                            //
///////////////////////////////////////////////////////////////////////////
void blinkDigits(int* digits, int count, void (*callback)(), int dit, int dah) {
  pinMode(LED_BUILTIN, OUTPUT);
  for(int i = 0; i < count; ++i) {
    do {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(dit);
      callback();
      digitalWrite(LED_BUILTIN, LOW);
      delay(dit);
      callback();
    } while(--digits[i] > 0);
    delay(dah);
    callback();
  }
}
 
///////////////////////////////////////////////////////////////////////////
//  blinkDigits callback                                                 //
///////////////////////////////////////////////////////////////////////////
void blinkDigitsIdle() {
  server.handleClient();
}
 

arduino/wifipreboot/sync.txt ยท Last modified: 2024/09/25 04:36 by office

Wizardry and Steamworks

© 2025 Wizardry and Steamworks

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.