ESP8266 EEPROM Adding a single null character after reset

Okay this has really been driving me crazy. I'm trying to implement a WiFi manager in my program. (yes I know there's a good library out there for this but that doesn't help me learn anything)

I want to implement my own so I learn how this works. I am using EEPROM to store the wifi ssid and password for my project so I don't have to hard code it and I can switch wifi networks if I have to.

I'm using two functions for this:

The write function:

void eepromWriteString(int startAddress, String stringToWrite) {
  char stringChars[stringToWrite.length()];
  stringToWrite.toCharArray(stringChars, stringToWrite.length() + 1);
  for (int i = 0; i < sizeof(stringChars) + 1; i++) {
    Serial.print("Storing ");
    Serial.print(stringChars[i]);
    Serial.print(" in ");
    Serial.println(startAddress);
    EEPROM.write(startAddress, stringChars[i]);
    startAddress++;
    delay(100);
  }

  EEPROM.commit();
}

The read function:

String eepromGetString(int startAddress) {
  char currentChar = EEPROM.read(startAddress);
  String gotString;

  while (currentChar != '\0' && startAddress < 512) {
    currentChar = EEPROM.read(startAddress);
    if (currentChar == '\0') {break;}
    gotString += currentChar;
    startAddress++;
    delay(100);
  }

  return gotString;
}

After writing to EEPROM before resetting I read from it to make sure that it worked and I get back the correct values. So these functions seem to do what I need, however after doing ESP.restart() I read the EEPROM to connect to wifi and for some reason my SSID gets a '\0' inserted into it at the second character so I only get back the first character and it stops reading at that point. But before the reset calling the same code I get back my data just fine with no random '\0'.

I just don't understand how the code works fine before reset, and after reset the stored data is corrupted with a single null character, I wrote a small test program to see if any other parts of the data are messed up but its not just this single null character. Oh! And by the way the password is just fine it read that no problem its just the SSID.

Please help! This problem has been messing with me for a month. The rest of the code is posted below:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include "vars.h"

// Create WebServer object
ESP8266WebServer server(80);

// Returns true if fan is on
bool getFanState() {
  if (digitalRead(TRIGGER_PIN) == HIGH) {
    return true;
  }
  return false;
}

void fanOn() {
  bool fanState = getFanState();
  if (fanState == false) {
    digitalWrite(TRIGGER_PIN, HIGH);
    digitalWrite(LED_BUILTIN, HIGH);
  }
  htmlControls.replace(FAN_OFF_STATUS, FAN_ON_STATUS);
  server.send(200, "text/html", htmlControls);
}

void fanOff() {
  bool fanState = getFanState();
  if (fanState == true) {
    digitalWrite(TRIGGER_PIN, LOW);
    digitalWrite(LED_BUILTIN, LOW);
  }
  htmlControls.replace(FAN_ON_STATUS, FAN_OFF_STATUS);
  server.send(200, "text/html", htmlControls);
}

void clearEEPROM() {
  EEPROM.begin(EEPROM_SIZE);
  for (int i = 0; i < EEPROM_SIZE; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    EEPROM.write(i, 0);
  }

  // Set first time setup to true
  EEPROM.write(MEM.isFirstTimeSetupAddr, 1);
  EEPROM.commit() ? Serial.println("EEPROM Cleared!") : Serial.println("EEPROM Clear Failed");
}

void eepromWriteString(int startAddress, String stringToWrite) {
  char stringChars[stringToWrite.length()];
  stringToWrite.toCharArray(stringChars, stringToWrite.length() + 1);
  for (int i = 0; i < sizeof(stringChars) + 1; i++) {
    Serial.print("Storing ");
    Serial.print(stringChars[i]);
    Serial.print(" in ");
    Serial.println(startAddress);
    EEPROM.write(startAddress, stringChars[i]);
    startAddress++;
    delay(100);
  }

  EEPROM.commit();
}


// Get a string starting from startAddress
String eepromGetString(int startAddress) {
  char currentChar = EEPROM.read(startAddress);
  String gotString;

  while (currentChar != '\0' && startAddress < 512) {
    currentChar = EEPROM.read(startAddress);
    if (currentChar == '\0') {break;}
    gotString += currentChar;
    startAddress++;
    delay(100);
  }

  return gotString;
}

