Please review my RF24 IoT network

JohnHoward, how are you supplying 3.3v to the RF module from the pro minis? Or are you using the 3.3v version of the mini?

I'm mostly using nano's, but since the pro minis are so cheap on ebay these days, I'm thinking I won't even use headers. I'll just solder wires directly from the mini to the rf24 module and to a temp sensor to minimize cost and space.

-transfinite

I found some small 5v-to-3v regulator breakouts a while back

http://www.aliexpress.com/snapshot/215915954.html

and am using those.

Since ultimately I plan to connect some PIR sensors that need a 5v supply, I run the minis at 5v (which means faster clock speed, too). I failed to notice the mini's didn't have 3v3 output when I bought them, otherwise I'd have a bunch of nanos.

pico:

JohnHoward:
Sorry if I'm being dense but are you saying:

#define NODEACK(x) (0xCC00000000LL + x)

should be edited to replace 'x' with a number? I compiled verbatim, with the 'x'. I presume #define doesn't know what 'x' is and ends up using zero? I'm astonished there is no compiler error due to x being undefined, which it seems to be as I could not find anything anywhere declaring it in the code files.

Google "C macro tutorial". #define is not just for constant values.

x is an argument in the macro definition, analogous to a argument declared in a function header. It doesn't have any value until you instantiate the macro, e.g., NODEACK(1) gets translated to (0xCC00000000LL + 1) by the preprocessor, and that's what the compiler sees.

Ah, the light comes on. I wasn't thinking in terms of a macro but as a constant declaration. After 15 years of writing macro assembler code on PDP-11's in the olden days, you'd think I'd have made the connection.

Maybe I found the reason for the relay stopping. If there is a dup detected, then the radio will be left in .stopListening() condition.

Now that I've said that and hit Post, I'll probably see it lock up again, but so far (15 minutes or so of watching it) and it has not.

I added a radio.startListening() right after this line, at around line 72:

radio.write( &header.ID, sizeof(header.ID), true ); //send out ack with the id of our received message

Edit: OK, it ran all night without locking up.

Looked at the node example after noting it would occasionally stop sending. Found a problem:

      if (src == header.ID)
        Serial.print("ACK: ");Serial.println(src, HEX);
        retries = 0;

There should be braces enclosing the 3 statements comprising the 'if' condition.

I noticed the missing braces yesterday while I was getting rid of the gotos from node.ino. I just couldn't deal with them any longer.
This seems to be working for me. I mostly just added a while loop. Posted in its entirety for clarity.

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"

RF24 radio(9,10);

#define RELAYBROADCAST(x) (0xAA00000000LL + x)
#define NODEACK(x) (0xCC00000000LL + x)

struct SENSOR{
  float temp;
  float humidity;
  float pressure;
};

struct HEADER{
  long type;
  long hops;
  long src;
  long ID;
  SENSOR sensor;
};

  HEADER header;

  long cnt[10] = {};
  byte cntID = 0;
  byte retries = 0;             //how many times have we tried to rx
  const byte MAX_RETRIES = 5;  //how many times will we try?
  

void setup(void){
  Serial.begin(57600);
  radio.begin();
  radio.setRetries(15,15);
  radio.enableDynamicPayloads();
  randomSeed(analogRead(0));
  Serial.println("node starting...");
  radio.openReadingPipe(1 ,NODEACK(1));
  radio.openWritingPipe(RELAYBROADCAST(2));
  radio.startListening();
 }

