How to define multiple versions of a structure?

I'm wondering if this is possible with C++/Arduino and/or how the best way to address this.

My sketch needs to process information coming via RS485 data packets. The first byte of the packet identifies the packet type and thus the data structure that follows. All packets are the same length. To simplify, assume there are 3 packet structures:

struct CmdStruct {
  byte packettype;    // 1 for command packet
  byte msgno;
  byte stat;
  byte command;
  unsigned long datetime;
} PacketA ;

struct DataStruct {
  byte packettype;   // 2 for data packet
  byte msgno;
  int alpha;
  int beta;
  int delta;
} PacketB ;

struct ReadStruct {
  byte msgread[8];  // This is actually used when reading from the RS485 data
} PacketR;

Is it possible to overlay the 3 structures so that they are referencing the same 8 bytes of memory? And if so, what's this called?

Note that in practice, the data structures are much longer.

If C++ doesn't support this, is there a way to copy PacketR to PacketA or PacketB?

Thank you.

Yes, it's called a union.

1 Like

Yes, that is very normal. I forget how it is done in C++ but we used pointer based structures. The key is to have a standard start. The first byte must be length in order to get to the next msg, the second byte must be message type so you know what structure you are working with.
My system did this a few million times a day for years before we were bought out and it never failed to understand and process the data.

1 Like
struct __attribute__((packed)) CmdStruct {
  uint8_t msgno;
  uint8_t stat;
  uint8_t command;
  uint32_t datetime;
};

struct __attribute__((packed)) DataStruct  {
  uint8_t msgno;
  int16_t alpha;
  int16_t beta;
  int16_t delta;
};

struct __attribute__((packed)) PacketStruct {
  uint8_t packettype;
  union {
    CmdStruct cmd;
    DataStruct data;
  };
};

void setup() {
  uint8_t rawRead[sizeof(PacketStruct)];
  // Read data from RS485 into rawRead here

  PacketStruct packetData;
  memcpy(&packetData, rawRead, sizeof(rawRead));

  if (packetData.packettype == 1) {
    // Do Command stuff with "cmd"
  } else {
    // Do Data stuff with "data"
  }
}

void loop() {
}
1 Like

Thank you to all - exactly what I'm looking for.

To be aware that these structures has the same size on 8bit Arduino only. If you try to send it to, say, ESP32 - these structures becames the different

Using <stdint.h> types and __attribute__((packed)) fixes that.

I was talking about the OP's structures.

yes, it is a possible solution.

once you know it's type, just copy it to an instance of the struct

(apparently casting a pointer to the buffer to a pointer of the struct causes grief i.e. undefined behavior)

1 Like

I attempted to code this and I'm getting a compiler error. Here is code that deals with the structure and initialization:

union myunion {
  byte msgread[28];  // This is actually used when reading from the RS485 data
  struct CmdStruct {
    byte client;
    byte msgno;
    byte stat;
    byte command;
    int PIN[4];
    byte Zone[16];
  } CMD;
};


void setup() {
}  // end of setup
  

void loop() {
  CMD.client  = 1;
  CMD.msgno   = CMD.msgno+1;
  CMD.stat    = 5;
  CMD.command = 255;
  CMD.PIN[0]  = 1234;
  CMD.PIN[1]  = 4321;
  CMD.PIN[3]  = 9876;
  CMD.PIN[4]  = 0;
  CMD.Zone[0] = 0;

}  // end of loop

When compiled I get 'CMD' was not declared in this scope.

I must have some syntax wrong, but can't see it.

Using Arduino IDE 1.8.9.

I dont think a union is the best way to go. I think it would be a lot "cleaner" to define the basic contents of one of the structures as a base class, then define a numer of derived classes for each specific type of structure. That way you can use a pointer or reference to the base class to reference any derived class for bookkeeping, with a pointer or reference to the specific class for using/updating the unique structures.

this kinda thing is common in communication. Ethernet frames are captured, ID'd by looking at some header bytes and the content copied to a struct that defines the sub-fields with the packet. Similar for IP frames carried within an Ethernet frame and themselves carrying TCP.

1 Like

Even though I first suggested a union, I think this method will be simpler for you:

That's fine, but for now (even as a learning point) what am I doing wrong that the union/structure code generates a compile error.

look this over, demonstrating one approach
(but copying is the more acceptable practice)

struct CmdStruct {
    byte packettype;    // 1 for command packet
    byte msgno;
    byte stat;
    byte command;
    unsigned long datetime;
} PacketA ;


struct DataStruct {
    byte packettype;   // 2 for data packet
    byte msgno;
    int alpha;
    int beta;
    int delta;
} PacketB ;


