Author Topic: Need help receiving two ints and separating them into variables once received  (Read 2464 times)

doublec4

  • Jr. Member
  • **
  • Posts: 53
Hi guys, so I've read through the sticky thread about "getting started sending/receiving payloads" but I'm a little stumped on how to go about breaking up the incoming data and assigning to different variables.

Basically I have a two button remote. Up button and down button. When I press the up button, it sends a '1' command to the receiver to turn on a hydraulic pump. When I press the down button it sends a '0' command to open the drain valve.

In response, the receiver sends a position status back to the remote. '0' for the cylinder in the down position, '1' for the cylinder in the up position, and a '2' for some error state. This results in the LED on the remote flashing to indicate the status.

I had these basic functions working fine because it was easy to send 1 character back and forth and read one character.

However, the remote runs on batteries and I've since tried some difference approaches to consume less power. I've successfully put the remote to sleep between different button presses, but I wanted to try to go one step farther...

One strategy is to have the receiver send a 'sleeptime' number back to the remote in addition to the position status. This sleep number is an integer that represents the number of seconds the pump or drain is going to be on. This allows the Moteino to go to sleep for those seconds rather than remain on and consume battery. When the sleep time is up, Moteino can wake back up and poll the radio to see if there was an error message sent back and then go back to sleep again.

Since this requires sending back several numbers and then somehow separating them into two different variables (posstatus and sleeptime) at the remote, I am having some trouble working with the data coming back from the radio. I'm not sure the best way to approach this. The first single digit number is always the posstatus, and then after that the sleeptime can be set from 1 second to 30 seconds. So the data coming back can be 2 digits or 3 digits.

Is it best to just read these numbers into a string and then try and work with that? Or is there some way to get them directly into int or byte variables?

I'm sure this is easy for you guys but I'm still trying to wrap my head around the best way to convert numbers etc into the strings that are sent over the radio and then back into something usable on the other end.

Here is my remote code... you can see where I've commented that I'm not sure the best way to read in the data after calling radio.ReceiveDone()

Code: [Select]
#define EI_NOTEXTERNAL
#include <EnableInterrupt.h>
#include <LowPower.h>
#include <OneButton.h>
#include <RFM69.h>
#include <SPI.h>

// Setup a new OneButton on pin D5 (UP BUTTON) --> true parameter enables the pullup resistor on this pin
OneButton button1(5, true);

// Setup a new OneButton on pin D3 (DOWN BUTTON) --> true parameter enables the pullup resistor on this pin
OneButton button2(3, true);

#define UPPIN 5
#define DOWNPIN 3

// Addresses for this node. CHANGE THESE FOR EACH NODE!
#define NETWORKID     0   // Must be the same for all nodes
#define MYNODEID      2   // My node ID
#define TONODEID      1   // Destination node ID

// RFM69 frequency
#define FREQUENCY     RF69_915MHZ

// AES encryption (or not):
#define ENCRYPT       true // Set to "true" to use encryption
#define ENCRYPTKEY    "TOPSECRETPASSWRD" // Use the same 16-byte key on all nodes

// Use ACKnowledge when sending messages (or not):
#define USEACK        true // Request ACKs or not

// Packet sent/received indicator LED (optional):
#define LED           4 // LED positive pin
//#define GND           8 // LED ground pin

#define posDOWN      0
#define posUP        1
#define posERROR     2

// Create a library object for our RFM69HCW module:
RFM69 radio;

int sendlength = 1;
char message[] = "1";
int posstatus = 0;  //1 = pos up, 0 = pos down, 2 = pos error
int sleeptime = 0;
volatile bool newbutton = false;

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