void loop(void){
  header.ID = random(1, 0xffff);    //this is above the label for testing
  while (retries < MAX_RETRIES) {

    radio.stopListening();
    header.type = 3;
    header.hops = 0;
    header.src = 0xabcd;
    
    header.sensor.temp = 78.8;
    Serial.println(header.ID, HEX);
    //send a relay broadcast to any relay that can hear
    radio.openWritingPipe(RELAYBROADCAST(1));
    radio.write( &header, sizeof(header), true );
    radio.startListening();
    // Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 200 )
        timeout = true;

    // Describe the results
    if ( timeout ){
      Serial.print("NACK");
      retries++;
    }
    else {
      long src;      //ack returns just the header.ID, so check what was returned with what was sent
      radio.read( &src, radio.getDynamicPayloadSize() );
      if (src == header.ID) {
        Serial.print("ACK: "); Serial.println(src, HEX);
        retries = 0;
        break;
      }
    }
  }
  
  retries = 0;
  delay(3000);

  //testing
if ( Serial.available() )
  {
    radio.stopListening();
    Serial.read();
    header.type = 3;
    header.hops = 0;
    header.src = 0xabcd;
    header.ID = random(1, 0xffff);

    Serial.println(header.ID, HEX);
  //send a relay broadcast
  radio.openWritingPipe(RELAYBROADCAST(2));
  bool ok = radio.write( &header, sizeof(header), true );
  radio.startListening();
  }
}

-transfinite

I took the liberty of making the code more structured and (I hope, readable):

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"

RF24 radio(9,10);

const long long       // Use pipe + 1 for nodes, pipe + 2 for relays
  RELAYBROADCAST  = 0xAA00000000LL,
  NODEACK         = 0xCC00000000LL;

struct SENSOR{
  float temp;
  float humidity;
  float pressure;
};

struct HEADER{
  long type;
  long hops;
  long src;
  long ID;
  SENSOR sensor;
};

  HEADER header;
  
  long cnt[10] = {};
  byte cntID = 0;
  byte retries = 0;                 // How many times have we tried to rx
  const byte MAX_RETRIES = 10;      // How many times will we try?
  

void setup(void){
  Serial.begin(57600);
  radio.begin();
  radio.setRetries(15,15);
  radio.enableDynamicPayloads();
  randomSeed(analogRead(0));
  Serial.println(F("node starting..."));
  
  radio.openReadingPipe(1 ,NODEACK + 1);  // Read the 'node' pipe
  
  relay(header, 2);           // Send using the 'relay' pipe
 }

void loop(void){

  header.ID = random(1, 0xffff);      // Identify this packet
  
  xmit(header.ID);            // Send some data
   
  wait(MAX_RETRIES, header);        // Wait for it to be acknowledged
  
  delay(3000);              // Pause before repeating
  
  beARelay();               // If serial port connected send some more data (for what purpose?)
}

// Send some data to the base
void xmit(long id) {

  HEADER header;
  
  header.ID = id;
  header.type = 3;
  header.hops = 0;
  header.src = 0xabcd;
  
  header.sensor.temp = 18.8;
  Serial.print(F("XMIT: "));Serial.println(header.ID, HEX);
  
  relay(header, 1);           // Send using the 'node' pipe
}

// Get Ack from relay or timeout
void wait(byte retries, struct HEADER header) {
  
  do  {
    // Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    
  if (millis() - started_waiting_at > 250 ) {
    
    retries = nak();
  }
    else {
      
      retries = ack();
    }

  } while (retries > 0);
}

// Signal a NAK
byte nak() {
  
  Serial.print(F("NACK:      ")); Serial.println(header.ID, HEX);
  
  return --retries;
}

// Signal an ACK
byte ack() {
  
  if (radio.available()) {
    
    long src;      //ack returns just the header.ID, so check what was returned with what was sent
    radio.read( &src, radio.getDynamicPayloadSize() );
    
    if (src == header.ID) {
      Serial.print(F("ACK:            ")); Serial.println(src, HEX);
      retries = 0;
    }
    else {
      Serial.print(F("???:            ")); Serial.print(src, HEX); Serial.print(" vs "); Serial.println(header.ID, HEX);
    }
  }
  return retries;
}

