Go Down

Topic: Please review my RF24 IoT network (Read 8 times) previous topic - next topic

JohnHoward

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.

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.

JohnHoward

#17
Sep 27, 2013, 05:24 am Last Edit: Sep 27, 2013, 01:20 pm by JohnHoward Reason: 1
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.

JohnHoward

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

Code: [Select]

      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.

transfinite

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.

Code: [Select]

#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

JohnHoward

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

Code: [Select]

#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.

transfinite

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:

Code: [Select]

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

JohnHoward

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.

transfinite

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:

Code: [Select]
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

JohnHoward

#24
Oct 02, 2013, 02:05 am Last Edit: Oct 02, 2013, 02:18 am by JohnHoward Reason: 1
I resolved my angst over global HEADER declarations!  :D

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:

Code: [Select]

#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;
}

JohnHoward

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

Quote

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.

justind000

#26
Oct 03, 2013, 06:05 pm Last Edit: Oct 03, 2013, 06:12 pm by justind000 Reason: 1
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).
https://github.com/justind000/nRF-IoT

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.

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


JohnHoward


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.

mortenx

#29
Oct 19, 2013, 01:08 am Last Edit: Oct 19, 2013, 01:45 am by mortenx Reason: 1
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

Go Up