User Tools

Site Tools


digispark:tutorials:nrfmesh

nRF Mesh Network Example

Requirements:

3x or more Digispark Pros 3x or more Pro nRF Shields (may also work with original Digisparks wired to nRF modules)

Theory:

This example implements a simple, easy to understand and change, Mesh network layer over the RF24 library. The goal of this is to be lightweight for relatively small meshes, and relatively easy to set up. While a more complex mesh layer might allow for ad hoc additions to the network - for simplicity this requires the total number of nodes (or max number at least - they don't all have to be on) to be known. That is all - addressing is automatic as long as you change the NODE_ADDRESS to be unique for each node.

Note: NODE_ADDRESS is 0 indexed so your first node is 0 NOT 1

Data Structure

For simplicity this implements a data structure in the RF24 payload. That structure is defined in the example as:

struct 
{
 char toAddress;
 char fromAddress;
 char fromSequence;
 long data;
} msg;

So each message contains the address of its final destination, the address of who sent it, the sequence number of the sender (more on this later), and the data itself (set to be a long).

You can change the type or length of the data - just make sure you do so on all nodes.

Repeating without looping forever

This mesh layer expects any node the receives a message (they all listen for any message by listening all on the same channel) to re-transmit the message if that node was not the node in the toAddress. In order to ensure that a node doesn't keep re-transmitting a message endlessly we could use some sort of ACK system (complex) or more simply (but less robust) we can record the last message we repeated based on the from address and not repeat it again - which is what we do here.

Each node stores an array of sequence numbers - one for each node - when it repeats a number it sets the sequence for that node to the sequence of that message and does not repeat again any message from the same node with a sequence number lower than that. This ensures that it only repeats a message once and also doesn't repeat a message older than the last one it repeated from a given node.

This results in as many duplicates of the message as there are nodes if the nodes are all close to each other (each message would be duplicated once by each node) - but if they are say in a line where each node is just in range of the one in front and behind it then we end up with no more than are necessary to get to the destination.

This mesh layer also implements a broadcast address at 255 so if you do sendToMesh(255, analogRead(A5)); all nodes that receive the message will repeat it and read it! (This of a mesh of motion detectors with alarms - if one detects motion it sends a message on broadcast to all nodes to turn on their alarms).

The Mesh Functions:

These two main functions are well commented and cover most of how this works on the code level:

void sendToMesh(uint8_t toAddress, long data){
  if(sequence[NODE_ADDRESS]<255)
    sequence[NODE_ADDRESS]++; //increment sequence count for this device
  else
    sequence[NODE_ADDRESS] = 0; //set to zero if last was 255 so we don't overflow - logic for read is built to handle this too
 
  msg.toAddress = toAddress; //set the address of the destination node
  msg.fromAddress = NODE_ADDRESS; //set the from as this node - of course
  msg.fromSequence = sequence[NODE_ADDRESS]; //set it to the sequence number we just implemented which should be greater than any the nodes have received 
  radio.stopListening(); //turn of recv so we can transmit
  radio.write(&msg, sizeof(msg));
  radio.startListening(); //turn recv back on
}
bool readAndRepeat(){
  if(radio.read(&msg, sizeof(msg))){ //if we have incoming data
    if(msg.fromAddress!=NODE_ADDRESS){ //make sure this node didn't send it
      if(sequence[msg.fromAddress] < msg.fromSequence || (sequence[msg.fromAddress] == 255 && msg.fromSequence == 0)){ //make sure we haven't already repeated it or received it
          //increment sequence for that address so we don't repeat it twice from this node or receive it twice
          sequence[msg.fromAddress] = msg.fromSequence;
          if(msg.toAddress==NODE_ADDRESS){ //is it for this node? if so return true so we can use it!
            return true;
          }
          //otherwise repeat it - send it back out
          radio.write(&msg, sizeof(msg));
          if(msg.toAddress == 255){ //it was a broadcast so return true so we do something with it
            return true;
          }
      }
    }
  }
  return false;
}

Topography:

This mesh layer can be used with many different topographies - such as:

Master and Nodes - One Node is the master connected to a screen/computer/etc (think of it as the server) the rest are just nodes sending data back to the master and repeating each others data so that it can get to the master. Example: whole house temperature monitors with the master connected to the internet. This is the most common setup and what our example below uses.

Ad hoc Nodes - Each node reports different things addressed to different nodes - say you have node A, B and C. Node A reports the temperature to C. B reports the voltage it sees to C and A and C reports its switch input state to B. Example: Machines in a factory where they need to react to what other machines are doing/reading, but each differently to different values. This is less common but really neat!

Multi master - multi node: A mix of the two - say you have A,B,C, and D - maybe all report their temperature to A including D, but they all (including A) report their voltage to D.

Obviously the mix of topographies is endless - this should support most of them because it does not apply any limits to topography (at the expense of optimization)

Generic Example Code:

Note: This is generic example code that must be edited to work with your setup and desired function - see the example below this for a guided working example.

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
 
//THESE MUST BE SET! CHANGE ADDRESS AS YOU UPLOAD TO EACH NODE!
#define TOTAL_NODES 3 //TOTAL NUMBER OF NODES IN MESH
#define NODE_ADDRESS 0 //the sero indexed address of this node - nodes must be numbered 0 thru TOTAL_NODES-1 //also note 255 is broadcast address
 
//hold the seuqnce numbers to avoid repeating the same messages
uint8_t  sequence[TOTAL_NODES];
 
//setup radio module
RF24 radio(9,12);
 
//message format - adjust data as needed  - the rest is madatory for this mesh to work
struct 
{
 char toAddress;
 char fromAddress;
 char fromSequence;
 long data;
} msg;
 
//don't change! read is same for all nodes, write is automatically calculated here
const uint64_t writePipe = 0xF0F0F0F00LL + NODE_ADDRESS;
const uint64_t readPipe = 0xF0F0F0F0D2LL;
 
 
void setup() {
  // put your setup code here, to run once:
  radio.begin();
  radio.setDataRate(RF24_250KBPS); //lowest speed = most range
  radio.setAutoAck(false); //this is a mesh so we don't want ACKs!
  radio.setRetries(15, 15);
  radio.setPayloadSize(sizeof(msg));
  radio.openWritingPipe(writePipe);
  radio.openReadingPipe(1, readPipe);
  radio.startListening();
}
 
long now = 0;
 
void loop() {
 
 
 
  if(readAndRepeat()){ //will repeat messages as needed and return false unless there is packet for this node - CALL FREQUENTLY!
    //if this does not return false then we have a packet for THIS node!
    //msg.fromAddress is the node that sent it
    //msg.data is the data itself
    //Do something with it!
    //For example display packets coming to this node on a LCD:
    /*
    NOTE: TO USE THIS ADD THE LCD INCLUDES AND SETUP ROUTINE FROM THE DigisparkLCD example  
    lcd.clear();
    lcd.print("From: ");
    lcd.println(msg.fromAddress);
    lcd.print("Value: ");
    lcd.println(msg.data);
    */
  }
 
  if(millis() - now > 10000){ //send a packet from this node to the mesh every 10 seconds but wait in a non-blocking way so that we can still run this loop and repeat things
    now = millis(); //set now to millis so we wait another 10 seconds before sending again
    //sendToMesh(To_Address,Data_To_Send);
    //sendToMesh(0, analogRead(A5)); //send to node 0 the analog read value of pin 5 - could also send a temp sensor value, etc ,etc 
    //sendToMesh(255, analogRead(A5)); //send to all nodes (255 is the broadcast address) the analog read value of pin 5
  }
 
}
 
 
void sendToMesh(uint8_t toAddress, long data){
  if(sequence[NODE_ADDRESS]<255)
    sequence[NODE_ADDRESS]++; //increment sequence count for this device
  else
    sequence[NODE_ADDRESS] = 0; //set to zero if last was 255 so we don't overflow - logic for read is built to handle this too
 
  msg.toAddress = toAddress; //set the address of the destination node
  msg.fromAddress = NODE_ADDRESS; //set the from as this node - of course
  msg.fromSequence = sequence[NODE_ADDRESS]; //set it to the sequence number we just implemented which should be greater than any the nodes have received 
  radio.stopListening(); //turn of recv so we can transmit
  radio.write(&msg, sizeof(msg));
  radio.startListening(); //turn recv back on
}
 
bool readAndRepeat(){
  if(radio.read(&msg, sizeof(msg))){ //if we have incoming data
    if(msg.fromAddress!=NODE_ADDRESS){ //make sure this node didn't send it
      if(sequence[msg.fromAddress] < msg.fromSequence || (sequence[msg.fromAddress] == 255 && msg.fromSequence == 0)){ //make sure we haven't already repeated it or received it
          //increment sequence for that address so we don't repeat it twice from this node or receive it twice
          sequence[msg.fromAddress] = msg.fromSequence;
          if(msg.toAddress==NODE_ADDRESS){ //is it for this node? if so return true so we can use it!
            return true;
          }
          //otherwise repeat it - send it back out
          radio.write(&msg, sizeof(msg));
          if(msg.toAddress == 255){ //it was a broadcast so return true so we do something with it
            return true;
          }
      }
    }
  }
  return false;
}

Master and Nodes Example:

This example uses 3 Nodes - We will call them Node 0, 1, and 2 - Node 0 is the master and should be connected to a computer - it will use the USB keyboard library to type out any messages it receives.

Node 1 and 2 send messages every 10 seconds of the analog Read value of their Pin 5 - they repeat messages for each other - that is all they do. They do not need to be attached to a computer once programmed - just powered on with at least one in range of the master.

Put this code on Node 0 which will be the master:

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
 
//THESE MUST BE SET! CHANGE ADDRESS AS YOU UPLOAD TO EACH NODE!
#define TOTAL_NODES 3 //TOTAL NUMBER OF NODES IN MESH
#define NODE_ADDRESS 0 //the sero indexed address of this node - nodes must be numbered 0 thru TOTAL_NODES-1 //also note 255 is broadcast address
 
//hold the seuqnce numbers to avoid repeating the same messages
uint8_t  sequence[TOTAL_NODES];
 
//setup radio module
RF24 radio(9,12);
 
//message format - adjust data as needed  - the rest is madatory for this mesh to work
struct 
{
 char toAddress;
 char fromAddress;
 char fromSequence;
 long data;
} msg;
 
//don't change! read is same for all nodes, write is automatically calculated here
const uint64_t writePipe = 0xF0F0F0F00LL + NODE_ADDRESS;
const uint64_t readPipe = 0xF0F0F0F0D2LL;
 
 
void setup() {
  // put your setup code here, to run once:
  radio.begin();
  radio.setDataRate(RF24_250KBPS); //lowest speed = most range
  radio.setAutoAck(false); //this is a mesh so we don't want ACKs!
  radio.setRetries(15, 15);
  radio.setPayloadSize(sizeof(msg));
  radio.openWritingPipe(writePipe);
  radio.openReadingPipe(1, readPipe);
  radio.startListening();
}
 
long now = 0;
 
void loop() {
 
 
 
  if(readAndRepeat()){ //will repeat messages as needed and return false unless there is packet for this node - CALL FREQUENTLY!
    //if this does not return false then we have a packet for THIS node!
    //msg.fromAddress is the node that sent it
    //msg.data is the data itself
    //Do something with it!
    //For example type out the packets coming to this node
    DigiKeyboard.println(msg.fromAddress);
    DigiKeyboard.println(msg.data);
 
  }
 
  /
  /*
  We don't do any sending because this node is the master and just receives things!
   if(millis() - now > 10000){ //send a packet from this node to the mesh every 10 seconds but wait in a non-blocking   way so that we can still run this loop and repeat things
    now = millis(); //set now to millis so we wait another 10 seconds before sending again
    //sendToMesh(To_Address,Data_To_Send);
    //sendToMesh(0, analogRead(A5)); //send to node 0 the analog read value of pin 5 - could also send a temp sensor value, etc ,etc 
    //sendToMesh(255, analogRead(A5)); //send to all nodes (255 is the broadcast address) the analog read value of pin 5
  }
  */
 
  DigiKeyboard.delay(100);//give the usb some time to poll
 
}
 
 
void sendToMesh(uint8_t toAddress, long data){
  if(sequence[NODE_ADDRESS]<255)
    sequence[NODE_ADDRESS]++; //increment sequence count for this device
  else
    sequence[NODE_ADDRESS] = 0; //set to zero if last was 255 so we don't overflow - logic for read is built to handle this too
 
  msg.toAddress = toAddress; //set the address of the destination node
  msg.fromAddress = NODE_ADDRESS; //set the from as this node - of course
  msg.fromSequence = sequence[NODE_ADDRESS]; //set it to the sequence number we just implemented which should be greater than any the nodes have received 
  radio.stopListening(); //turn of recv so we can transmit
  radio.write(&msg, sizeof(msg));
  radio.startListening(); //turn recv back on
}
 
bool readAndRepeat(){
  if(radio.read(&msg, sizeof(msg))){ //if we have incoming data
    if(msg.fromAddress!=NODE_ADDRESS){ //make sure this node didn't send it
      if(sequence[msg.fromAddress] < msg.fromSequence || (sequence[msg.fromAddress] == 255 && msg.fromSequence == 0)){ //make sure we haven't already repeated it or received it
          //increment sequence for that address so we don't repeat it twice from this node or receive it twice
          sequence[msg.fromAddress] = msg.fromSequence;
          if(msg.toAddress==NODE_ADDRESS){ //is it for this node? if so return true so we can use it!
            return true;
          }
          //otherwise repeat it - send it back out
          radio.write(&msg, sizeof(msg));
          if(msg.toAddress == 255){ //it was a broadcast so return true so we do something with it
            return true;
          }
      }
    }
  }
  return false;
}

Nodes 1 and 2: These are the sending nodes. They send the analog value of Pin 5. Load this on to the other two Nodes changing NODE_ADDRESS to 2 for the 3rd node (which is node #2 because the addresses are zero indexed)

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
 
//THESE MUST BE SET! CHANGE ADDRESS AS YOU UPLOAD TO EACH NODE!
#define TOTAL_NODES 3 //TOTAL NUMBER OF NODES IN MESH
#define NODE_ADDRESS 1 //the sero indexed address of this node - nodes must be numbered 0 thru TOTAL_NODES-1 //also note 255 is broadcast address
 
//hold the seuqnce numbers to avoid repeating the same messages
uint8_t  sequence[TOTAL_NODES];
 
//setup radio module
RF24 radio(9,12);
 
//message format - adjust data as needed  - the rest is madatory for this mesh to work
struct 
{
 char toAddress;
 char fromAddress;
 char fromSequence;
 long data;
} msg;
 
//don't change! read is same for all nodes, write is automatically calculated here
const uint64_t writePipe = 0xF0F0F0F00LL + NODE_ADDRESS;
const uint64_t readPipe = 0xF0F0F0F0D2LL;
 
 
void setup() {
  // put your setup code here, to run once:
  radio.begin();
  radio.setDataRate(RF24_250KBPS); //lowest speed = most range
  radio.setAutoAck(false); //this is a mesh so we don't want ACKs!
  radio.setRetries(15, 15);
  radio.setPayloadSize(sizeof(msg));
  radio.openWritingPipe(writePipe);
  radio.openReadingPipe(1, readPipe);
  radio.startListening();
}
 
long now = 0;
 
void loop() {
 
 
 
  if(readAndRepeat()){ //will repeat messages as needed and return false unless there is packet for this node - CALL FREQUENTLY!
    //if this does not return false then we have a packet for THIS node!
    //msg.fromAddress is the node that sent it
    //msg.data is the data itself
    /*
       These nodes just send data so we don't do anything here for this example
       we still need to call readAndRepeat though because we still need to repeat 
       packets for other nodes!
    */
  }
 
  if(millis() - now > 10000){ //send a packet from this node to the mesh every 10 seconds but wait in a non-blocking way so that we can still run this loop and repeat things
    now = millis(); //set now to millis so we wait another 10 seconds before sending again
    //sendToMesh(To_Address,Data_To_Send);
    sendToMesh(0, analogRead(A5)); //send to node 0 the analog read value of pin 5 - could also send a temp sensor value, etc ,etc 
  }
 
}
 
 
void sendToMesh(uint8_t toAddress, long data){
  if(sequence[NODE_ADDRESS]<255)
    sequence[NODE_ADDRESS]++; //increment sequence count for this device
  else
    sequence[NODE_ADDRESS] = 0; //set to zero if last was 255 so we don't overflow - logic for read is built to handle this too
 
  msg.toAddress = toAddress; //set the address of the destination node
  msg.fromAddress = NODE_ADDRESS; //set the from as this node - of course
  msg.fromSequence = sequence[NODE_ADDRESS]; //set it to the sequence number we just implemented which should be greater than any the nodes have received 
  radio.stopListening(); //turn of recv so we can transmit
  radio.write(&msg, sizeof(msg));
  radio.startListening(); //turn recv back on
}
 
bool readAndRepeat(){
  if(radio.read(&msg, sizeof(msg))){ //if we have incoming data
    if(msg.fromAddress!=NODE_ADDRESS){ //make sure this node didn't send it
      if(sequence[msg.fromAddress] < msg.fromSequence || (sequence[msg.fromAddress] == 255 && msg.fromSequence == 0)){ //make sure we haven't already repeated it or received it
          //increment sequence for that address so we don't repeat it twice from this node or receive it twice
          sequence[msg.fromAddress] = msg.fromSequence;
          if(msg.toAddress==NODE_ADDRESS){ //is it for this node? if so return true so we can use it!
            return true;
          }
          //otherwise repeat it - send it back out
          radio.write(&msg, sizeof(msg));
          if(msg.toAddress == 255){ //it was a broadcast so return true so we do something with it
            return true;
          }
      }
    }
  }
  return false;
}
digispark/tutorials/nrfmesh.txt · Last modified: 2016/06/09 12:03 (external edit)