/*
* Fall back to AP mode by setting the isFirstTimeSetup variable to true so when it resets it will start in AP mode,
* and the user can go through the setup process again
*/
void fallBackAP() {
  EEPROM.begin(EEPROM_SIZE);
  // Set to true so when we reset it starts in AP mode
  EEPROM.write(MEM.isFirstTimeSetupAddr, 1);
  EEPROM.commit() ? Serial.println("Fall back Successful!") : Serial.println("Fall back failed...");
  // Initiate a soft reset
  ESP.restart();
  //Serial.println("Hit Reset button n shit");
}

/**
 * This function sets up the wifi for the first time
 * 
 */
void runFirstTimeSetup(String ssid, String pass)
{
  //EEPROM.begin(EEPROM_SIZE);
  // Start in SoftAP mode
  WiFi.softAP(ssid, pass);
  Serial.println("Fan Setup...");
  Serial.println(WiFi.softAPIP());

  // Scan for network SSIDs to display to the user
  int networksFound = WiFi.scanNetworks();
  if (networksFound == 0)
  {
    Serial.println("No Networks Detected...");
    server.send(200, "text/html", "No Networks Detected!");
  }

  // Create an array to store our network credentials
  String detectedSSIDs[networksFound];
  String ssidHTML[networksFound];
  
  for (int i = 0; i < sizeof(detectedSSIDs) / sizeof(detectedSSIDs[0]); i++) {
    detectedSSIDs[i] = WiFi.SSID(i);
    for (int o = 0; o < sizeof(detectedSSIDs) / sizeof(detectedSSIDs[0]); o++) {
      String element = "<option value=\"" + detectedSSIDs[o] + "\">" + detectedSSIDs[o] + "</option>";
      ssidHTML[o] = element;
    }
  }

  // Create a string of the <option> elements
  String htmlString;
  for(int i = 0; i < sizeof(ssidHTML) / sizeof(ssidHTML[0]); i++) {
    htmlString.concat(ssidHTML[i]);
  }

  // Inject custom HTML into networkSetupPage and serve it
  networkSetupPage.replace("<option>", htmlString);
  server.on("/", [](){
    server.send(200, "text/html", networkSetupPage);
  });
  
  server.on("/connect", [](){
    NetworkConfig.networkSSID = String(server.arg("ssid"));
    NetworkConfig.networkPASS = String(server.arg("password"));

    Serial.println("Network SSID: " + NetworkConfig.networkSSID);
    Serial.println("Network PASS: " + NetworkConfig.networkPASS);

    // Try to connect to WiFi using provided credentials
    
    WiFi.begin(NetworkConfig.networkSSID, NetworkConfig.networkPASS);

    while (WiFi.status() == WL_DISCONNECTED) {
      Serial.println("Connecting: " + String(WiFi.status()));
      if (WiFi.status() == WL_CONNECTED) {
        successPage.replace("^networkname^", NetworkConfig.networkSSID);
        server.send(200, "text/html", successPage);
        // Clear eeprom for new write
        clearEEPROM();
        // Store SSID
        eepromWriteString(MEM.networkSSIDBegin, NetworkConfig.networkSSID);
        // Store Password
        eepromWriteString(MEM.networkPassBegin, NetworkConfig.networkPASS);
        break;
      } else if (WiFi.status() == WL_CONNECT_FAILED) {
        badPassPage.replace("^networkname^", NetworkConfig.networkSSID);
        server.send(200, "text/html", badPassPage);
        break;
      } else if (WiFi.status() == WL_NO_SSID_AVAIL) {
        server.send(200, "text/html", "SSID not found...");
        break;
      }
    }

    // Get stored SSID and PASS
    NetworkConfig.networkSSID = eepromGetString(MEM.networkSSIDBegin); // These work!
    NetworkConfig.networkPASS = eepromGetString(MEM.networkPassBegin); // These work!

    Serial.println("SSID: " + NetworkConfig.networkSSID);
    Serial.println("Pass: " + NetworkConfig.networkPASS);

    // Set first time setup variable to false as we have completed network setup
    isFirstTimeSetup = false;

    // Write it to EEPROM
    EEPROM.begin(EEPROM_SIZE);
    EEPROM.put(MEM.isFirstTimeSetupAddr, isFirstTimeSetup);
    EEPROM.end() ? Serial.println("Saved First Time Setup") : Serial.println("Failed to save first time setup state...");

    // Reset and attempt to connect with saved credentials
    Serial.println("Restarting in 5 Seconds");
    delay(5000);
    ESP.restart();
  });
}