// If something is typed on the Serial console, pretend to be a relay and send a relay broadcast
void beARelay() {
 
if ( Serial.available() )
  {
    Serial.read();
    
    HEADER header;
    
    header.type = 3;
    header.hops = 0;
    header.src = 0xabcd;
    header.ID = random(1, 0xffff);

    Serial.print(F("TEST: ")); Serial.println(header.ID, HEX);
    
    relay(header, 2);
  }
}

// Send packet to any relay that can hear this node
bool relay(struct HEADER header, byte pipe_id) {

  radio.stopListening();
  radio.openWritingPipe(RELAYBROADCAST + pipe_id);
  bool ok = radio.write( &header, sizeof(header), true );
  radio.startListening();
  
  return ok;
}

Curious thing, the node doesn't print out that many ACKs. I get a steady stream of 'XMIT:" output on the node's console without an "ACK:" appearing. However, watching the relay and master, I see the node's packet appearing. So there's something amiss in the relay, it would seem. I tried both increasing and decreasing the 'turnaround time' - delay(20) - as well as the timing in the node, to no avail.

That is more readable, but I don't understand why you reallocate header in xmit() and beARelay()when it is already a global variable. I'd leave the redeclaration of HEADER header out.

Also, as in the original node, the delay(3000) and then checking for Serial.available() just doesn't seem like a good idea. The delay should be removed, and replaced with something like:

long previousMillis = 0; 

void loop(void){
  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > 3000) {
    previousMillis = currentMillis;
    
    header.ID = random(1, 0xffff);    // Identify this packet
    xmit(header.ID);                       // Send some data
    wait(MAX_RETRIES, header);       // Wait for it to be acknowledged 
    beARelay();
  }
}

Otherwise, it's all good.

-transfinite

True I neglected to remove the global header declaration. My preference was to allocate the header within the function that would utilize it, rather than have one globally reused. That's my OCD kicking in -- disliking data structures that lay around and run the risk of getting corrupted if the radio functions screw up. I know there will be an intact, clean header structure each time the radio gets it.

I hoped to clear up the sporadic malformed packets with huge hop counts or other bogus data, assuming it was data corruption of the global structure. But they still show up from time to time.

That's surprising since the nRF24L01 is supposed to be managing the end-to-end data transfers to avoid corruption in-transit.

After having time to think about it (but not enough time to actually test it), the code I posted without the delay missed the point of checking for serial availability without pausing. The call to beARelay()is not supposed to be inside the if(currentMillis... block. That defeats the whole purpose of it. To make it more explicit, I'd move the check for serial out of beARelay, and back into the main loop like this:

long previousMillis = 0;

void loop(void){
  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > 3000){
    previousMillis = currentMillis;
    
    header.ID = random(1, 0xffff);   // Identify this packet
    xmit(header.ID);                 // Send some data
    wait(MAX_RETRIES, header);       // Wait for it to be acknowledged 
  }
  
  if (Serial.available()){
	beARelay();
  }
}

Hopefully, that makes sense.

On large hop counts- I noticed that since the fix to the relay, I'm only seeing large hop counts when I'm at the edge of the radio's range. I was seeing this when I moved a node upstairs. I moved the relay just a few inches closer (and away from a metal lamp), and it ran overnight without a non-zero hop count.

I just ordered a RF24 radio with an external antenna to see how that affect the range.

-transfinite

I resolved my angst over global HEADER declarations! :smiley:

Also, I found that relay() transmits would fail fairly often (sporadically) when I added another Serial.print to watch the radio.write(). So I implemented a retry there if the radio.write() returns false. Now seems to send/ack flawlessly. I suppose there are collisions with other nodes transmitting simultaneously, as this happened quite randomly.

My latest 'node' code:

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"

RF24 radio(9,10);

const long long       // Use pipe + 1 for nodes, pipe + 2 for relays
  RELAYBROADCAST  = 0xAA00000000LL,
  NODEACK         = 0xCC00000000LL;

struct SENSOR{
  float temp;
  float humidity;
  float pressure;
};

