Multiple payloads?

Started by MrGlasspoole, November 29, 2016, 06:25:49 AM

MrGlasspoole

I have a BH1750FVI and DS18B20 connected and send the values every 2 minutes.
Now i also need to send the state from a digital input but need to run it in another loop (continuous).

How do i do that? I'm trying it with two payload structures but only get invalid on the second one.
Can i have two "radio.receiveDone"? I need to send the state from the digital input when it changes.

Here is the part from the sender:
/**************************************************************
* DATA STRUCTURE TO BE SENDE                                  *
**************************************************************/
typedef struct {
  int nodeId; // Store this nodeId
  float temp; // Temperature reading
  float lx;   // Light reading
  int vcc;    // Battery voltage
} Payload;
Payload theData;

typedef struct {
  int nodeId; // Store this nodeId 
  int ls;
} LightState;
LightState theDataLight;

/**************************************************************
* SEND PAYLOAD DATA                                           *
**************************************************************/
static void rfwrite() {
  radio.receiveDone();
  if (radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData), RETRY_LIMIT, ACK_TIME)) {
    if (DEBUG) {
      Serial.print("ACK received! ");
      Serial.print("Sending ");
      Serial.print(sizeof(theData));
      Serial.print(" bytes...");
      Serial.print(", TX level: ");
      Serial.print(radio._transmitLevel,DEC); // current transmit level used by this sender
      Serial.print(", RSSI: ");
      Serial.println(radio.getAckRSSI(),DEC); // this sender's RSSI acked back from the receiving node (Gateway)
      Serial.flush();
    }
    radio.sleep(); // Put radio to sleep
    } else {
      if (DEBUG) {
        Serial.println("No ACK response!");
        Serial.flush();
      }
      //Sleepy::loseSomeTime(RETRY_PERIOD * 1000); // If no ack received wait and try again
    }
}

void loop() {

  digitalWrite(ONE_WIRE_POWER, HIGH); // Turn DS18B20 on
  LightSensor.Begin(Addr_LOW, OneTime_H); // Start light sensor by turning on and initializing it
  
  //Sleepy::loseSomeTime(5); // Allow 5ms for the sensor to be ready
  delay(5); // The above doesn't seem to work for everyone (why?)
 
  sensors.begin(); // Start Dallas Temperature library
  sensors.requestTemperatures(); // Get the temperature
  theData.temp = (sensors.getTempCByIndex(0)); // Read first sensor

  digitalWrite(ONE_WIRE_POWER, LOW); // Turn DS18B20 off

  int light_intensity = digitalRead(LIGHT_STATE); // read the input pin
  Serial.println(light_intensity); // print out the intensity of light, 1 for Dark and 0 for Light:

  theData.nodeId = NODEID;
  theData.vcc = readVcc(); // Get battery voltage
  theData.lx = (LightSensor.GetLux());
  
  theDataLight.ls = test;

  rfwrite(); // Send data via RF

  Sleepy::loseSomeTime(5000);
}

Yes i know i need to remove the "Sleepy" if i don't want to use interrupts and check the digital pin continuously.

And receiver:
/**************************************************************
* DATA STRUCTURE                                              *
**************************************************************/
typedef struct {
  int nodeId; // Store this nodeId
  float temp; // Temperature reading
  float lx;   // Light reading
  int vcc;    // Battery voltage
} Payload;
Payload theData;

typedef struct {
  int nodeId; // Store this nodeId 
  int ls;
} LightState;
LightState theDataLight;

void loop() {
  if (radio.receiveDone()) {
    if (radio.DATALEN != sizeof(Payload)) {
      Serial.print("Invalid Payload received!");
    } else {
      theData = *(Payload*)radio.DATA; //assume radio.DATA actually contains our struct and not something else
      Serial.print("node");
      Serial.print(theData.nodeId);
      Serial.print("_dev01:");
      Serial.print(theData.temp);
      Serial.print(",node");
      Serial.print(theData.nodeId);
      Serial.print("_dev02:");
      Serial.print(theData.lx);
      Serial.print(",node");
      Serial.print(theData.nodeId);
      Serial.print("_v:");
      Serial.print(theData.vcc);
      Serial.print(",rssi:");
      Serial.print(radio.RSSI);
    }

    if (radio.DATALEN != sizeof(LightState)) {
      Serial.print(" Invalid LightState received!");
    } else {
      theDataLight = *(LightState*)radio.DATA; //assume radio.DATA actually contains our struct and not something else
      Serial.print("no:");
      Serial.print(theDataLight.nodeId);
      Serial.print(",ls:");
      Serial.print(theDataLight.ls);
      Serial.println(radio.RSSI);
    }

    if (radio.ACKRequested()) { // When a node requests an ACK, respond to the ACK
      //byte theNodeID = radio.SENDERID;
      radio.sendACK();
      Serial.print(" - ACK sent.");
    }
    
    Serial.println();
  }
}

TomWS

Ignoring your code for the moment, if your data structures are 'self-defining', ie, they contain an 'type' field in a consistent byte position in both (all) structures that you send, then parsing the type of the structure on the receiving end is trivial.

In my case ALL transmitted data structures begin with a header that contains two bytes.  The first is len(gth) of the whole structure and the second is 'type', which is a well-defined constant throughout my code so all receiving nodes can easily parse the packet (and allow me to nest structures almost arbitrarily).

