fbpx

While working on a recent project, using a custom version of the ESP32, I had a particular need to be able to update the firmware of the device, as well as allow the end-user to set various configuration options on the device. This need can best be described as follows below:

1. The device firmware needs to be updated periodically, as new features are needed, or when bugs are discovered by the end-user, that needs fixing. The problem encountered was that the physical device will be quite far away from me, so travelling, or sending the device to and fro via post was not really an option.

Sending uncompiled firmware to the customer is a possibility, but also risky and prone to errors, as everyone is not always inclined to learn how to update firmware via USB port from an IDE, and accidental changes to code can render it unable to compile and upload in the first place.

The device also needs to run on battery power, in remote areas without connectivity. Conserving battery power is thus also a very important issue, one that prevents me from having a permanent OTA server running via WiFi.

2. The second issue was that there are certain setup parameters that needed to be set by the end-user, these include addresses and others. My initial thoughts were to implement a simple UI via UART, but that I also quickly saw that that, although very useable to me, would not appeal very much to an end-user.

These two issues, OTA and Configuration, both done through WiFi, seemed mutually exclusive as far as all the research that I have done online seemed to be concerned. To complicate it even further, the WiFi HAD to stay off when not in use.

I believe that I have developed a very neat, workable solution, that I would like to share with you today, in the hope that it will solve some problems for somebody out there, that maybe having a similar problem.

My solution makes use of a “flag” set in the ESP32/ESP8266 device’s EEPROM.
This “flag” can then be set to a “Normal” Run mode, a Firmware Update Mode, and a Configuration Mode.

Please Note: The code below has been tested, and works perfectly. You will however have to make modifications to use it for your own needs.

The code is also very long, so I will explain the operation here:

When first started, the chip will run your standard code.
When you quickly press the PGM button, 5 times after each other, a flag will be set to 0x0F.
This will then activate the ESP Soft IP.
You can then connect to this AP, and telnet to 192.168.4.1 on port 23

You will be presented with a very brief menu, where you can SET Firmware Mode, Exit or see the menu again.

This can be expanded to suit your needs

On setting Firmware Mode, the Flag 0xFF is written to EEPROM, and the ESP is restarted.
upon startup, you once more connect to the generated SoftIP, and then browse to http://192.168.4.1, log in with username admin, and password admin.

You can now load and upload a .bin firmware file to the ESP.
on successful upload, the flag 0x00 is once more written to EEPROM.

After restarting, the device will once more run your standard code.


Let us look at how I have done this:

The actual code will be commented with details, please read that for more info.

#include "EEPROM.h"
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

#define EEPROM_SIZE 64 // This can be smaller or larger, adjust to    //your needs as well as the chip capabilities
int wifi_ap_on = 0; // the address for the "flag"


// Wifi for Firmware Update and Configuration Server(s)
const char* host = "ESP32/8266";
char* ssid = "--- your desired ssid ---";
char* password = "--- your password ---";

// Define two types of Servers, they will however not run at the same // time
WebServer OTAserver(80); // OTA Server Port
WiFiServer ConfigServer(23); // Configuration Server Port

String header; // HTML requests will be stored in here

// HTML Pages for Firmware Updater
/*
 * These are the standard OTA examples that came with Arduino IDE
 * Modify and enhance as see fit, keeping in mind that my application
 * would not have access to the internet
 */

/*
 * Login page
 */
const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP Firmware Update Page</b></font></center><br>"
                
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";

const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
// End HTML Pages 

// Other variables
int Btn = 0; // The PGM button, Usually connected to GPIO0
int wifi_ap = 0; // The Running MODE
int buttonState;
int buttonValue = 0;
int lastButtonState = LOW;
int buttoncount = 0;

void setup()
{
   Serial.begin(115200);
   EEPROM.begin(EEPROM_SIZE); initiate EEPROM
  /*
   * Upload the sketch via USB the first time, and let chip run for
   * a few seconds. We need to set the EEPROM location to a known
   * initial value. Then comment the following 3 lines, to prevent 
   * the chip from overwriting the EEPROM location on next startup
   */
  pinMode(Btn,INPUT);
  EEPROM.write(wifi_ap_on,0x00); // comment this line after first run
  EEPROM.commit();// comment this line after first run
  delay(50);// comment this line after first run
  wifi_ap = EEPROM.read(wifi_ap_on); // read EEPROM value into "flag"
  
  if (wifi_ap == 0x00) { 
   // This will be executed on normal startup
   // Place all your normal initialisation stuff here
   // For example other pin configurations and 
   // peripheral  initialisations
  } else if (wifi_ap == 0xFF) { // Firmware Update MODE
   // Setup for OTA Webserver.
   // I have used SoftAP mode, as internet would not
   // be available anyway.

    WiFi.softAP(ssid,password);
    IPAddress IP = WiFi.softAPIP();
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP()); 
     
    if (!MDNS.begin(host)) { //http://esp32.local
        Serial.println("Error setting up MDNS responder!");
        while (1) {
        delay(1000);
      }
    }

    
    server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress

/*
 * NOTE : We clear the EEPROM Flag after a sucessful Firmware Update
 * That way, the chip will stay in OTA mode on a failure.
 */
        EEPROM.write(wifi_ap_on,0x00);
        EEPROM.commit();
        delay(5);
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
    OTAserver.begin();
  } else if (wifi_ap == 0x0F) { // End of Mode 0xFF 

    //This is Configuration Mode
    //This code will normally not be executed,
    //As the device exits Config mode on restart, and
    //We dont write Mode 0x0F to EEPROM

  } // End of Mode 0x0F
} // End of Void Setup

