LowPowerLab Forum

Hardware support => Moteino => Topic started by: Steinarrr on October 21, 2015, 09:19:30 PM

Title: My well commented BaseSketch
Post by: Steinarrr on October 21, 2015, 09:19:30 PM
Hi everybody, I have been working on designing a small network of devices controlled by a PC (haven't we all, right ;))
Anyway I wrote my base sketch and commented it heavily so that my collaborators could learn from it as they haven't used moteinos before and I figured that it might also be useful for someone else out there.

Here it is:

// This is a sketch written for a base. The base is a moteino that acts as a gateway between
// a PC and other moteinos. It is connected to the PC through a serial port and relays info
// to and from the radio network.

// The sketch is a mash up between the gateway, struct send and the struct recieve sketches
// of the moteino library.

// Written by Steinarr
////////////////////////////////////////////////////////////////////////////////////////////

// our includes:
#include <RFM69.h>   
#include <SPI.h>

// our defines
#define NODEID        1    // Unique for each node on same network, this is the base and it
                           // gets to have the ID of 1 
#define NETWORKID     7    //the same on all nodes that talk to each other
//Match frequency to the hardware version of the radio on your Moteino:
#define FREQUENCY     RF69_433MHZ
#define ENCRYPTKEY    "ABCDABCDABCDABCD" //exactly the same 16 characters/bytes on all nodes!
#define SERIAL_BAUD   9600
RFM69 radio;
bool promiscuousMode = false; // set to 'true' to sniff all packets on the same network


// Here we are defining "Payload" as a type of struct, in our case it contains an array of 11 ints
typedef struct{int numbers[11]; } Payload;

// and here we define "OutgoingData" as a Payload. Most people are more familiar with seeing something
// like: "int A" where we define A as an int. In the same way ae are defining OutgoingData as a Payload.
// Remember we just defined "Payload" as a type of structure.
Payload OutgoingData; 
Payload IncomingData; // Same goes for IncomingData

// In order to do less calculating at runtime i figured i would define a global variable to hold the size
// (Because this base will always be sending and recieving the same structure)
byte DataLen = sizeof(OutgoingData);

void setup()
{ // Setup runs once
  Serial.begin(SERIAL_BAUD);
  delay(10);
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  radio.setHighPower(); //only for RFM69HW! (all of ours are HW)
  radio.encrypt(ENCRYPTKEY);
  radio.promiscuous(promiscuousMode);
  Serial.println("Ready  ");
}
// Global variables to recieve incoming serial messages
char Temp[8] = ""; // Temp will hold our string that contains the number
int Send2ID = 0;
byte TempLen = 0;  // TempLen will keep track of how long Temp is
byte Counter = 0; // Counter keeps count of how many numbers have been recieved.


void loop()
{  //loop runs over and over forever
   // we want to do 2 thing at once, Listen to the Serial port and the radio. We can't
   // actually do both at the 'same' time but we can do one and then the other, extremely fast.
 
  // So, lets first process any serial input:
  if (Serial.available() > 0)
  {
    // The string will be on the form (Send2ID)#(number1):(number2):    with up to 10 numbers.
    // every number will be 'terminated' by a ':'
    char incoming = Serial.read(); // reads on char from the buffer
    if (incoming == '\n')
    { // if the line is over
      sendTheStuff();
      Counter = 0;
    }
    else
    {
      if (incoming==':')
      { // if the is 'terminated'
        OutgoingData.numbers[Counter] = Int(Temp,TempLen); // Cast the number to int
        Counter++;
        TempLen = 0;
      }
      else if (incoming == '#')
      {
        Send2ID = Int(Temp,TempLen);
        TempLen = 0;
      }
      else
      {
        Temp[TempLen] = incoming;
        TempLen++;
      }
    }
  }
 
  // and then check on the radio:
 
  // The radio is always listening and recieving but doesn't respond on its own,
  // We have to constantly check if something has been recieved and answer with an ACK
  if (radio.receiveDone())
  {
    // the PC expects us to deliver the info in the same way as it gives us info, that is:
    // (senderID)#(number1):number2:  with up to 10 numbers and then a newline symbol.
   
    // First lets put what we recieved into IncomingData. We have to do this before we
    // send the ACK because the radio.DATA cache will be overwritten when sending the ACK.
    IncomingData = *(Payload*)radio.DATA;
    Serial.print(radio.SENDERID); // radio.SENDERID will also be overwritten so let's print it now
    if (radio.ACKRequested())
    {
      radio.sendACK();
    }
    // and then print the rest:
    Serial.print('#');
    for(byte i = 0; i<11;i++)
    {
      Serial.print(IncomingData.numbers[i]);
      Serial.print(':');
    }
    Serial.println();
  }
}

// This functoin parses a string to its decimal value.
// Inputs are the string and its length
int Int(char* c, byte len)
{
  int exp10[] = {1,10,100,1000,10000,100000};
  int ans = 0;
  for (byte i = 0;i<len;i++)
  {
    ans += (c[i]-'0')*exp10[len-i-1];
    // '5'-'0' subtracts the ASCII value of '0' from the ASCII value of '5'
    // leaving us with the number 5.

  }
  return ans;
}

void sendTheStuff()
{
  if (!radio.sendWithRetry(Send2ID,(const void*)(&OutgoingData),sizeof(OutgoingData)))
  {
    Serial.println("ERROR: NO ACK Recieved");
  }
}



do what you want with it and feel free to ask  :)