Tom

MrGlasspoole

Do you have a example - some code?
It will always be:
NodeID,val1,val2,val3

Then on my gateway i need to build:
1/temp 22.3
1/volt 3.2
1/lux 526


But i don't get how to send different values at different times/intervals.
I have this for the temp and lux reading:
unsigned long prevMillisSend=0; // millis() returns an unsigned long.
void sendTheData() { 
  unsigned long sendInterval=8000;  // the time we need to wait
  if ((unsigned long)(millis() - prevMillisSend) >= sendInterval) {
    prevMillisSend = millis();

    digitalWrite(ONE_WIRE_POWER, HIGH); // Turn DS18B20 on
    LightSensor.Begin(Addr_LOW, OneTime_H); // Start light sensor by turning on and initializing it

    // delay(5);
 
    sensors.begin(); // Start Dallas Temperature library
    sensors.requestTemperatures(); // Get the temperature
    theData.temp = (sensors.getTempCByIndex(0)); // Read first sensor

    digitalWrite(ONE_WIRE_POWER, LOW); // Turn DS18B20 off

    theData.nodeId = NODEID;
    theData.lx = (LightSensor.GetLux());

    rfwrite(); // Send data via RF
  }
} // END sendTheData()


And there is a LDR that needs to send its state when it changes:
void loop() {

  sendTheData();

  lightState = digitalRead(LIGHT_STATE);
  if (lightState != lastLightState) {
    if (lightState == HIGH) {
      Serial.println(lightState); // print out light state, 1 for Dark and 0 for Light
    } else {
      Serial.println(lightState); // print out light state, 1 for Dark and 0 for Light
    }
  }
  lastLightState = lightState; // save current state as the last state, for next time through the loop

}

TomWS

Quote from: MrGlasspoole on December 01, 2016, 07:01:07 AM
Do you have a example - some code?

Using your structures from earlier, it could look like:

// HEADER Structure
typedef struct {
  uint8_t
    len,
    type;
} commonHeader;

enum PACKET_TYPES {
  PAYLOAD=0,
  LIGHTSTATE,
  OTHERS
}

/**************************************************************
* DATA STRUCTURE                                              *
**************************************************************/
typedef struct {
  commonHeader h;           // added to identify packet type
  int nodeId; // Store this nodeId
  float temp; // Temperature reading
  float lx;   // Light reading
  int vcc;    // Battery voltage
} Payload;
Payload theData = {{sizeof(Payload),PAYLOAD},0,0,0,0};

typedef struct {
  commonHeader h;           // added to identify packet type
  int nodeId; // Store this nodeId 
  int ls;
} LightState;
LightState theDataLight = {{sizeof(LightState),LIGHTSTATE},0};

// I believe transmit code is the same as before
...


On the receive side:
void loop() {
  if (radio.receiveDone()) 
  {
    if (radio.DATALEN != ((commonHeader*)radio.DATA)->len) 
    {
      Serial.print("Invalid Payload received!");
    } else 
    {
       switch (((commonHeader*)radio.DATA)->type) 
      {
         case PAYLOAD:
            {
               Payload* pkt = (Payload*)radio.DATA;
               node_temp = pkt->temp;
               ...
            }
            break;
          case LIGHTSTATE:
            {
               LightState* pkt = (LightState*)radio.DATA;
               node_ls = pkt->ls;
               ...
            }
            break;
            case OTHERS:
              ...
           }
        }
    }


You can send either packet arbitrarily and the receiving node will know what you sent.
Tom

Sean

Tom,
Thanks for sharing the example code.  I've just run into a need to send different structures. I'm sure your post has saved me a lot of time.
Cheers, Sean

TomWS

Quote from: Sean on August 19, 2018, 02:25:40 PM
Tom,
Thanks for sharing the example code.  I've just run into a need to send different structures. I'm sure your post has saved me a lot of time.
Cheers, Sean
I'm glad it helps. Good to know! 
Thanks.

syrinxtech

FWIW, I'm always a sucker for a union in these cases.  I just use a byte to differentiate the packet types (usually with some ENUMs) to make it easy to read.  That way I'm always referencing the same data everywhere in the program, just picking off different fields as needed.  Yes, there is a slight efficiency hit if the packet types are radically different in length, but usually I'm working with data that isn't too different and the readability of the code outweighs the bytes wasted.

TomWS

Quote from: syrinxtech on August 20, 2018, 10:16:54 AM
FWIW, I'm always a sucker for a union in these cases.  I just use a byte to differentiate the packet types (usually with some ENUMs) to make it easy to read.  That way I'm always referencing the same data everywhere in the program, just picking off different fields as needed.  Yes, there is a slight efficiency hit if the packet types are radically different in length, but usually I'm working with data that isn't too different and the readability of the code outweighs the bytes wasted.
Indeed, good point. 

Unions are useful in saving storage and are especially useful on the receiving side (so you don't have to keep casting to the different structs).  On the sending side, it depends on whether you use the struct for local storage of each type of sample you have (and simply sending it without storing into a separate packet) or if you're just encapsulating data for transmission.

An useful aspect of this self-defining structure is that you can append several structs into a single packet since the length of each struct is included in the element making it easy to separate them at the receiver.

Tom

Tom