I am having an issue storing/removing data in ESP32S3 Preferences. I took the bits of code to save/delete/read data to/from the Preferences db from my project and created a test sketch that I have attached and uploaded.
sketch_aug28b.ino (10.3 KB)
I believe the issue is one of timing in the Preferences library. I am saving launcher_id/MAC address pairs in Preferences. The rule is there can only be one launcher_id per MAC address and one MAC address per launcher_id.
The error occurs in the saveToPreferences() function. In that function, I am saving new data to Preferences, and also insuring the rule of launcher_id/MAC address is preserved as well as ignoring duplicates. The code behaves correctly if I (1) save the data first, (2) create a delay by printing out the Preferences, and (3) remove any entries that violate the launcher_id/MAC address rule. The keys to making the code work are
(1) the delay introduced between saving the new data and deleting the old data, and
(2) saving the data first before deleting the data.
Remove that delay or deleting the data before saving it, and the code breaks.
At the top of the code, you will see a const bool BREAK_THE_CODE variable. Setting it to true will break the code a shown in the output, and setting it to false will make the code behave. The variable adds in the printPreferences() delay between saving the data and deleting the data in the saveToPreferences() function. I have included a set of test cases in the setup() function, and you can see the contents of the Preferences db as the code progresses in the serial output.
The correct final output, with BREAK_THE_CODE=false:
print keys:
number of keys = 4
key=1
key=3
key=8
key=9
print preferences:
{1: 01:02:03:04:05:06}, {010203040506: 1}
{3: 03:05:04:03:02:01}, {030504030201: 3}
{8: 08:05:04:03:02:01}, {080504030201: 8}
{9: 02:05:04:03:02:01}, {020504030201: 9}
The broken final output if BREAK_THE_CODE=true. Note that the intervening steps of saving data and ignoring duplicates works in both cases. The failure occurs when I try to save and delete data.
print keys:
number of keys = 4
key=1
key=3
key=8
key=9
print preferences:
{1: 01:02:03:04:05:06}, {010203040506: 1}
{3: 03:05:04:03:02:01}, {030504030201: 3}
{8: 08:05:04:03:02:01}, {080504030201: 8}
{9: }, {080504030201: 8}
The error occurs in the last test case, where I try to add a MAC address with a new launcher_id. The result should be to delete the old launcher_id/MAC address and add thew new one, as shown in the correct output above. The incorrect output shows that launcher_id does not have an associated MAC address, and the second entry is totally wrong.
I am curious if anyone has run into a situation like this where (1) the order of saving and deleting data from Preferences matters and (2) a delay is needed between saving and deleting data from Preferences.
Is there a better way to achieve what I want to achieve without the artificial delay of printing the Preferences data in the middle of the saveToPreferences() function?
If the delay is necessary, is there a better way to introduce a delay in the code?
Thanks!
#include <Preferences.h>
#include <string.h>
#include <cstring> // For strcpy
#include <stdbool.h>
/* Preference Data
* launcher_id_str: macAddrStr - note launcher_id is an int, so have to convert to a String to use as a key
* "keys": [launcher-1_id, launcher-2_id, launcher-3_id,] - an int array of the launcher_ids using the key "keys"
*/
Preferences preferences;
const char PREFERENCES_NAME_SPACE[] = "multiLauncher";
const bool CLEAR_PREFERENCES = true;
const bool TEST_PREFERENCES = true;
const bool BREAK_THE_CODE = true;
/*
Testing different scenarios.
* Add a launcher_id MAC address pair
* Add a duplicate launcher_id and MAC address
* Add a duplicate launcher_id with a different MAC address
In each case print out the contents of the preferences and the keys
Start the process each time with the preferences db empty
*/
void setup() {
Serial.begin(115200);
delay(1000);
Serial.printf("Storage Setup*****");
if (CLEAR_PREFERENCES) {
preferences.begin(PREFERENCES_NAME_SPACE, false);
preferences.clear();
preferences.end();
}
if (TEST_PREFERENCES) {
Serial.println("Testing Preferences - START");
printKeys();
printPreferences();
// Add the first set of data
char mac_str[] = "01:02:03:04:05:06";
saveToPreferences(1, mac_str);
printKeys();
printPreferences();
// Add a second set
saveToPreferences(3, "03:05:04:03:02:01");
printKeys();
printPreferences();
// Add a third set
saveToPreferences(2, "02:05:04:03:02:01");
printKeys();
printPreferences();
// Add a fourth set
saveToPreferences(8, "08:05:04:03:02:01");
printKeys();
printPreferences();
// Add a duplicate launcher_id MAC address pair
saveToPreferences(3, "03:05:04:03:02:01");
printKeys();
printPreferences();
// Update the launcher_id=2 a new MAC address
// This should replace the launcher_id=2 with launcher_id=9
// for MAC address 02:05:04:03:02:01
saveToPreferences(9, "02:05:04:03:02:01");
printKeys();
printPreferences();
Serial.println("Testing Preferences - DONE");
}
Serial.println("Storage setup done");
}
/*
Save a launcher_id/MAC address pair to the preferences DB.
* Ignore duplicates
* If the MAC address for a particular launcher_id changes,
then update the MAC address and remove the old entries
*/
void saveToPreferences(int launcher_number, char* macStr) {
Serial.printf("Saving to preferences: launcher_number=%d, macStr=%s\n", launcher_number, macStr);
preferences.begin(PREFERENCES_NAME_SPACE, false);
char macStr_short[13];
macToShort(macStr_short, macStr);
int key = preferences.getInt(macStr_short);
// deleting the data before saving always breaks the code
// so I moved the deleting part to after the saving part
// if (key != launcher_number && key != 0) {
// Serial.printf("\tremoving mac=%s, mac_short=%s, launcher_id=%d\n", macStr, macStr_short, key);
// char key_str[6];
// keyToString(launcher_number, key_str);
// preferences.remove(key_str);
// removeKey(key);
// }
char new_key_str[6];
keyToString(launcher_number, new_key_str);
preferences.putString(new_key_str, macStr);
Serial.printf("\tSaving %s: %s\n", new_key_str, macStr);
preferences.putInt(macStr_short, launcher_number);
Serial.printf("\tSaving %s: %d\n", macStr_short, launcher_number);
if (!BREAK_THE_CODE) {
printPreferences(); // remove this line, and the code breaks as shown in the output!
}
if (key != launcher_number && key != 0) {
Serial.printf("\tremoving mac=%s, mac_short=%s, launcher_id=%d\n", macStr, macStr_short, key);
char key_str[6];
keyToString(launcher_number, key_str);
preferences.remove(key_str);
removeKey(key);
}
preferences.end();
saveKey(launcher_number);
}
/*
Save a key to Preferneces. Check for duplicates and ignore them.
Create the "keys" array if needed.
*/
void saveKey(int launcher_number) {
Serial.printf("Save Key: launcher_number=%d\n", launcher_number);
preferences.begin(PREFERENCES_NAME_SPACE, false);
size_t storedSize = preferences.getBytesLength("keys");
int arraySize = storedSize / sizeof(int);
//Serial.printf("\t#1 storedSize=%d\n", storedSize);
if (storedSize > 0) {
int retrievedArray[arraySize + 1];
preferences.getBytes("keys", retrievedArray, storedSize + sizeof(int));
bool duplicate = false;
for (int i = 0; i < arraySize; i++) {
if (launcher_number == retrievedArray[i]) {
duplicate = true;
//Serial.printf("Found a duplicate: launcher_number=%d\n", launcher_number);
// ignore the duplicate, just don't add another.
}
}
if (!duplicate) {
retrievedArray[arraySize] = launcher_number;
preferences.putBytes("keys", retrievedArray, sizeof(retrievedArray));
}
}
else {
// storing first key, so create a new array and save it
int keys[] = {launcher_number};
preferences.putBytes("keys", keys, sizeof(keys));
}
// storedSize = preferences.getBytesLength("keys");
// Serial.printf("\t#2 stored_size=%d\n", storedSize);
preferences.end();
}
/* Copy the keys into a new array, one element shorter than the
original array and leave out the one we want to remove.
*/
void removeKey(int key) {
Serial.printf("Remove Key: key=%d\n", key);
preferences.begin(PREFERENCES_NAME_SPACE, false);
size_t storedSize = preferences.getBytesLength("keys");
int arraySize = storedSize / sizeof(int);
//Serial.printf("#1 stored_size=%zu, arraySize=%d\n", storedSize, arraySize);
if (arraySize > 1) {
//Serial.println("here");
int retrievedArray[arraySize];
int newArray[arraySize - 1];
preferences.getBytes("keys", retrievedArray, storedSize);
for (int i = 0; i < arraySize-1; i++) {
//Serial.printf("key=%d, i=%d, array_value=%d\n", key, i, retrievedArray[i]);
if (key != retrievedArray[i]) {
newArray[i] = retrievedArray[i];
}
else {
newArray[i] = retrievedArray[i+1];
}
//Serial.printf("newArray[i]=%d\n", newArray[i]);
}
preferences.putBytes("keys", newArray, sizeof(newArray));
}
else {
preferences.remove("keys");
}
storedSize = preferences.getBytesLength("keys");
//Serial.printf("#2 new stored_size=%zu\n", storedSize);
preferences.end();
}
/*
Print out each key on one line from preferences.
*/
void printKeys() {
Serial.println("print keys: ");
preferences.begin(PREFERENCES_NAME_SPACE, false);
size_t storedSize = preferences.getBytesLength("keys");
//Serial.printf("#1 stored_size=%d\n", storedSize);
if (storedSize > 0) {
int arraySize = storedSize / sizeof(int);
Serial.printf("\tnumber of keys = %d\n", arraySize);
int retrievedArray[arraySize];
preferences.getBytes("keys", retrievedArray, storedSize);
for (int i = 0; i < arraySize; i++) {
Serial.printf("\tkey=%d\n", retrievedArray[i]);
}
}
else {
Serial.printf("\tNo keys in the store\n");
}
preferences.end();
}
/*
Print out the preferences. Preferences consist of three types:
launcher_id_str : macString
macString: launcher)id_int
"keys": [launcher_id_int_1, launcher_id_int_2, ...]
Use print keys to print the keys preferences
*/
void printPreferences() {
Serial.println("print preferences: ");
preferences.begin(PREFERENCES_NAME_SPACE, false);
size_t storedSize = preferences.getBytesLength("keys");
if (storedSize > 0) {
int arraySize = storedSize / sizeof(int);
//Serial.printf("\tnumber of keys = %d\n", arraySize);
int retrievedArray[arraySize];
preferences.getBytes("keys", retrievedArray, storedSize);
for (int i=0; i < arraySize; i++) {
int key = retrievedArray[i];
//Serial.printf("\tkey=%d\n", key);
char key_str[6];
keyToString(key, key_str);
String cma = preferences.getString(key_str);
//Serial.printf("\tcma=%s\n", cma.c_str());
uint8_t mac_array[6];
macToArray((char*)cma.c_str(), mac_array);
char macStr_short[13];
macToString(mac_array, macStr_short, 13, true);
//Serial.printf("\tmacStr_short=%s\n", macStr_short);
int same_key = preferences.getInt(macStr_short);
Serial.printf("\t{%s: %s}, {%s: %d}\n", key_str, cma.c_str(), macStr_short, same_key);
}
}
else {
Serial.printf("\tNo data stored in Preferences\n");
}
preferences.end();
}
/*
Use to convert a launcher_id_int to a launcher_id_string, so it can
be used as a key in preferences.
*/
void keyToString(int launcher_number, char* launcher_number_str) {
char buffer[6];
sprintf(launcher_number_str, "%d", launcher_number);
//Serial.printf("launcher_number=%d, launcher_number_str=%s\n", launcher_number, launcher_number_str);
}
/*
MAC addresses for ESP_NOW need to be an array, whereas it is easier to store the MAC address as
a string in preferences. However, keys used in preferences cannot exceed 15 characters, so a formatted
MAC address as a string (18 characters) is too long for a preference key. Therefore, we nned a third MAC address
form as a stirng without the ':' to keep it at 12 characters.
*/
// Formats MAC Address from uint8_t array to a string for printing and saving to Preferences, short format removes ':'
void macToString(const uint8_t *macArray, char *macString, int maxLength, bool short_format) {
if (short_format) {
snprintf(macString, maxLength, "%02x%02x%02x%02x%02x%02x", macArray[0], macArray[1], macArray[2], macArray[3], macArray[4], macArray[5]);
}
else {
snprintf(macString, maxLength, "%02x:%02x:%02x:%02x:%02x:%02x", macArray[0], macArray[1], macArray[2], macArray[3], macArray[4], macArray[5]);
}
}
/*
The long string version (with ':') of the MAC address is passed as function arguments, so when saving
the MAC address to preferences it has to be trimmed down to the short version.
*/
void macToShort(char* macShort, char* macLong) {
int i, j;
Serial.printf("size macShort=%d, size macLong=%d\n", strlen(macShort), strlen(macLong));
for (i = 0, j = 0; i < strlen(macLong); i++) {
if (macLong[i] != ':') {
macShort[j++] = macLong[i];
}
}
macShort[j] = '\0';
}
/*
Converts a string version of the mac address back into a uint8_t array used by esp-now
*/
void macToArray(char *macString, uint8_t *macArray) {
sscanf(macString, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &macArray[0], &macArray[1], &macArray[2], &macArray[3], &macArray[4], &macArray[5]);
}
/*
Not needed for this excercise.
*/
void loop() {
}