struct HEADER{
  long type;
  long hops;
  long src;
  long ID;
  SENSOR sensor;
};
 
  long cnt[10] = {};
  byte cntID = 0;
  byte retries = 0;                 // How many times have we tried to rx
  const byte MAX_RETRIES = 5;       // How many times will we try?
  
  long myID;

void setup(void){
	
  Serial.begin(57600);
  radio.begin();
  radio.setRetries(15,15);
  radio.enableDynamicPayloads();
  randomSeed(analogRead(0));
  Serial.println(F("node starting..."));
  
  radio.openReadingPipe(1 ,NODEACK + 1);  // Read the 'node' pipe
  
  myID = random(1, 0xffff);      // Identify this packet
  xmit(myID, 2);           // Send using the 'relay' pipe
 }

void loop(void){

  myID = random(1, 0xffff);      // Identify this packet
  
  xmit(myID, 1);            // Send some data
   
  wait(MAX_RETRIES, myID);        // Wait for it to be acknowledged
  
  delay(3000);              // Pause before repeating
  
  beARelay();               // If serial port connected send some more data (for what purpose?)
}

// Get Ack from relay or timeout
void wait(byte retries, long myID) {
  
	bool reply;
	
	do {
		
 		unsigned long started_waiting_at = millis();
  
	  while (millis() - started_waiting_at < 250) {
	    // Wait here until we get a response, or timeout (250ms)
	    
	    if (reply = radio.available()) break;
	  }

	  retries--;
	  
  } while (retries > 0 && !reply);
  
  
  if (reply) {
	  
	  ack(reply, myID);
  }
  else {
	  
	  nak(myID);
  }
}

// Signal a NAK
void nak(long myID) {
  
  Serial.print(F("NACK:      ")); Serial.println(myID, HEX);
  
}

// Signal an ACK
void ack(bool reply, long myID) {
  
  if (reply) {
    
    long src;      //ack returns just the header.ID, so check what was returned with what was sent
    radio.read( &src, radio.getDynamicPayloadSize() );
    
    if (src == myID) {
      Serial.print(F("ACK:            ")); Serial.println(src, HEX);
    }
//     else {	// Display packets destined for nodes other than us
// 	    Serial.print(src, HEX);Serial.print(" = "); Serial.println(myID, HEX);
//     }
  }
  else {
	  Serial.println(F("NOT AVAILABLE"));
  }
}

// If something is typed on the Serial console, pretend to be a relay and send a relay broadcast
void beARelay() {
 
if ( Serial.available() )
  {
    Serial.read();
       
    myID = random(1, 0xffff);
    
    xmit(myID, 2);

    Serial.print(F("TEST: ")); Serial.println(myID, HEX);
  }
}

// Send some data to the base
void xmit(long myID, byte pipe_id) {

  HEADER header;
  
  header.ID = myID;
  header.type = 3;
  header.hops = 0;
  header.src = 0xabcd;
  
  header.sensor.temp = 18.8;

  Serial.print(F("XMIT: "));Serial.println(header.ID, HEX);

  byte retries = 5;
  bool ok;
  
  do {
	  ok = relay(header, pipe_id);           // Send using the 'node' pipe
  } while (!ok && --retries > 0);
  
}

// Send packet to any relay that can hear this node
bool relay(struct HEADER header, byte pipe_id) {

  radio.stopListening();
  radio.openWritingPipe(RELAYBROADCAST + pipe_id);
  bool ok = radio.write( &header, sizeof(header), true );
  
  Serial.print(ok ? F("SENT            ") : F("FAILED TO SEND  "));	// Seems to fail to transmit often
  Serial.println(header.ID, HEX);

  radio.startListening();
  
  return ok;
}

Let a base, relay and 3 nodes run most of the day while counting the traffic:

XMIT: 47D7, ACKS = 3839, NACKS = 696, XMITS = 4754, RETRIES = 3979
SENT 47D7
ACK: 47D7
XMIT: 45A7, ACKS = 3840, NACKS = 696, XMITS = 4755, RETRIES = 3979
FAILED TO SEND 45A7
SENT 45A7
ACK: 45A7
XMIT: 8BDB, ACKS = 3841, NACKS = 696, XMITS = 4756, RETRIES = 3980
FAILED TO SEND 8BDB
FAILED TO SEND 8BDB
SENT 8BDB
ACK: 8BDB

"RETRIES" indicates a radio.write() that returned false and was retried (usually it retries several times)
"XMITS" are radio.write() that returned true
"ACKS" are received acknowledgements

The good news is most messages get through. The bad news is it takes lots of trying to get there. For my planned purposes, I don't see this being detrimental. I would only need to send a message at intervals of several minutes. So retrying several times won't matter. And if I have to send out twice as many and just throw out the extras to be sure they get received, I'm OK with that.

I really should try to count the receptions at the base to find out whether ACKS are missed because they were not sent back from the base or were simply not heard by the node for some reason.

I just hope the success rate doesn't fall off rapidly as the number of nodes increases. I still have to prepare 6 more nodes and possibly another relay depending on what I find in actual operation. I will get that question answered once I have them running.

Great work on fixing the relay issue and rewriting the node code.

Anyways, I've updated the github code with the new node and relay code (and given credit where due, of course).

Oh, and as a sidenote. I didn't mention it in my initial post, but the hardware layout for the relays and base (in my head) had them being LNA PA radios, and should be able to reach 1k meters. All the nodes would be in range of one of the relays. If they couldn't reach, another relay would be placed somewhere in range. With such a large range, all the relays should be in range of all the others, adding a lot redundancy. They PA radios are slightly more expensive, but probably less expensive than adding several cheaper ones to make range. They can be found on any of the Chinese sites
http://www.aliexpress.com/item/Special-promotions-1100-meter-long-distance-NRF24L01-PA-LNA-wireless-modules-with-antenna/921867287.html

The next thing I'll be working on is the sleep code for nodes. There is no reason for them to be running while they are just waiting for the next time to send. I've already had it working in previous incarnations of this project and is quite simple. I had planned for the relays and nodes to be externally powered, but my nodes will be powered from a coin cell.

There's a bug in the new node code. I added some print statements in the base to print the received temp, humidity, and pressure. In the new node code I added one line to set the pressure to a constant:

header.sensor.temp = 18.8;
header.sensor.pressure = 30.1;

In the prior versions of node, I would receive the temp and pressure values on the base, but with the current version I receive an ovf (overflow) for the pressure. The temp is still ok. Seems like a fencepost error somewhere, but I haven't found it yet.

-transfinite

transfinite:
There's a bug in the new node code. I added some print statements in the base to print the received temp, humidity, and pressure. In the new node code I added one line to set the pressure to a constant:

header.sensor.temp = 18.8;
header.sensor.pressure = 30.1;

In the prior versions of node, I would receive the temp and pressure values on the base, but with the current version I receive an ovf (overflow) for the pressure. The temp is still ok. Seems like a fencepost error somewhere, but I haven't found it yet.

-transfinite

Every so often, the packet contents has huge numbers in it. Sometimes I find the packet size vary from the normal 28 bytes to 20 or 24. Somehow the framing is off.

printf detects the value it was given is larger than 4294967040 (FFFFFF00) and outputs 'ovf'. Evidently the SPI reads or writes are screwing up, if we are to believe the nRF24L01 communicates error-free (which it apparently does because there are no checksum errors).

Perhaps retrying more slowly is the answer, as I added 'node' code to do so whenever radio.write() returns false.

hi,
i have 2 nodes and relay and base, nodes send data, relay gets it but base dont get it, what seems to be the problem?
node id 2 & 7

node 2 serial :

XMIT: 2
FAILED TO SEND 2
289
FAILED TO SEND 2
289
FAILED TO SEND 2
289
FAILED TO SEND 2
289
FAILED TO SEND 2
289
ACK: 2