void setup() {

//PULL ALL UNUSED PINS TO PULLUP OR OUTPUT ... FLOATING PINS CAN WASTE BATTERY

  pinMode(LED,OUTPUT);
  digitalWrite(LED,LOW); //make sure LED is off
 
  // link the button 1 functions.
  button1.attachClick(click1);    //enable single click detection
  button1.attachNull(nullclick1); //enable debounce error function

  // link the button 2 functions.
  button2.attachClick(click2);    //enable single click detection
  button2.attachNull(nullclick2); //enable debounce error function

  //Initialize the RFM69HCW:
  radio.initialize(FREQUENCY, MYNODEID, NETWORKID);
 
  // Turn on encryption if set to true above
  if (ENCRYPT)
    radio.encrypt(ENCRYPTKEY);

}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void upinterrupt(){
  newbutton = true;
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void downinterrupt(){
  newbutton = true;
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


// main code here, to run repeatedly:
void loop() {
 
  strconvert = "";  //clear String
  sleeptime = 0;
  digitalWrite(LED,LOW); //make sure the LED is off before going to sleep indefinitely.
 
//These interrupts will wake the module when it is powered down to save battery
  enableInterrupt(UPPIN, upinterrupt, FALLING);      //enables interrupt on UPPIN and calls upinterrupt
  attachInterrupt(digitalPinToInterrupt(DOWNPIN), downinterrupt, FALLING);   //enables interrupt on DOWNPIN and calls downinterrupt

  radio.sleep();                                          //Put the RFM69 to sleep
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);    //Put Moteino to power down sleep. Will wake on interrupt and run the next line of code after ISR

//First line to run after the ISR that wakes the module:

  disableInterrupt(UPPIN);
  detachInterrupt(DOWNPIN);                               //Prevents interrupts being called again until everything is complete
 
  while(newbutton){                                          //ISR that wakes up module will set this to true
  button1.tick(); //check button1 (UP) for click, double click, hold
  button2.tick(); //check button1 (UP) for click, double click, hold      //checking to see what button was pressed and if click/doubleclick/hold
  }

  if (radio.receiveDone())
          {

            /*--------------------------------
             * This is where I need to read the incoming data and separate it. The first number is the 'posstatus'. The next single or two digit number is the 'sleeptime'
             */

           
            if (radio.ACKRequested())
            {
              radio.sendACK();    //immediately respond to acknowledge message was received (there is a time limit to respond)
            }
           
                       
            switch (posstatus)
            {
              case posUP:
              {
                digitalWrite(LED,HIGH);   //LED stays on to indicate position is up
                for (byte y = 1; y < sleeptime; y++){               //save some battery by sleeping the Moteino while the pump is running but the LED stays on for this duration
                  LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
                }
                break;
              }

              case posDOWN:
              {
               
               
                  for (int x = 0; x > (2*sleeptime)+1; x++){   //sleep two times longer than the pump downtime, plus 2 seconds thereby giving time to detect and send the error back
                    LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
                  }               
                 
                 if (radio.receiveDone()) //check for a new message
                        {

                          posstatus = (char)radio.DATA[0] - '0'; //convert the recieved data at entry [0] to an int. This is the position status.
                         
                          if (radio.ACKRequested())
                          {
                            radio.sendACK();    //immediately respond to acknowledge message was received (there is a time limit to respond)
                          }
                         
                                             
                          if (posstatus == 2){                   //if error was detected, flash the error code
                            for (int i = 0; i < 7; i++){    //LED 7 long flashes for error state
                            digitalWrite(LED,HIGH);
                            LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);
                            digitalWrite(LED,LOW);
                            LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);
                            }
                          }
               
                        }
                break;
              }
             
            }
         
          }
 
} // loop

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


// -------> MOVE UP

// This function will be called when the button1 was pressed 1 time (and no 2. button press followed).
void click1() {
 
 
  message[0] = '1';
  if (radio.sendWithRetry(TONODEID, message, sendlength)){
          digitalWrite(LED,LOW);
          LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,HIGH);   //ack received, quick confirm flash
          LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,LOW);
          LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,HIGH);
          LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,LOW);
          }
        else{                         //no ack received, assume receiver did not get message. Slow flash.
          digitalWrite(LED,HIGH);
          LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
          digitalWrite(LED,LOW);
          }
   newbutton = false;
                   
   
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


// -------> MOVE DOWN

void click2() {


 
  message[0] = '2';
  if (radio.sendWithRetry(TONODEID, message, sendlength)){
          digitalWrite(LED,LOW);
          LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,HIGH);   //ack received, quick confirm flash
          LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,LOW);
          LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,HIGH);
          LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);
          digitalWrite(LED,LOW);
          }
        else{                         //no ack received, assume receiver did not get message. Slow flash.
          digitalWrite(LED,HIGH);
          LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
          digitalWrite(LED,LOW);
          }
          newbutton = false;
}
 // click2

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


//Debounce Error on first button