struct ReadStruct {
    byte msgread[8];  // This is actually used when reading from the RS485 data
} PacketR;

enum { PT0, PT1, PT2 };

char s [90];

// -----------------------------------------------------------------------------
int msgno;

void
gen (
    int   type,
    byte *buf,
    int   nByte )
{

    switch (type)  {
    case PT1:
        {
        CmdStruct *p = (CmdStruct*) buf;
        p->packettype    = type;
        p->msgno         = ++msgno;
        p->stat          = 100;
        p->command       = 99;
        p->datetime      = millis ();
        }
        break;

    case PT2:
        {
        DataStruct *p    = (DataStruct*) buf;
        p->packettype    = type;
        p->msgno         = ++msgno;
        p->alpha         = 1000;
        p->beta          = 2000;
        p->delta         = 3000;
        }
        break;

    default:
        sprintf (s, "gen: unknown type %d", type);
        Serial.println (s);
        break;
    }
}

// -----------------------------------------------------------------------------
void
decode (
    byte *buf,
    int   nByte )
{

    switch (*buf)  {
    case PT1:
        {
        CmdStruct *p = (CmdStruct*) buf;
        sprintf (s, " msgno %d, stat %d command %d, datetime %lu",
            p->msgno, p->stat, p->command, p->datetime );
        Serial.println (s);
        }
        break;

    case PT2:
        {
        DataStruct *p = (DataStruct*) buf;
        sprintf (s, " msgno %d, alpha %d beta %d, delta %d",
            p->msgno, p->alpha, p->beta, p->delta );
        Serial.println (s);
        }
        break;

    }
}

// -----------------------------------------------------------------------------
void
loop (void)
{
    byte buf [90];
    if (Serial.available ()) {
        switch (Serial.read ())  {
        case 'a':
            gen (PT1, buf, sizeof(buf));
            decode (buf, sizeof(buf));
            break;

        case 'b':
            gen (PT2, buf, sizeof(buf));
            decode (buf, sizeof(buf));
            break;

        case 'z':
            gen (PT0, buf, sizeof(buf));
            break;
        }
    }
}

void
setup (void)
{
    Serial.begin (9600);
}

That will invoke the undefined behavior you mentioned:

Your other idea was much better:

As a learning point, you didn't define an object of type MyUnion:

union MyUnion {
  byte msgread[28];  // This is actually used when reading from the RS485 data
  struct CmdStruct {
    byte client;
    byte msgno;
    byte stat;
    byte command;
    int PIN[4];
    byte Zone[16];
  } CMD;
};

MyUnion theUnion;

void setup() {
  theUnion.CMD.client  = 1;
  theUnion.CMD.msgno++;
  theUnion.CMD.stat    = 5;
  theUnion.CMD.command = 255;
  theUnion.CMD.PIN[0]  = 1234;
  theUnion.CMD.PIN[1]  = 4321;
  theUnion.CMD.PIN[2]  = 9876;
  theUnion.CMD.PIN[3]  = 0;
  theUnion.CMD.Zone[0] = 0;
}


void loop() {
}

I don't think that declaring the struct inside the union is correct. This compiles

struct CmdStruct {
  byte client;
  byte msgno;
  byte stat;
  byte command;
  int PIN[4];
  byte Zone[16];
} CMD; 

union myunion {
  byte msgread[28];  // This is actually used when reading from the RS485 data
  /*struct CmdStruct {
    byte client;
    byte msgno;
    byte stat;
    byte command;
    int PIN[4];
    byte Zone[16];
  } //CMD;
  */
  struct CMD;
};


void setup() {}  

void loop() {
  CMD.client  = 1;
  CMD.msgno   = CMD.msgno + 1;
  CMD.stat    = 5;
  CMD.command = 255;
  CMD.PIN[0]  = 1234;
  CMD.PIN[1]  = 4321;
  CMD.PIN[3]  = 9876;
  CMD.PIN[4]  = 0;
  CMD.Zone[0] = 0;
}  // end of loop

@gfvalvo knows way more about structs and unions than I do, so his arrangement may be the way to go.

Never having tried this, I'm surprised to hear that.

However, a struct in C++ is the same thing as a class without a constructor, so an alternate way would be a base class and subclasses. I would use have a base class pointer to the data, then dereference the pointer by casting to the desired subclass.

It's a clean method and won't have any surprises. I'm not a big fan of unions.

It's risky at best. Casting from a base class to a subclass (downcasting) requires dynamic_cast. This is not supported on many small processors as it consumes code space and incurs a runtime penalty.