Saving a photon's name to eeprom

I need to know the device name of the Photon and display it on a OLED even if it has not connected to the cloud yet. With that being said, I am assuming the Photon has connected at least once prior to get that data. I need to save the Photon’s name in EEPROM so I will have it on powerup / reset. I could not find any documentation on the maximum photon’s name length however, I am starting at address 24 and only looking at 24 bytes of data in the EEPROM. I used the following code to get the Photon’s name in void setup

void setup() {
...
Particle.subscribe("spark/device/name", handler);
Particle.publish("spark/device/name");
}

I created a routine called handler…

void handler(const char *topic, const char *data) { // to get the Hardware name of the Photon
  Serial.println("received " + String(topic) + ": " + String(data));
  Data = String(data);
  Serial.println(Data);
  EEPROM.put(24, Data);
}

In a serial terminal window I get the following from line 2 of the handler

“received spark/device/name: ROOM_DISPLAY_6”

Great, this is what I expect.

In a serial terminal window I get the following from line 4 of the handler

“ROOM_DISPLAY_6”

Great, also what I expect.

But after line 5 executes, I get the following from the EEPROM using this code…

   for (int i = 24; i < 49; i++) { Serial.print(EEPROM.read(i)); Serial.print(" "); } Serial.println( );

“88 93 0 32 14 0 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0”

I have read about data stored to EEPROM must be saved as char[ ] but can’t seem to figure out how to convert a “const char *data” to char EEPROMdata[ ].

Any suggestions or links to suggested reading?

Try this

char name[strlen(data)+1];
strcpy(name, data);
EEPROM.put(24, name);

String object are somewhat special in multiple regards :wink:

2 Likes

I couldn’t get the above code to work without modification. EEPROM.put doesn’t like the char name[strlen(data)+1];, as the compiler complains. It wants a fixed size char array:

//Get device name and store it in EEPROM
void device_name_handler(const char *event, const char *data)
{
    char name[strlen(data)+1] ;
    char fname[20] = "" ;
    strcpy(name, data) ;
    strcpy(fname, name) ; // copy into fixed size char array for EEPROM.put
    EEPROM.put(device_name_address, fname) ;
}

then to read it in the main code loop:

#define device_name_address 0x100
char device_name[20] = “DEFAULT” ;

// Get device name from the cloud
Particle.subscribe("particle/device/name", device_name_handler, MY_DEVICES) ;
Particle.publish("particle/device/name", NULL, 60, PRIVATE) ;
delay(2000) ;
EEPROM.get(device_name_address, device_name) ;

This all works, and I can include the device name in my subsequent data publishes. I haven’t tried just making name fixed size yet, that may or may not work, and would be cleaner if it did. I was just happy to get this working.

I put the Particle.subscribe/publish in the main loop because WiFi/cloud is not enabled during startup(), and I don’t want to enable it there. I am in SEMI_AUTOMATIC mode, and only enable WiFi as needed for lowest power use, and only want to do it once every waking from deep sleep.

That certainly does work. You can create a global char array that's bigger than any name you will ever use, but you will only write the number of bytes (plus 1 for the terminating 0) that actually are in your name if you do it this way,

char deviceName[50];
const int nameLoc = 24;

void setup() {
   // Particle.subscribe("spark/device/name", handler);
    Serial.begin();
   // Particle.publish("spark/device/name");
    EEPROM.get(nameLoc, deviceName);
    Serial.printlnf("deviceName is: %s", deviceName);
    for (int i=nameLoc; i<nameLoc + 50; i++) {
        Serial.printf("%d ", EEPROM.read(i));
    }
    
    Serial.printlnf("deviceName from get is: %s", EEPROM.get(nameLoc, deviceName));
}

void loop() {}


void handler(const char *topic, const char *data) { // to get the Hardware name of the Photon

    int len = strlen(data) +1 ;
    strncpy(deviceName, data, len);
    EEPROM.put(nameLoc, deviceName);
}

So, this was run once with the publish and subscribe lines un-commentd. When I comment them out, and run again, I get this from the print statements:

deviceName is: Knut
75, 110, 117, 116, 0, 255, 255, 255, ... 255
deviceName from get is: knut

As you can see, the EEPROM.put only wrote the 4 letters of the name plus the terminating 0.