void initialize() {
  EEPROM.begin(EEPROM_SIZE);
  while (!Serial) {
    ; // Wait for Serial to start
  }

  server.begin();
  EEPROM.begin(EEPROM_SIZE);
  pinMode(TRIGGER_PIN, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  isFirstTimeSetup = EEPROM.read(MEM.isFirstTimeSetupAddr);
  
  if (isFirstTimeSetup)
  {
    runFirstTimeSetup(SOFT_AP_SSID, SOFT_AP_PASS);
  } else {
    // Get the saved data from EEPROM
    NetworkConfig.networkSSID = eepromGetString(MEM.networkSSIDBegin); // This fails
    NetworkConfig.networkPASS = eepromGetString(MEM.networkPassBegin); // This works

    //fallBackAP();
    
    Serial.println("Saved SSID: " + String(NetworkConfig.networkSSID));
    Serial.println("Saved Pass: " + String(NetworkConfig.networkPASS));

    WiFi.begin(NetworkConfig.networkSSID, NetworkConfig.networkPASS);
    while (WiFi.status() == WL_DISCONNECTED) {
      Serial.print(".");
      delay(500);

      if (WiFi.status() == WL_CONNECTED) {
        Serial.println("Connection Success!");
      } else if (WiFi.status() == WL_CONNECT_FAILED) {
        Serial.println("Wrong Password...");
        fallBackAP();
      } else if (WiFi.status() == WL_NO_SSID_AVAIL) {
        Serial.println("Wrong SSID...");
        fallBackAP();
      }
    }

    EEPROM.end();
    
    server.on("/", []() {
      server.send(200, "text/html", htmlControls);
    });
  
    server.on("/turnon", fanOn);
    server.on("/turnoff", fanOff);
    Serial.println(WiFi.localIP());
  }
}

void setup() {
  Serial.begin(115200);
  while (! Serial) {
    ;
  }
  delay(200);
  initialize();
  digitalWrite(TRIGGER_PIN, LOW);
}

void loop() {
  // put your main code here, to run repeatedly:
  server.handleClient();
}

Please show us "vars.h" also.

problem is here..
look closely..
let me know if you don't see it..

~q

I'd add the use of the infamous "String" also...:wink: When EEPROM storage is needed, avoiding "String" will make things easier and there's no need to build custom functions (e.g. using a "struct" for all the settings, and then just use put and get to read it all together).

1 Like

Please note that the ESP8266 doesn't have EEPROM storage, the EEPROM emulates that by using flash storage.

You have quite some EEPROM.begin() calls in your code. Please note that each time you call that the flash storage is read again and the buffer is overwritten. Only EEPROM.commit() does write to the flash storage.

I agree with @qubits-us that the buffer overrun might be your problem.

Hey! Thanks for all the replies. I have removed all other EEPROM.begin()s from the code I am only calling EEPROM.begin() from setup() now. It had no change in the behavior. I'm still only getting the first character of my SSID and all of my password.

vars.h

String htmlControls = R"(
<!DOCTYPE html>
<html>
  <head>
    <title>Living Room fan Controls</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      html 
      body {
        height: 100%;
        width: 100%;
        margin: 0;
        padding: 0;
        background-color: lightpink;
      }

      h1 {
        text-align: center;
      }
      
      .width_100 {
        padding: 5rem;
        font-size: 5rem;
        border-radius: 20px;
        box-shadow: 10px 10px;
      }
      
      .width_100:active {
        box-shadow: 0 0;
        transform: translate(10px, 10px);
      }
      
      .width_100:hover {
        background-color: orange;
      }
      
      div#controller_container {
        height: 100%;
        width: 100%;
        text-align: center;
        padding-top: 1rem;
      }
      
      div#on_button_container {
        position: relative;
      }
      
      button#on_button {
        background-color: green
      }
      
      button#off_button {
        background-color: red;
      }
    </style>
  </head>
  <body>
    <h1>
      Living Room Fan: OFF
    </h1>
    <br>
    <div id="controller_container">
      <div id="on_button_container">
        <form id="on_button_form" action="/turnon">
          <button id="on_button" class="width_100" type="submit">On</button>
        </form>
      </div>
      <br>
      <div id="off_button_container">
        <form id="off_button_form" action="/turnoff">
          <button id="off_button" class="width_100" type="submit">Off</button>
        </form>
      </div>
      <br>
      <div id="temperature_set_container">
        <form id="temperature_set_form" action="/settemp">
          <label for="temperature">Temperature Set:</label>
          <input id="temperature" type="number" step="0.1" name="temp"/>
          <button id="temperature_set_button" type="submit">Set</button>
        </form>
      </div>
    </div>
  </body>
