0

I'm trying to store (read&write( a struct using LittleFS on ESP8266.

Appreciate assistance

struct oper_string
{
  bool state;     /* On or Off */
  uint8_t step;   /* Step, in case of PWM */
  uint8_t reason; /* What triggered the button */
  time_t ontime;  /* Start Clk */
  time_t offtime; /* Off Clk */
};
0

3 Answers 3

4

you can think of a struct as an array (group) of variables which might be of a different sizes and struct helps you take specific portion of this string. Lets say you have a struct of

struct foo{
   bool first;
   uint8_t Second;
   uint16_t Third;
} structMember;

Then it is stored in a row in exactly same order as they have been defined

---------------------------
| bool | uint8_t | uint16_t|
|1 byte| 1 bytes | 2 bytes |  
--------------------------
1B+1B+2B = sizeof(srtuctMember)

File.write() saves date in a file in block of 1Byte each.

When you send array to the function it actualy receives only a pointer to its beginning. That is why many functions take a size as a second argument.

When you send a data into a function in a format its not designed to take you can tell the compiler that the function should trait it as a different type of variable. Lets say we have function defined as void sampleFunction(byte var1) then we can use it by

char x='j';
sampleFunction((byte) x);

which tells compiler to treat 'x' as if it was byte.

when you pass array you are not really passing the actual data, but an information where the data is(same with the struct member), so by putting asterisk after the definition you are telling compiler the format of data it points to. and when you read it back you will get all the contend back as same data in same order... which struct then divides in a same way

file.write((byte*) &StructMember,sizeof(structMember));
4
  • 1
    I only wish somebody have explained this to me when I was struggling to understand it dealing with char and utf8 few years ago...
    – Tomas
    Jul 17, 2022 at 23:54
  • So the idea is that each member in this struc is referenced as a byte / utin8_t pointer? and the result is an arrary of pointers (without any delimiter, space or so) ?
    – guyd
    Jul 18, 2022 at 5:12
  • 1
    @guyd: StructMember is an instance – not a member – of the struct. The idea is that the struct as a whole is to be handled as an array of bytes. More precisely, the address of the struct is to be treated as the starting address of an array of bytes. That is a pointer to an array (of bytes), not an array of pointers. Jul 18, 2022 at 8:04
  • @EdgarBonet - Sorry for my english, but a member of the struct is for example offtime which is referenced an address (&). Thank you for clearing this out.
    – guyd
    Jul 18, 2022 at 8:21
3

Just as a complement to Tomas' answer (which is perfectly valid), I would like to point out that consecutive members of a struct may not be adjacent in memory. Some structs have padding within them, which is needed to satisfy alignment requirements.

The struct given by Tomas as an example will not have padding, but yours may. For example, on my 64-bit PC, your struct is stored as follows:

offset size member
0 1 state
1 1 step
2 1 reason
3 5 (padding)
8 8 ontime
16 8 offtime

In the table above, “offset” is the position of the member relative to the start of the struct. The padding after reason is needed in order to align ontime at an address that is multiple of 8. Your ESP being a 32-bit machine, it is likely to use only a single byte of padding.

Padding may be an issue for two reasons:

  1. It can differ between computer architectures. Thus, if you plan to write the file on the ESP and read it back on your PC (or vice versa), it is one of the possible sources of incompatibility, the others being endianness and member sizes.

  2. It is a waste of disk space, although this might not be an issue if you are only loosing 1 byte out of 20.

If padding can be problematic, the only valid option is to write and read the struct one member at a time. Conversely, if the file is only ever going to be accessed by the ESP, and you don't mind loosing a few percent of the file to padding, then don't worry, follow Tomas' advice, and read and write the struct as an array of bytes: it is just simpler.

5
  • thank you for that point of view (which was a hunch I got when commenting on tomas's answer). I have 3 other files on that ESP ( can be either ESP32 or ESP8266 ), that paramters are stored. Other option I was thinking of was save it a string file value0;value1;value2... having a delimiter as a marker. And lasltly, using ArduinoJSON as key/ value pairs, and again save it similiarly to a parameter file I have on board. What would you recommend ?
    – guyd
    Jul 18, 2022 at 10:39
  • Thanks I was intending to refer to padding, as a part or reasoning for using sizeof rather then specific size number, and wanted to point it out in regards to bool, but somehow it got lost in edit attempting not to write too many points. I accept I am a IDYer, and attempt to write mine answers on a simpler level. And thatnks for improving so far all of mine answers here :-) you deseve the editing rights
    – Tomas
    Jul 18, 2022 at 10:50
  • @guyd: I almost always favor text formats over binary: they are somewhat harder to parse for a computer, but way more convenient for the humans who are going to debug the stuff. Binary is the choice for when you need space efficiency (either in the file itself or the code that handles it) or I/O performance. That being said: all the options you suggest are valid. Pick the format you are most comfortable with. Jul 18, 2022 at 11:08
  • @Tomas: Thanks! Sometimes I am a nitpicker. Your answers are helpful and valuable contributions, and improving upon something that is already good feels quite rewarding. ;-) Jul 18, 2022 at 11:09
  • Thank you both for a complete answer! Guy
    – guyd
    Jul 18, 2022 at 11:16
0

one can write a struct directly into a SPIFS/ FFAT file like this:

 * EXAMPLE READING AND WRITING A C++ STRUCT TO A FILE
 */
#include <FS.h>
#include <SPIFFS.h>

typedef struct {
  char someString[10];
  float someFloat;
} MyStruct;

MyStruct myStruct = { "test test",1.7 };

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  
  if(!SPIFFS.begin()){
      Serial.println("Card Mount Failed");
      return;
  }

  strncpy( myStruct.someString, "testtesttest", sizeof(myStruct.someString) );
  File myFile = SPIFFS.open("/somefile.txt", FILE_WRITE);
  myFile.write((byte *)&myStruct, sizeof(myStruct));
  myFile.close();
}

void loop() {
  // put your main code here, to run repeatedly:
  File myFile = SPIFFS.open("/somefile.txt", FILE_WRITE);
  myFile.read((byte *)&myStruct, sizeof(myStruct));
  myFile.close();
  Serial.printf( "read: %s, %.2f\n", myStruct.someString, myStruct.someFloat );
  delay(1000);
}

the code above is available on this Github Gist.

6
  • 2
    Nitpick: if the card fails to mount, setup() should abort() rather than return. Mar 20, 2023 at 10:57
  • I agree. Is missing a few good practice code verifications for that to work flawlessly. Still is a sound code snippet to use. Mar 20, 2023 at 11:42
  • @MiguelTomás Guessing it'll work the same using LittleFS ?
    – guyd
    Mar 21, 2023 at 18:40
  • to be honest, haven't tried it recently. however, I see no reason why it will not work... Mar 21, 2023 at 18:42
  • @MiguelTomás - I'm trying to understand why myFile.read((byte *)&myStruct, sizeof(myStruct)); is used (byte *) ? I would have never guessed that syntax
    – guyd
    Mar 21, 2023 at 18:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.