void loop() 
{
  CountButton(); // Count Button Presses
  if (buttoncount == 5) { // Activate Configuration Mode
    buttoncount = 0;
    StartSoftAP(); // Start a SoftAP
  }
  if (wifi_ap == 0x0F) { // Code for the Config Server (TELNET Style)
       WiFiClient client = ConfigServer.available(); // Start Config
       if (client) {
          String currentLine = "";
          client.println();
          client.println("Telnet Server - Welcome");
          client.println();
          client.println("Press <Enter> to Login");
          
    while (client.connected()) {
      if (client.available()) {
       char c = client.read();
       if (c == '\n') {
        if (currentLine.length() == 0) {
           
        } else {
          // Process Commands
       if (currentLine == "Master123abc") {
        telnetLogin == true;
        client.println();
        client.println();
        client.println("****** MENU ******");
          client.println();
          client.println("MENU -  SHOW THIS MENU");
          client.println("RUF  -  Enable Firmware Update Mode");
          client.println("EXIT -  Exit Menu and RESTART");
          client.println();
          client.print("Command> ");
          currentLine = "";
       }
       // Show MENU
       if (currentLine == "MENU") {
        client.println();
        client.println();
        client.println("****** MENU ******");
          client.println();
          client.println("MENU -  SHOW THIS MENU");
          client.println("RUF  -  Enable Firmware Update Mode");
          client.println("EXIT -  Exit Menu and RESTART");
          client.println();
          client.print("Command> ");
          
       }
 
       // EXIT AND RESTART
       if (currentLine == "EXIT") {
        client.println();
        client.println("Restarting in 5 Seconds");
        delay(5000);
        client.stop();
        ESP.restart();
      }
      // Upgrade Frmware Mode
      if (currentLine == "RUF") {
          client.println();
          client.println("Enable Firmware Update Mode");
          client.print("Connect to SoftAP :");
          client.println(ssid);
          client.println("Browse to http://192.168.4.1 ");
          client.println("Login with Username admin, Password admin");
          EEPROM.write(wifi_ap_on,0xFF); // Set Flag in EEPROM
          EEPROM.commit();
          delay(5000);
          client.stop();
          ESP.restart(); // Restart Device
      }
      
       // End Process commands 
           
          currentLine = ""; 
        }
        
       } else if (c != '\r') {
        currentLine += c;
       }  
        
      } // End if client Available
    } // End While Client Connected
    client.stop();
   } // END Client
  } // END MODE 0x0F
  if (wifi_ap == 0xFF) {
    OTAserver.handleClient(); // Handle OTA Server
    delay(1);
  }
  if (wifi_ap == 0x00 )
    // Normal code goes here , to be executed every cycle
  {

} // End of Void Loop

void StartSoftAP() // Start SoftAP, for Configuration Server
{
  WiFi.mode(WIFI_OFF);
  WiFi.softAP(ssid,password);
  IPAddress IP = WiFi.softAPIP();
  ConfigServer.begin();  
  wifi_ap = 0x0F; // Set Flag to Config Mode
                  // Note that we dont write it in EEPROM
}

void CountButton() // Counts Button Presses, to enable Config Mode
{
  //int buttonState;
  //int buttonValue = 0;
  //int lastButtonState = LOW;
  //int buttoncount = 0;
  //unsigned long lastDebounceTime = 0;
  //int debounceDelay = 50;
  buttonValue = digitalRead(Btn);
  if (buttonState != lastButtonState) {
    if (buttonValue == LOW) {
      buttoncount++;
      if (buttoncount > 10) buttoncount = 0;
    }
  }
  delay(50);
  lastButtonState = buttonValue;
  // End Debouncing
  if (buttoncount == 5) digitalWrite(LED,HIGH);
}

This concludes a very long piece of code, question are welcome.
Thank you

Categories:

No responses yet

Leave a Reply

Your email address will not be published.