-Steinarr
Title: Re: My well commented BaseSketch
Post by: Sergegsx on October 22, 2015, 03:19:00 AM
very nice, I always like to over comment my code so that I can read it if I come back to it years later.
How about explaining this two lines in more details? I am now reading about pointers so a nice explanation of why are we using "*" and "&" here would be useful

IncomingData = *(Payload*)radio.DATA;

if (!radio.sendWithRetry(Send2ID,(const void*)(&OutgoingData),sizeof(OutgoingData)))

Thanks
Title: Re: My well commented BaseSketch
Post by: Steinarrr on October 22, 2015, 11:19:15 AM
Well, I'm no expert and haven't used pointers (http://home.netcom.com/~tjensen/ptr/pointers.htm) a lot, those lines of code are directly from Felix's example sketches but I'll give it a shot.

These are both instances where we are casting (https://www.arduino.cc/en/Reference/Cast) pointers.

IncomingData = *(Payload*)radio.DATA;
We want to copy radio.DATA in to IncomingData. A conventional 'IncomingData = radio.DATA' would not work here because radio.DATA is of type uint8_t[61] which is obviously not the same as our Payload datatype.
We are casting what we received (and is being held in radio.DATA) in to IncomingData. We are basically telling the compiler that we want to take whatever is in radio.DATA, cast that into our Payload structure and then copy this to IncomingData.
Like Gilles says on this StackExchange thread (http://stackoverflow.com/questions/17260527/what-are-the-rules-for-casting-pointers-in-c) "A pointer is an arrow that points to an address in memory, with a label indicating the type of the value. The address indicates where to look and the type indicates what to take. Casting the pointer changes the label on the arrow but not where the arrow points"
The first * operator gives us whatever is located in memory where it is pointing. The second * operator is changing the label on our pointer (which would have been uint8_t[61]) to Payload. So we are retrieving a Payload structure from the memory where radio.DATA is located

if (!radio.sendWithRetry(Send2ID,(const void*)(&OutgoingData),sizeof(OutgoingData)))
Again, casting pointers. The library function sendWithRetry expects a pointer of type const void. Why? Because it doesn't know what datatype you are sending and it doesn't really have to, it just has to know where this data is located in memory and how long it is. Thus it asks for a pointer with no label (because it won't use the label) and a bufferSize which we hand it with sizeof().
The & operator retrieves the address of OutgoingData and the * operator is changing the label to const void because that is what the function expects.

hope this helps :)
Title: Re: My well commented BaseSketch
Post by: Felix on October 22, 2015, 12:13:58 PM
Steinarrr,
Great explanation and example, thanks!
But I can say from experience, that without practicing pointers, theory will remain just that. For some reason pointers are just hard to get unless people practice and write some code to see how pointers are truly great and a major reason why C++ is still around and forever will be.