node 7 serial :

FAILED TO SEND 7
331
FAILED TO SEND 7
331
FAILED TO SEND 7
331
FAILED TO SEND 7
331
FAILED TO SEND 7
331
NACK: 7
XMIT: 7

relay serial:

Got message from 0x10F ID:2 Hops: 266
Got message from 0x10F ID:2 Hops: 266
Got message from 0x10F ID:2 Hops: 266
Got message from 0x10F ID:2 Hops: 266
Got message from 0x10F ID:2 Hops: 266
Got message from 0x186 ID:7 Hops: 402
Got message from 0x186 ID:7 Hops: 402
Got message from 0x186 ID:7 Hops: 402
Got message from 0x186 ID:7 Hops: 402

base serial:

base starting...
Got message from 0x6060606 ID:6060606 Hops: 101058054
Got message from 0x1010101 ID:1010101 Hops: 16843009
Got message from 0x2020202 ID:2020202 Hops: 33686018
Got message from 0x5050505 ID:5050505 Hops: 84215045
Got message from 0x3030303 ID:3030303 Hops: 50529027
Got message from 0xE0E0E0E ID:E0E0E0E Hops: 235802126
Got message from 0x4040404 ID:4040404 Hops: 67372036
Got message from 0x141 ID:7 Hops: 319
Got message from 0x13131313 ID:13131313 Hops: 320017171
Got message from 0x14141414 ID:14141414 Hops: 336860180
Got message from 0x6060606 ID:6060606 Hops: 101058054
Got message from 0x1B1B1B1B ID:1B1B1B1B Hops: 454761243
Got message from 0x7070707 ID:7070707 Hops: 117901063

i noticed that if i do make a reset all arduinos at same time, then base gets first data but nothing else...

base starting...
Got message from 0x19F ID:1 Hops: 406
Got message from 0x1BA ID:7 Hops: 450
Got message from 0x1010101 ID:1010101 Hops: 16843009

Bizarre that you consistently get large hop counts. Have you filtered the 3v3 with a 10mF capacitor (or larger) @ the nRF? I would be suspicious of noisy power causing erratic reading of the data.

Notice how they are not merely random but repetitive: 0x10101010 0x20202020, etc.

little confusing is this thing also, do i have to change anything?

node :
const long long // Use pipe + 1 for nodes, pipe + 2 for relays
RELAYBROADCAST = 0xAA00000000LL,
NODEACK = 0xCC00000000LL;

relay:

#define BASEBROADCAST(x) (0xBB00000000LL + x)
#define RELAYBROADCAST(x) (0xAA00000000LL + x)
#define NODEACK(x) (0xCC00000000LL + x)

base:

#define BASEBROADCAST(x) (0xBB00000000LL + x)
#define RELAYBROADCAST(x) (0xAA00000000LL + x)

and how/where can i use command : setDataRate()
if i put it on void setup(void) nothing happens , serial woun t start..

I was confused, and still am bothered, by the macros (such as BASEBROADCAST().

The original poster started from some code another person wrote and carried through that means of assigning addresses. I thought it was unnecessary and while fiddling around with the code to modularize the code I took them out. I was only working in the node, however, so the macros are still in the relay and base.

However, the three sketches do work together without changing any of the radio pipe addresses.

I'm still refining my approach by writing classes for each of the 3 modes of operation so it becomes easier to drop them into other sketches. Further, my goal is to add some functionality to the base and nodes such that they pull a unique ID from the base and store it in their EEPROM thus facilitating knowing which node is sending data.

I am intending to create some sensor nodes and simply plug in the radio support when I had a sensor doing what I want it to do so that the information would be passed to a base and recorded.

i found that my connection problem was coming from powering rf24 with 3.3v.
with 5v power connection is good:)

but now problem is that relay hangs about couple of hours...is there a solution for that?