</html>
)";

String networkSetupPage = R"END(
<!DOCTYPE html>
<html>
  <head>
    <title>Network Setup Page</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Fan Network Setup</h1>
    <div>
      <form action="/connect">
        <label for="ssid">Select Wifi Name:</label><br>
        <select name="ssid" id="ssids" value="Choose your network" required>
          <option>
        </select><br>
        <label for="password">Wifi Password:</label><br>
        <input type="password" id="password" name="password" required><br>
        <input type="checkbox" onclick="showPass()"><span>Show Password</span>
        <input type="submit" value="Submit">
      </form>
    </div>

    <script>
      var checkBox = document.getElementById("password");
      
      function showPass() {
        if (checkBox.type === "password") {
          checkBox.type = "text";
        } else {
          checkBox.type = "password";
        }
      }
    </script>
  </body>
</html>
)END";

String successPage = R"END(
<!DOCTYPE html>
<html>
  <head>
    <title>Lamp control</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Success!<h1>
    <p>You have successfully logged into ^networkname^</p>
  </body>
</html>
)END";

String badPassPage = R"END(
<!DOCTYPE html>
<html>
  <head>
    <title>Bad Password</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      html,
      body {
        background-color: red;
      }

      h1 {
        text-align: center;
        background-color: yellow;
        border-radius: 10px;
      }

      p {
        margin-top: 5rem;
        text-align: center;
        font-size: 20px;
      }

      a {
        margin-top: 5rem;
        padding: 1rem;
        background-color: green;
        text-decoration: none;
        color: black;
        border-radius: 1rem;
        display: inline-block;
        box-shadow: 10px 10px;
      }

      div {
        text-align: center;
      }

      a:active {
        box-shadow: 0 0;
        transform: translate(10px, 10px);
      }
    </style>
  </head>
  <body>
    <h1 id="warning_text">Wrong Password for ^networkname^</h1>

    <p>
      Click the button below to return to the setup page.
    </p>

    <div>
      <a href="/">Back to Setup</a>
    </div>
    <script>
      var warningText = document.querySelector("#warning_text");

      var blinkH1 = setInterval(function() {
        if (warningText.style.visibility === "hidden") {
          warningText.style.visibility = "visible";
        } else {
          warningText.style.visibility = "hidden";
        }
      }, 1000);
    </script>
  </body>
</html>
)END";

const String FAN_ON_STATUS = "ON";
const String FAN_OFF_STATUS = "OFF";

bool isFirstTimeSetup = true;

struct {
  String networkSSID;
  String networkPASS;
} NetworkConfig;

// Try partitioning our memory blocks
struct {
  int networkSSIDBegin = 0;
  int networkSSIDEnd = 100;

  int networkPassBegin = 101;
  int networkPassEnd = 200;

  bool isFirstTimeSetupAddr = 420;
} MEM;

#define DHTPIN 4
#define DHTTYPE DHT22

#define EEPROM_SIZE 512

#define SOFT_AP_SSID "SuperFanMan"
#define SOFT_AP_PASS "fanman123"

#define TRIGGER_PIN 4

Is it because I'm using sizeof() instead of length()?

I have also tried using put() and get() to store the entire NetworkConfig struct and I changed the fields to character arrays, but when I tried that I got back a weird mess random curly braces and new lines. I'm sure I did something wrong there too so I went back to this way because it seemed to mostly "work" :sweat_smile:

No, because you allocate an array of length() bytes but you write length()+1 bytes to it.

I added char stringChars[stringToWrite.length() + 1]; no change in behavior.

Its so weird to me that this works:

// Get stored SSID and PASS
NetworkConfig.networkSSID = eepromGetString(MEM.networkSSIDBegin); // These work!
NetworkConfig.networkPASS = eepromGetString(MEM.networkPassBegin); // These work!

Serial.println("SSID: " + NetworkConfig.networkSSID); // Everything prints out here with no random null character
Serial.println("Pass: " + NetworkConfig.networkPASS); // This also prints out fine

But this fails after reset:

