Getting started with sending radio/wireless payloads

Started by LesB, December 09, 2014, 11:01:59 PM

LesB

I'm a noob at coding, have completed one Arduino project so far.

Now, I'm building a remote temperature logging system.  I have my Modeino successfully working with a sample sketch for the MLX90614 IR temperature detector over an I2C interface.  I can get the same Modeino to communicate with a partner using the Node and Gateway sample sketches on this site.  So far so good.

Next step is to get my Modeino at the node end to perform the node and I2C functions simultaneously.  And that will involve some coding on my part.  I tried to backwards-engineer the Node sketch to figure out the process for stuffing data over the radio, but I'm baffled.  Looks like the data to be transmitted needs to be put into a "payload" variable.  But I'm not getting the whole process. 

Can you name a source for me to read up on this process?  I'm assuming that it's a little involved to explain here on the forum.  Maybe not.

Thanks,
LesB

Reck_law

I am not sure if this will help.  I think what you need to do is modify the payload structure to me the same on both the node and gateway.  Then you put the data to be sent in the payload.  The gateway will recieve the data and you can use it as needed.  Look at the mail box code.

Felix

I think you're looking for something like this, (taken from the node example sketch):

sprintf(buff, "FLASH_MEM_ID:0x%X", flash.readDeviceId());
byte buffLen=strlen(buff);
if (radio.sendWithRetry(GATEWAYID, buff, buffLen))
   Serial.print(" ok!");


First line will generate the payload - it writes a message with a variable in the middle of the message. For more such formatting and data types check the printf format definition.
Second line gets the payload length, as it was generated. This is needed to pass on to the radio so it knows how many bytes to read from the buffer.
Third line is sends the message to the GATEWAYID. The 2nd and 3rd parameters are the payload and the payload length.
The sendWithRetry function will retry up to a default of 3 times to send the message, each time waiting for a ACKnowledgement from the receiver. If the ACK is received, it will return TRUE and the "ok" message is printed.

You can generate your own payloads like this, with as many variables as you need. You can use integers, decimals, etc. Just get familiar with the printf syntax. For instance to put an unsigned integer in your message you would use a %u. Use the strlen(buff) function to get the length of your generated buffer, then pass them to the radio send/sendWithRetry() function. I don't think it's that hard, except the first time ;)

ggallant

You control the data format at both ends. The radio transmits/receives raw bytes. There is nothing stopping you from transmitting binary data. There are some practical limitations on message length, especially if you enable encryption.

TomWS

In Felix's example, you can save a line (and a bit of processor time) if you take advantage of the fact that sprintf returns the string length already:
byte buffLen=sprintf(buff, "FLASH_MEM_ID:0x%X", flash.readDeviceId());
if (radio.sendWithRetry(GATEWAYID, buff, buffLen))
   Serial.print(" ok!");


And, in ggallant's case, an example would be:
struct PAYLOAD {
   byte len;
   byte type;
   struct SOME_KIND_OF DATA_PACKAGE {
    ...
   } data;
} payload;

payload.len = sizeof(payload);
payload.type = SOME_KIND_OF_DATA_PACKAGE_TYPE;
payload.data = { ... };
if (radio.sendWithRetry(GATEWAYID,(byte *)&payload, payload.len))
   Serial.print(" ok!");


On the receiving end you would have the same struct declaration and simply cast the radio.DATA as a struct of that type (probably using pointers to the data is less cumbersome from a coding perspective). You can check the integrity of the message by comparing radio.DATALEN with payload.len or, alternatively, send multiple different structures if you are consistent in keeping .len and .type members at the beginning of every struct.

Tom


LesB

Thanks for the helpful hints everyone.
I'm having to do other stuff right now, holidays & stuff, you know.  When I get back on to this I'll report in.
LesB

LesB

Farther along on my project, having solved the problem above, and posting a problem farther down the line.

In Gateway.ino I get the transmitted data in volatile byte radio.data.
To work with the data I need to pass it to a non-volatile byte.
I understand that C++ has no built-in workaround on this.

Suggestions on how to do this?

Thanks,
LesB

TomWS

Quote from: LesB on December 30, 2014, 08:10:52 PM
Farther along on my project, having solved the problem above, and posting a problem farther down the line.

In Gateway.ino I get the transmitted data in volatile byte radio.data.
To work with the data I need to pass it to a non-volatile byte.
I understand that C++ has no built-in workaround on this.

Suggestions on how to do this?

Thanks,
LesB
I'm not sure what you're saying here.  Are you getting compiler errors/warnings because you're trying to pass this data somewhere else?