void nullclick1() {

   newbutton = false; //in the event of a debounce error we still need to set this variable to false, otherwise while loop will keep moteino awake until the next button press essentially wasting battery
           
   
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

//Debounce Error on second button

void nullclick2() {

   newbutton = false; //in the event of a debounce error we still need to set this variable to false, otherwise while loop will keep moteino awake until the next button press essentially wasting battery
           
   
}


And this is my receiver code. I'm using the sprintf approach I saw in the sticky thread to put together something to send:

Code: [Select]
#include <RFM69.h>
#include <SPI.h>

// Addresses for this node. CHANGE THESE FOR EACH NODE!
#define NETWORKID     0   // Must be the same for all nodes
#define MYNODEID      1   // My node ID
#define TONODEID      2   // Destination node ID

// RFM69 frequency
#define FREQUENCY     RF69_915MHZ

// AES encryption (or not):
#define ENCRYPT       true // Set to "true" to use encryption
#define ENCRYPTKEY    "TOPSECRETPASSWRD" // Use the same 16-byte key on all nodes

// Use ACKnowledge when sending messages (or not):
#define USEACK        true // Request ACKs or not

// Packet sent/received indicator LED (optional):
#define LED           15 // LED positive pin


#define rmtUP     1
#define rmtDOWN   2

//Pins
int coilupmosfet = 30;
int coildownmosfet = 29;
int limitswitch = 28;

//Program Variables

bool posstatus = 0; //keeps track of state of coilovers ex: up/down where 0 = down
int uptime = 5;
int downtime = 12;
char message[4];
int remote = 0;

// Create a library object for the RFM69HCW module:
RFM69 radio;

/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void setup()
{

  pinMode(limitswitch, INPUT); //Input pin for limit switch
  pinMode(coilupmosfet, OUTPUT);
  pinMode(coildownmosfet, OUTPUT);
 
  digitalWrite(coilupmosfet, LOW);
  digitalWrite(coildownmosfet, LOW);

  //Initialize the radio
      radio.initialize(FREQUENCY, MYNODEID, NETWORKID);

  // Turn on encryption if set to true above
    if (ENCRYPT)
    radio.encrypt(ENCRYPTKEY); 

}

/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void loop()                                       //main program loop
{
 
  if (radio.receiveDone()) // Received a command from the remote!
    {
      remote = (char)radio.DATA[0] - '0'; //convert the recieved data back to the int data type. Consume this data before the next radio.whatever its called or it can be lost
     
      if (radio.ACKRequested())
            {
              radio.sendACK();    //immediately respond to acknowledge message was received (there is a time limit to respond)
            }
     
     
      switch (remote)
      {
        case rmtUP:
        {
          coilup();       //extend the cylinder
          remote = 0;
          break;
        }
        case rmtDOWN:
        {
          coildown();     //retract the cylinder
          remote = 0;
          break;
        }
       
      }
     
    }
}


/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void coildown()
{
    byte msglength = sprintf(message, "%d%d", 0, downtime); //sets msglength to the length of the string created in message. first %d is replaced with 0, second %d is replaced with downtime int
     
    radio.sendWithRetry(TONODEID, message, msglength);   //send the message back to the remote and retry if no ack was received.   
   
    digitalWrite(coilupmosfet, LOW);      //turn off up solenoid
    digitalWrite(coildownmosfet, HIGH);   //turn on down solenoid
      for (int x = downtime; x > 0; x--){   //initiate countdown using the settings for time
        delay(1000);
      }                           
    digitalWrite(coilupmosfet, LOW);       //make sure all solenoids are off
    digitalWrite(coildownmosfet, LOW);

   
    if (checkpos()){                     //check limit switches to see if it worked, if still raised, try again.
      digitalWrite(coilupmosfet, LOW);      //turn off up solenoid
      digitalWrite(coildownmosfet, HIGH);   //turn on down solenoid
      for (int x = downtime; x > 0; x--){   //initiate countdown using the settings for time
        delay(1000);
      }                           
      digitalWrite(coilupmosfet, LOW);       //make sure all solenoids are off
      digitalWrite(coildownmosfet, LOW);
    }

   
    if (checkpos()){                 //check the switch again... if the cylinder is still up...
      sprintf(message, "%d%d", 2, downtime); //change the status to '2' to indicate there is some kind of error with the system
      radio.sendWithRetry(TONODEID, message, msglength);   //send error message back to remote

    }

   
}

/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void coilup()
{
   byte msglength = sprintf(message, "%d%d", 1, uptime); //sets msglength to the length of the string created in message. first %d is replaced with 1, second %d is replaced with uptime int
     
     
    radio.sendWithRetry(TONODEID, message, msglength);   //send the message back to the remote and retry if no ack was received.
       
   digitalWrite(coilupmosfet, HIGH);   //Activate up solenoid
   digitalWrite(coildownmosfet, LOW);  //make sure down solenoid is off
      for (int x = uptime; x > 0; x--){   //initiate the countdown using the settings for the time
        delay(1000);                       //delay 1 second
      }                                     
   digitalWrite(coilupmosfet, LOW);       //make sure all solenoids are off
   digitalWrite(coildownmosfet, LOW);
   
   
}

/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

bool checkpos(){
  posstatus = digitalRead(limitswitch);
  return posstatus;
}

/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

Any guidance would be great! This forum has taught me a lot already!

ChemE

  • Sr. Member
  • ****
  • Posts: 419
  • Country: us
The data is transferred back and forth as bytes between the radios.  As long as the node packages it the same way as the gateway unpackages it, you won't have any problems.  Below is some of my code that loads and unloads a few numeric values in a payload.

Node-side code to load the payload
Code: [Select]
uint8_t data[4];  //16-bit Vcc, 16-bit packet counter

data[0] = ADCL;                   // Avoid 16-bit math in this loop since it adds 40us
data[1] = ADCH;                   // Read back the results of the ADC
data[2] = (++packet_cnt & 0xFF);  // Increment packet_count and stash the LSB
data[3] = (packet_cnt >> 8);      // Stash the MSB of the packet counter

radio.send(RECEIVER, data, 4, 0); //target node Id, message as string or byte array, message length, ack requested

Gateway-side code to unload the payload
Code: [Select]
uint16_t Last_Packet, Curr_Packet, Packets_Received, Packets_Missed;

uint16_t rbgv = radio.DATA[1]<<8 | radio.DATA[0];
uint16_t voltage = ((1125300/rbgv)+5)/10;
Curr_Packet = radio.DATA[3]<<8 | radio.DATA[2];
« Last Edit: April 14, 2018, 09:16:45 AM by ChemE »

ChemE

  • Sr. Member
  • ****
  • Posts: 419
  • Country: us
And this concept may be a bit advanced for where you are right now, but if you want to minimize power as much as possible, shorter messages mean less transmission.  You are wanting to send a command which is 0-2 and then a time to sleep between 0-30.  Neither of those deserves an integer (16 bits) since they are naturally bytes (8 bits).  But in reality, they both fit in the same byte since your command requires 2 bits and your sleep time requires 5 bits.  Again, as long as the node and gateway both know how to package/unpackage the payload, you can do whatever you want.  I routinely stuff variables together like this to save on payload size plus I enjoy the mental challenge.  Your payload could be:

Bit:       7  6  5  4  3  |     2        |          1  0
Data:     sleep time   | extra bit  | command/error

by doing this on the node side:

Code: [Select]
uint8_t sleep_time, command;
uint8_t data;

sleep_time = 30;
command = 2;           /* an error occurred */

data = sleep_time<<3   /* shift the value of 30 left 3 bits - now the top 5 bits hold sleep_time's value */
data |= command;       /* write command to the lowest 2 bits */
« Last Edit: April 14, 2018, 09:21:36 AM by ChemE »

doublec4

  • Jr. Member
  • **
  • Posts: 53
Hi ChemE, thanks for your response.

So basically what you're saying is that by going through sprintf and changing my numbers into chars and then on the other side trying to take those chars and turn them back into numbers I am making things more complicated than need be.

I can essentially create an array of bytes (each one representing a different byte variable I want to send) and just send that whole array. My receiver then just knows which array position to look at for each variable. That seems much easier!

In your second example I have to read more into bitwise operators to understand them before understanding what is being done in that code.

Thanks! I'll give the byte array a try and follow up

doublec4

  • Jr. Member
  • **
  • Posts: 53
For some reason I'm still having trouble with this...

So if on my node I try to send a message like this (I cut out all of the radio initialization code etc for simplicity)

Code: [Select]
byte outgoing[2];
#define posSTATUS    0
#define sleepTIME     1

//then somewhere else in my code:

outgoing[posSTATUS] = 1;   //set the posstatus to 1 for the outgoing message
outgoing[sleepTIME] = 20;   //set the sleeptime for the remote to 20 seconds
radio.sendWithRetry(TONODEID, outgoing, sizeof(outgoing));   //send the message back to the remote and retry if no ack was received.


and try to receive it on the receiver like this (also cut out the radio.receiveDone() etc to just get the point across):

Code: [Select]
byte incoming[2];   //this is the incoming information from the receiver. 
#define posSTATUS    0     //First position is the posstatus where 1 =  up, 0 = down, 2 = error
#define sleepTIME     1     //Second position is the sleeptime in seconds

 for (byte x = 0; x < sizeof(incoming); x++){   
              incoming[x] = radio.DATA[x];                    //take the incoming byte array and stuff it into the byte array
            }


and now try and access incoming[0] or incoming[1] I should see values 1 and 20 right?

« Last Edit: April 14, 2018, 01:27:12 PM by doublec4 »

ChemE

  • Sr. Member
  • ****
  • Posts: 419
  • Country: us
Correct.  BTW, for development work on a send/receive pair, use a USB hub, plug 2 motes into the hub and open 2 instances of the Arduino IDE.  This lets you have serial comms between each mote so you can easily see both what is being sent and what is being received.

LukaQ

  • Sr. Member
  • ****
  • Posts: 302
  • Country: si
Or DualTerminal, which is also time correlated

doublec4

  • Jr. Member
  • **
  • Posts: 53
I'll have to try to see whats happening through Serial because this still isn't working? For example, if I take my example code above and test the output at the receiver by turning on an LED:

Code: [Select]
byte incoming[2];   //this is the incoming information from the receiver. 
#define posSTATUS    0     //First position is the posstatus where 1 =  up, 0 = down, 2 = error
#define sleepTIME     1     //Second position is the sleeptime in seconds

 for (byte x = 0; x < sizeof(incoming); x++){   
              incoming[x] = radio.DATA[x];                    //take the incoming byte array and stuff it into the byte array
            }

if (incoming[0]== 1) digitalWrite(LED, HIGH);          //I defined pin 15 as as the LED earlier in my code, I am using a Moteino Mega for this


so I'm not sure what is in incoming[0] but it does not appear to be the value of 1 if that LED is not turning on...

doublec4

  • Jr. Member
  • **
  • Posts: 53
So trying this with the Serial monitor... if I send this:

Code: [Select]
byte message = 0;   //this is the outgoing byte to send to the receiver
//send it
message = 1;          //change the message to '1' to command the pump to turn on
radio.sendWithRetry(TONODEID, message, 1);

and then on the receiving end if I do this:

Code: [Select]
byte remote = 0;
remote = radio.DATA[0]; //the incoming remote command set to variable remote

Serial.print("remote variable sent: ");
Serial.println(remote);


This results in--> "remote variable sent: 0"

If I change the message variable to '2' and I send that, I get: "remote variable sent: 109"

 :-\

TomWS

  • Hero Member
  • *****
  • Posts: 1930
If I change the message variable to '2' and I send that, I get: "remote variable sent: 109"

 :-\
I suggest you try two things:
1. Read the RFM69 library prototype for sendWithRetry to see what TYPE the second parameter is supposed to be.
2.try modifying your transmit code to:

Code: [Select]
radio.sendWithRetry(TONODEID, &message, 1);


doublec4

  • Jr. Member
  • **
  • Posts: 53
Hi Tom,

I apologize for my ignorance as I'm still just a beginner. When I look at the .cpp file and search for sendwithretry the buffer type appears to be "const void*" which appears to be the same for the regular .send command? Am I not reading this correctly?

so the & in front of the variable makes it point to the memory address?

If my message variable was an array such as message[2], could I then have sent it as message without the & in front? Then it would be pointing to the memory address by default, correct?

ChemE

  • Sr. Member
  • ****
  • Posts: 419
  • Country: us
Don't feel bad, my strongest language is VB which means I'm quite weak with pointers and dereferencing.  You'll get it and once you do, it is off to the races!