// Get the saved data from EEPROM
NetworkConfig.networkSSID = eepromGetString(MEM.networkSSIDBegin); // This fails
NetworkConfig.networkPASS = eepromGetString(MEM.networkPassBegin); // This works

Serial.println("Saved SSID: " + String(NetworkConfig.networkSSID)); // I only get first character because second character is a null character
Serial.println("Saved Pass: " + String(NetworkConfig.networkPASS)); // This works fine but its the same code

no..
some comments..

//creates array length of the string..
 char stringChars[stringToWrite.length()];
//stuffs 1 too many into array..
  stringToWrite.toCharArray(stringChars, stringToWrite.length() + 1);
//arrays are zero based.. highest element is sizeof -1, the +1 index is out of bounds..
  for (int i = 0; i < sizeof(stringChars) + 1; i++) {

good luck.. ~q

1 Like

A few things I'm confused about here

  1. If I increase the size of stringChars by one then its big enough to hold everything right? I initially added this because the last character of everything was getting cut.
  2. If the problem was in this function why isn't the password mangled as well?
  3. Why do I get successful reads as shown above? But after reset is when it doesn't work?

The behavior is strange to me and obviously above my understanding.

yes, are you making room for a '\0' ending null??
but loose the +1 in the for loop..

when you go out of bounds, only the gods know what will happen..

~q

1 Like

Hey guys!!!! I FIXED ITTTTT!!!!!!!!!!!!!!!! WHooooo hooooooooo

All I did was change this struct from:

// Try partitioning our memory blocks
struct {
  int networkSSIDBegin = 0;
  int networkSSIDEnd = 100;

  int networkPassBegin = 101;
  int networkPassEnd = 200;

  bool isFirstTimeSetupAddr = 420;
} MEM;

to

// Try partitioning our memory blocks
struct {
  int networkSSIDBegin = 50;
  int networkSSIDEnd = 100;

  int networkPassBegin = 101;
  int networkPassEnd = 200;

  bool isFirstTimeSetupAddr = 420;
} MEM;

So does this mean that the memory address 1 is dead??? Because that is the only change I have made. I made all the changes talked about here with the same behavior but when I changed the start address of the password it worked. I know it has a limited number of writes so maybe I messed up that address by writing too much?

idk, strange.. do have to be careful of EEPROM has limited writes..

I was playing with put and get..

#include <EEPROM.h>


struct {
  byte   Saved;
  char   SSID[100];
  char   PASS[100];
} NetworkConfig;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("Hello, ESP32!");

EEPROM.get(0,NetworkConfig);
if (NetworkConfig.Saved != 100){
  Serial.println("not there, save it..");
NetworkConfig.Saved = 100;
strcpy(NetworkConfig.SSID, "123456\0");
strcpy(NetworkConfig.PASS, "superscret\0");
EEPROM.put(0,NetworkConfig);
}

Serial.println(NetworkConfig.SSID);
Serial.println(NetworkConfig.PASS);

}

void loop() {
  // put your main code here, to run repeatedly:
  delay(10); // this speeds up the simulation
}

seems to work in a sim anyways..
glad you got it..

have fun.. ~q

1 Like

Absolutely not.
The reason I asked you to show me the "vars.h" file is this, have a closer look at this definition you have inside the "struct":

  bool isFirstTimeSetupAddr = 420;

Note something strange?
You are defining "isFirstTimeSetupAddr" as a "boolean" variable, so that value 420 is like "true", and "true" value is 1.
So every time you do this:

    EEPROM.put(MEM.isFirstTimeSetupAddr, isFirstTimeSetup);

you are changing the byte at address 1, and when "isFirstTimeSetup" is "false", you're writing a zero, damaging the string!

Change that definition with "int" type and you'll have it working:

struct {
  int networkSSIDBegin = 0; // <-- Put it back to zero
  int networkSSIDEnd = 100;

  int networkPassBegin = 101;
  int networkPassEnd = 200;

  int isFirstTimeSetupAddr = 420; // <--- THIS MUST BE "int"!!!
} MEM;

It's already working with the change listed above. Changing it from 0 to 50. It worked after that specific change.

I'm sure, and I never said it won't. But the reason is the one I told you, if you want to learn something (especially the mystic "bug fixing").

Declaring an address variable as "boolean" is a non-sense, and always brings to unexpected behaviours, in this case it points to EEPROM address 1 if true.
So it's your choice to have a bad or good programming habit, and/or learn something instead of saying a dumb "it works, who cares why". Cheers.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.