The KEY to 'volatile' keyword is that you're telling the compiler that this value could change at any time so don't assume that if the compiler moves it to a register, it hasn't changed before its referenced again.  However, IF YOU KNOW that the only time it changes is when you've called a 'radio' method, then the secret sauce is to 'consume' the data BEFORE you make the next call to a radio method.

For example:
    if (radio.receiveDone()) {
       // oh boy! We've got data!!! Let's save it to my own buffer!
       memcpy(myOwnBuffer, radio.DATA, radio.DATALEN);
       ...

seems like the right thing to do, and it is...  The only problem is that the compiler will tell you that it CAN'T do this!  Why? Because memcpy needs CONSTANT data to copy it and radio.DATA is 'volatile'!  But, guess what?  YOU KNOW that the data isn't gonna change at this point so you have to tell the compiler this.  Here's how:
    if (radio.receiveDone()) {
       // oh boy! We've got data!!! Let's save it to my own buffer!
       memcpy(myOwnBuffer,(const void *) radio.DATA, radio.DATALEN);
       ...

This tells the compiler that the data REALLY is constant and the compiler will do what you want...

Further, IF you will need to have anything else from this particular reception, then literally grab it while you can 'cause it ain't gonna last once you call another radio. method.  So, for example:
    if (radio.receiveDone()) {
       // oh boy! We've got data!!! Let's save it to my own buffer!
       memcpy(myOwnBuffer, (const void *)radio.DATA, radio.DATALEN);
       datalen = radio.DATALEN;
       sender = radio.SENDERID;
       target = radio.TARGETID

      if (radio.AckRequested()) {
         radio.sendAck();
      ...

where datalen, sender, target, are all variables that you've defined in your sketch and you're saving them even before you send the Ack.  Why?  Well, if the sender sends something immediately after Ack, the SENDERID, etc, MAY have changed before you even get to parse the original message...

Have fun.  This isn't too hard if you keep asking questions...

Tom






LesB

OK, so in TomWS's line above:
memcpy(myOwnBuffer,(const void *) radio.DATA, radio.DATALEN);
what kind of variable is myOwnBuffer?  I'm having a time of it trying to use it.

What I'm looking to do with this data is:

1. Put it to Serial.print, which I can do already with:

for (byte i = 0; i < radio.DATALEN; i++)
Serial.print((char)radio.DATA);

2. Stuff the streaming data sequentially into the onboard 4G flash

3. Send the most recently received data to an LCD via an I2C interface

So what I'm ultimately trying to accomplish now is get data from radio.DATA to do 2 and 3 above.

Thanks. 
This is fun, but it would be more so if I had more coding experience.
LesB

TomWS

Quote from: LesB on January 19, 2015, 07:18:19 PM
OK, so in TomWS's line above:
memcpy(myOwnBuffer,(const void *) radio.DATA, radio.DATALEN);
what kind of variable is myOwnBuffer?  I'm having a time of it trying to use it.

What I'm looking to do with this data is:

1. Put it to Serial.print, which I can do already with:

for (byte i = 0; i < radio.DATALEN; i++)
Serial.print((char)radio.DATA[i]);


2. Stuff the streaming data sequentially into the onboard 4G flash

3. Send the most recently received data to an LCD via an I2C interface

So what I'm ultimately trying to accomplish now is get data from radio.DATA to do 2 and 3 above.

Thanks. 
This is fun, but it would be more so if I had more coding experience.
LesB
Les,
I've modified your post slightly by putting the coding example inside a 'code' bracket (see '#' symbol on the second command line above post).  This will keep your code intact (so subscripted variables show up).

So, having done that, I'm not sure where your struggles are. You know how print each individual byte, what do you need to know to accomplish 2 & 3?  My suggestion is that you save the incoming data into your own buffer BEFORE you sendACK() and then you can use your own buffer to save to
"2. Stuff the streaming data sequentially into the onboard 4G flash

3. Send the most recently received data to an LCD via an I2C interface"

Re your question what type is 'myOwnBuffer', I'd say the easiest to work with is a byte array, so the declaration would be:
byte myOwnBuffer[MAX_NUM_BYTES_I_COULD_GET];

Note, with Moteino RFM69 radio, you'll never get more than 64 bytes in a transmission, so that's a good place to start.  What else do you need to know?

Tom
PS: I'm glad you're having fun.  That's VERY important!  Seriously.

LesB

Maybe instead of composing a question I really need to post what I'm doing along with the admonishment the compiler spits out at me and let you formulate the question and answer.
In this example I'm trying to utilize suggested code for converting volatile data:
byte myOwnBuffer;///
memcpy(myOwnBuffer,(const void *) radio.DATA, radio.DATALEN);/// 

error :
Gateway_4:140: error: invalid conversion from 'byte' to 'void*'
Gateway_4:140: error: initializing argument 1 of 'void* memcpy(void*, const void*, size_t)'


In this next example I'm trying to utilize the suggested code for the byte array:
byte myOwnBuffer[radio.DATALEN]; ///
for (byte i = 0; i < radio.DATALEN; i++)///
myOwnBuffer[0]=(const void*)radio.DATA;

error:
Gateway_4.ino: In function 'void loop()':
Gateway_4:140: error: invalid conversion from 'const void*' to 'byte'

Felix

Maybe you wanted something like:

byte myOwnBuffer[radio.DATALEN];
for (byte i = 0; i < radio.DATALEN; i++)
myOwnBuffer[i]=radio.DATA[i];

TomWS

Quote from: LesB on January 21, 2015, 11:31:51 PM
Maybe instead of composing a question I really need to post what I'm doing along with the admonishment the compiler spits out at me and let you formulate the question and answer.
In this example I'm trying to utilize suggested code for converting volatile data:
byte myOwnBuffer;///
memcpy(myOwnBuffer,(const void *) radio.DATA, radio.DATALEN);/// 

error :
Gateway_4:140: error: invalid conversion from 'byte' to 'void*'
Gateway_4:140: error: initializing argument 1 of 'void* memcpy(void*, const void*, size_t)'

In this case you are only allocating a single byte scalar storage location (myOwnBuffer is only 1 byte in size) and trying to reference it as an array (or pointer).  If the compiler didn't flag this as an error, you'd have a real MESS if you actually executed the code (all the storage after myOwnBuffer would get overwritten).
Quote from: LesB on January 21, 2015, 11:31:51 PM

In this next example I'm trying to utilize the suggested code for the byte array:
byte myOwnBuffer[radio.DATALEN]; ///
for (byte i = 0; i < radio.DATALEN; i++)///
myOwnBuffer[0]=(const void*)radio.DATA;

error:
Gateway_4.ino: In function 'void loop()':
Gateway_4:140: error: invalid conversion from 'const void*' to 'byte'

In this case, Felix's suggestion is correct.  You need to index into both arrays with 'i', copying one byte from one array, into one byte of the other.  In this case, memcpy would have worked too:
byte myOwnBuffer[radio.DATALEN];
memcpy(myOwnBuffer,(const void *)radio.DATA, radio.DATALEN);

Note that 'myOwnBuffer' can ONLY be a local array, dynamically allocated at the point that the value of radio.DATALEN is known.  Otherwise the compiler won't know how much storage to allocate for it.  I'm also not sure if you could get away with this if it was pure 'C' (and not C++ code).

Finally, don't be discouraged that you're struggling with this.  The interchangeability of pointers and arrays and the distinction between an array and scalar value in 'C' confuses practically everyone new to 'C'.

Tom

LesB

Success!

Got both TomTS's method and Felix's method to work.  Just having to understand some basics...

BTW, I am working my way through "Beginning Programming With C++ for Dummies".  At some point I might catch up to where I need to be in this project.

In the mean time, I'm heading over to another part of the forum to get some basics on reading/writing to the onboard flash.

What I'm up to here is building a system to monitor wheel rim temperatures on the front and rear wheels of a bicycle once per second with an i2C  IR temperature sensor that connects to Moteino's right there at the wheels.  Then sending that data via RF from the wheels to a receiving Moteino on the handlebar with an LCD to display the temperature.  At the same time it will stuff the 1/sec. readings to the onboard FLASH.

Once completed I will open-source the project as a wireless temperature monitoring & logging system for general usage on Instructables.com

Thanks for the help!

TomWS

Quote from: LesB on January 23, 2015, 08:52:32 PM
Success!

Got both TomTS's method and Felix's method to work.  Just having to understand some basics...

BTW, I am working my way through "Beginning Programming With C++ for Dummies".  At some point I might catch up to where I need to be in this project.

In the mean time, I'm heading over to another part of the forum to get some basics on reading/writing to the onboard flash.

What I'm up to here is building a system to monitor wheel rim temperatures on the front and rear wheels of a bicycle once per second with an i2C  IR temperature sensor that connects to Moteino's right there at the wheels.  Then sending that data via RF from the wheels to a receiving Moteino on the handlebar with an LCD to display the temperature.  At the same time it will stuff the 1/sec. readings to the onboard FLASH.

Once completed I will open-source the project as a wireless temperature monitoring & logging system for general usage on Instructables.com

Thanks for the help!
OOOOOOH! That sounds like a fun project!  Is temperature all you need to monitor to (presumably) monitor tire pressure?  That's really interesting!  I'd have thought that you'd have to actually measure pressure, and, given that, what 'use' is that info?  ie, what would you adjust knowing what you'd learn from this?

Tom
PS: I'm glad you're making progress.