You should only call Particle.subscribe once, so putting it in loop() is not a good idea, and there's no need for it. You can call it in setup() even if you're not connected to the cloud. From the docs:

Particle.subscribe() returns a bool indicating success. It is ok to register a subscription when the device is not connected to the cloud - the subscription is automatically registered with the cloud next time the device connects.

1 Like

Thanks for the feedback. WRT the placement of the subscribe. I can’t find in the docs when I wake from SLEEP_MODE_DEEP, if it runs setup or not. I figured it out at one point, but don’t remember what I found out. If it does run setup when waking from SLEEP_MODE_DEEP, then it wouldn’t seem to matter where I put it. What are the issues with calling it multiple times with the same arguments? It’s not an issue to move it there, now that I know it doesn’t need to be connected. This code only runs once every four hours, then goes to SLEEP_MODE_DEEP.

I did try to use a global variable to store the device_name (instead of using EEPROM), but was not successful. Doesn’t seem to work like “normal” C, so I went to using the EEROM. I may revisit this at a later time. For now, my code does exactly what I want functionally at least. This is a different issue than the fixed length of the char array in the handler…I’m still digesting your comments on that.

To avoid constant writing to the EEPROM, I modified my code to read from the EEPROM, compare it to what the cloud returns, and only write to the EEPROM if the two are not equal.

Best,

Austin

I'm not sure what you mean by "normal c". I wasn't suggesting that you use a global variable instead of EEPROM, just that you could have a global variable for your device name so it can be used in the handler, as well as in loop if you want.

It does run setup() when it wakes from deep sleep, but I'm not sure how that affects where you would put it, unless your loop is only running once before it goes to sleep - in that case, you could put it there. I'm not sure what the issues are in calling it multiple times, you're just not supposed to do it that way. What you're doing by calling Particle.subscribe, is registering your handler and event name with the cloud, and there's no need to do that more than once.

To have the RAM content survive a system reset (which happens when waking from deep sleep) you need to mark the global variable retained. But that doesn't have anything to do with "normal C" or any other language - after a reset normal variables will be cleared/undefined on any machine irrespective of used language.

First, doing (or attempting to) setup things over and over again although you know they have been set up already is just superfluous work and may (in general) have unexpected side effects (when not used as designed).
But in particular registering any cloud feature some time after the connection was established won't have any effect and hence cause confusion. You may add some extra tasks before the call or later on decide to tweak the call and add "mutating" arguments and out of a sudden your "working" code "stops" working as expected. Where it in fact never worked as you anticipated it to do. Only the first call a few seconds after connect worked and all subsequent ones were just ignored.

@Ric, for safety reasons I'd alter your suggested code this way

void handler(const char *topic, const char *data) { // to get the Hardware name of the Photon
  strncpy(deviceName, data, sizeof(deviceName)-1);
  deviceName[sizeof(deviceName)-1] = '\0';  // lazy way to make sure to have the string terminated 
  EEPROM.put(nameLoc, deviceName);
}

If the string in data is shorter than deviceName[] can hold, you're fine since strncpy() would only copy the data up to (and including) the zero-terminator. If it should happen to be longer than you expect your code would corrupt some data while the adapted code would just truncate the string there.
The "lazy termination" is there to take care of the behaviour of strncpy() which would just truncate at the last byte but not terminate the string - if the original string was terminated it would just copy that terminator, but since in case of truncation there was not terminator in the original it can't be copied and won't be "artificially" added.

A less lazy way to terminate would catch the return value of strncpy() and do the termination only when needed. But since the check would require more processing cycles than the lazy solution I think it's fine to be lazy at times :wink:

1 Like

I guess I was being too lazy in assuming nobody would use a name as long as 50 characters :wink:. How about a middle-lazy way using snprintf() instead of strncpy() since it terminates the string for you? I generally use the former in my own code because of the ensured termination. Is there any particular reason to prefer strncpy() over snprintf()?

void handler(const char *topic, const char *data) { // to get the Hardware name of the Photon
    snprintf(deviceName, sizeof(deviceName), "%s",  data);
    EEPROM.put(nameLoc, deviceName);
}

snprintf() is actually way more elaborate than my “less lazy” approach since the internals of snprintf() do tons of stuff (i.e. format string parsing) behind the scenes. It may just add one line of code on your side but does add several dozens of machine code instructions.

1 Like