Jeremy McGinnis
Published © GPL3+

Building a Sensor Network for an 18th Century Gristmill

Monitoring 100 year old factory processes are hard, but it gets easier, safer and more reliable with a network of nRF24L01 RF transmitters.

AdvancedShowcase (no instructions)10 hours8,526

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
nRF24 Module (Generic)
×1
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
LED (generic)
LED (generic)
×2
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×1
DHT11 Temperature & Humidity Sensor (4 pins)
DHT11 Temperature & Humidity Sensor (4 pins)
×1
Capacitor 10 µF
Capacitor 10 µF
×1
MagiDeal 10pcs Female MICRO USB to DIP 5-Pin Pinboard 2.54mm micro USB type
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
3D Printer (generic)
3D Printer (generic)
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Custom parts and enclosures

RF24 Node Case

This is the case for my RF24 Network board

RF24 Node Lid

This is the lid for my RF24 Network board

Schematics

Network Board Version 1

This is the gerber file for my RF24 Network node

Network Board Version 2 (not the board the project is based on)

This is the gerber file for version 2 of the network board. This is not the board the project is based on, but it is the better of the two versions.

Code

Network Base Code

Arduino
Use this as the base code (address 00) for your RF24 Network project
//RF24 Network Base Code Template
//Created by totalJTM
//RF24Network library by TMRH20

#include <RF24Network.h>
#include <RF24Network_config.h>
#include <Sync.h>
#include <RF24.h>
#include <SPI.h>

const String versionStr = "~Version 1.0 9/11/2018";
const uint16_t this_node = 00;

const int redLed = 2, greenLed = 4;

RF24 radio(7, 8);                   // nRF24L01(+) radio attached using 7 as CE and 8 as CSN

RF24Network network(radio);

struct n_message {                  //n_message structure
  float voltageReading;             //add more variables or change the datatype to whatever suits your project
  float data1;
  //bool data2;                     //example of another variable, just remove the "//"
};
struct b_message {                  //b_message structure
  char message[8];               //this array is here so we can have multiple types of formatted messages
};

void setup() {
  Serial.begin(115200);
  Serial.println(versionStr);

  SPI.begin();
  radio.begin();
  network.begin(89, this_node);
}

unsigned long lastSent = 0;
void loop() {
  network.update();

  while (network.available()) {
    RF24NetworkHeader fromheader;        // If so, grab it and print it out
    network.peek(fromheader);
    if (fromheader.type == 'n') {
      n_message receivedMessage;
      network.read(fromheader, &receivedMessage, sizeof(receivedMessage));
      String msg = "";
      msg += fromheader.from_node;
      msg += ":";
      msg += receivedMessage.voltageReading;
      msg += ":";
      msg += receivedMessage.data1;
      Serial.println(msg);
    }
  }
  if (Serial.available()) {
    //node id|message
    String input = Serial.readString();
    RF24NetworkHeader toheader;
    b_message messageToSend;
    String toRaw = "";
    bool messageB = false;
    byte messageStart = 0;
    char messageRaw[8];
    for (int i = 0; i < input.length(); i++) {
      if (input.charAt(i) == '|') {
        messageB = true;
        messageStart = i + 1;
      }
      else if (messageB == true)
        messageRaw[i - messageStart] = input.charAt(i);
      else
        toRaw += input.charAt(i);
    }
    for (int i = 0; i < 8; i++)
      messageToSend.message[i] = messageRaw[i];
    toheader.to_node = toRaw.toInt();
    toheader.type = 'b';

    if (network.write(toheader, &messageToSend, sizeof(messageToSend))) {             //try to send message
      Serial.println("message sent");                                                 //if message succeeds to send, print "message sent" and turn on green led on circuit board
      digitalWrite(greenLed, HIGH);
      digitalWrite(redLed, LOW);
    } else {
      Serial.println("message failed");                                               //if message fails to send, print "message failed" and turn on red led on circuit board
      digitalWrite(greenLed, LOW);
      digitalWrite(redLed, HIGH);
    }
  }
}

Network Node Code

Arduino
This is the code you would run on an RF24 Node
//RF24 Network Node Code Template
//Created by totalJTM
//RF24Network library by TMRH20

#include <RF24Network.h>
#include <RF24Network_config.h>
#include <Sync.h>
#include <RF24.h>
#include <SPI.h>
#include <EEPROM.h>

const String versionStr = "Version 1.0 9/11/2018";
/****************** User Config ***************************/
uint16_t this_node = 01; //this is the address of this node CHANGE FOR EACH NODE
float data_delay = 5.0; //this is the delay interval between when messages are sent
/**********************************************************/

const int redLed = 2, greenLed = 4;

RF24 radio(7, 8);                   // nRF24L01(+) radio attached using Getting Started board

RF24Network network(radio);         //Declaring rfnetwork as network

struct n_message {                  //n_message structure
  float voltageReading;             //add more variables or change the datatype to whatever suits your project
  float data1;
  //bool data2;                     //example of another variable, just remove the "//"
};
struct b_message {                  //b_message structure
  char message[8];               //this array is here so we can have multiple types of formatted messages
};

//void(* resetArduino) (void) = 0; //function to reset arduino

void onReceiveMessage(char message[8]) {   //when a message is received, this function is called.

  if (message[0] == 'e' and message[1] == 'g') {                      //a message starting with "eg" triggers this statement
    //action for receiving this message
  }
}

n_message sendMessageActions(n_message nmessage) {
  nmessage.data1 = 50.0;
  return nmessage;
}

void setup() {
  Serial.begin(115200);
  Serial.println(versionStr);

  SPI.begin();                      //Starting SPI
  radio.begin();                    //Start Radio
  network.begin(89, this_node);     //start network (default channel 89, you can adjust it to whatever but a node can only communicate with nodes on its channel)
}
unsigned long lastSent = 0;
void loop() {
  network.update();                 //update node with network traffic

  while (network.available()) {     //if there is a message,
    RF24NetworkHeader fromheader;
    network.peek(fromheader);       //peek at the message's header
    if (fromheader.type == 'b') {   //if it is a b_message (b messages are of type b)
      b_message receivedMessage;
      network.read(fromheader, &receivedMessage, sizeof(receivedMessage));            //read message
      String printMessage = ""; printMessage += "~Message Received: "; printMessage += receivedMessage.message;
      printMessage += " -From: "; printMessage += fromheader.from_node;
      Serial.println(printMessage);  //print message to arduino serial
      onReceiveMessage(receivedMessage.message);                                              //call onReceiveMessage function to do user functions
    }
  }

  if ((millis() - lastSent) > (data_delay * 1000))                                    //countdown to next message transmission
  {
    Serial.println("sending");
    RF24NetworkHeader toheader;                                                       //create a new message header
    n_message messageToSend;                                                          //create a new message
    messageToSend = sendMessageActions(messageToSend);                                //rewrites message with user defined sensor code (can be edited in sendMessageActions function)
    toheader.to_node = 00;                                                            //message told to be directed at node 00 as a final destination (base node)
    messageToSend.voltageReading = readBatteryVoltage();                              //read battery voltage
    Serial.println(messageToSend.voltageReading);
    toheader.type = 'n';                                                              //set message type to an n_message

    if (network.write(toheader, &messageToSend, sizeof(messageToSend))) {             //try to send message
      Serial.println("message sent");                                                 //if message succeeds to send, print "message sent" and turn on green led on circuit board
      digitalWrite(greenLed, HIGH);
      digitalWrite(redLed, LOW);
    } else {
      Serial.println("message failed");                                               //if message fails to send, print "message failed" and turn on red led on circuit board
      digitalWrite(greenLed, LOW);
      digitalWrite(redLed, HIGH);
    }
    lastSent = millis();            //update lastSent variable with current time to start over the countdown
  }
  //if(millis() < 86400000)           //reset arduino after 24 hours
  //resetArduino();
}


float readBatteryVoltage() {        //code to read the voltage of the power source to the node
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
  delay(2);
  ADCSRA |= _BV(ADSC);
  while (bit_is_set(ADCSRA, ADSC));
  uint8_t low  = ADCL;
  uint8_t high = ADCH;
  long result = (high << 8) | low;
  result = 1125300L / result;
  return (((float)(result)) / 1000.0);
}

Personal Network Sensor Code

Arduino
This is the code I wrote and uploaded to all of my network nodes. Use this code as an example but build upon the other arduino code Ive provided.
//CVMill Network Program
//Created by totalJTM
//RF24Network library by TMRH20

#include <dht.h>
#include <RF24Network.h>
#include <RF24Network_config.h>
#include <Sync.h>
#include <RF24.h>
#include <SPI.h>

const String versionStr = "Version 1.1 8/20/2018";
/****************** User Config ***************************/
const uint16_t this_node = 03;
float data_delay = 2.0;

const int sensorType[3] = {0, 0, 0}; //1-Ultra,2-Temp,3-Pressure,4-Valve,5-DHT Humidity,6-DHT Temp, 7-mill power
const int sensorPin[3] = {A0, A1, A2}; //A0,A1,A2 or 14,15,16
/**********************************************************/

int messagesSent = 0, fail = 0, success = 0;
const int redLed = 2, greenLed = 4;
float dhtTempHold = 0.0;

RF24 radio(7, 8);                   // nRF24L01(+) radio attached using Getting Started board

RF24Network network(radio);

struct s_message {
  float voltageReading;
  float data1;
  float data2;
  float data3;
};
struct BasePacket
{
  uint8_t message[8];
};
struct stat_message {
  float voltageReading;
  int messagesSent;
  int fail;
  int success;
};

s_message myPacket;

void onReceiveMessage(char message[8]) {   //when a message is received, this function is called.

  if (message[0] == 'c' and message[1] == 'a' and message[2] == 'd') { //a message has to start with "cad" to change this nodes address
    String newAddress = "";
    for (int i = 3; i < 8; i++)               //parses through message (excluding spaces)
      if (!message[i] == ' ')
        newAddress += message[i];
    Serial.println(newAddress);
    //this_node = newAddress.toInt();              //updates this arduinos rfnetwork address
    //Serial.println(this_node);
    //EEPROM.write(1, this_node);               //saves new address to eeprom
    //resetArduino();                           //resets arduino so its address is changed to new address (not needed in most situations)
    Serial.println("Address Changed");
  }

  if (message[0] == 's' and message[1] == 't') {
    Serial.println("sending");
    RF24NetworkHeader toheader;                                                       //create a new message header
    stat_message messageToSend;                                                          //create a new message
    messageToSend.messagesSent = messagesSent;                               //rewrites message with user defined sensor code (can be edited in sendMessageActions function)
    messageToSend.fail = fail;
    messageToSend.success = success;
    toheader.to_node = 00;                                                            //message told to be directed at node 00 as a final destination (base node)
    messageToSend.voltageReading = readBatteryVoltage();                              //read battery voltage
    Serial.println(messageToSend.voltageReading);
    toheader.type = 't';                                                              //set message type to an n_message
    messagesSent++;
    if (network.write(toheader, &messageToSend, sizeof(messageToSend))) {             //try to send message
      Serial.println("message sent");                                                 //if message succeeds to send, print "message sent" and turn on green led on circuit board
      digitalWrite(greenLed, HIGH);
      digitalWrite(redLed, LOW);
      success++;
    } else {
      Serial.println("message failed");                                               //if message fails to send, print "message failed" and turn on red led on circuit board
      digitalWrite(greenLed, LOW);
      digitalWrite(redLed, HIGH);
      fail++;
    }
  }

  if (message[0] == 'e' and message[1] == 'g') {                      //a message starting with "eg" triggers this statement
    //action for receiving this message
  }
}

float sensorCommand(int st, int pin) {
  if (st == 1) { //ultrasonic
    const int trigPin = 5;
    float combined = 0;
    pinMode(trigPin, OUTPUT);
    pinMode(pin, INPUT);
    int j = 0;
    for (int i = 0; i < 3; i++) {
      long duration;
      float distance;
      digitalWrite(trigPin, LOW);  // Added this line
      delayMicroseconds(5); // Added this line
      digitalWrite(trigPin, HIGH);
      delayMicroseconds(10); // Added this line
      digitalWrite(trigPin, LOW);
      duration = pulseIn(pin, HIGH);
      distance = ((float)duration / 2) / 74;
      if(distance <= 200 && distance >= 0)
        combined += distance;
      else{
        i -= 1;
        j += 1;
      }
      if(j > 4)
        return -99.0;
      Serial.println(distance);
      delay(100);
    }
    Serial.print("_");
    Serial.println(combined / 3);
    return (combined / 3);
  }
  if (st == 2) { //temp
    pinMode(pin, INPUT);
  }
  if (st == 3) { //Pressure
    pinMode(pin, INPUT);

  }
  if (st == 4) { //Valve
    pinMode(pin, INPUT);
  }
  if (st == 5) {//DHTS Humidity
    pinMode(pin, INPUT);
    dht DHT;
    int chk = DHT.read11(pin);
    float d = float(DHT.humidity);
    dhtTempHold = DHT.temperature;
    Serial.println(d);
    return d;
  }
  if (st == 6) {//DHTS Temperature
    float d = dhtTempHold;
    Serial.println((1.8 * d) + 32.0);
    return (1.8 * d) + 32.0;
  }
  if (st == 7) {//mill power
    pinMode(pin, INPUT);
    int mVoltage = analogRead(pin);
    Serial.println(mVoltage);
    float voltage = (mVoltage * (5.0/1024.0));
    Serial.println(voltage);
    return voltage;
  }
  if (st == 0) {//Standard
    return 0.00;
  }
}

void getSensorData() {
  for (int i = 0; i < 3; i++) {
    if (i == 0)
      myPacket.data1 = sensorCommand(sensorType[0], sensorPin[0]);
    if (i == 1)
      myPacket.data2 = sensorCommand(sensorType[1], sensorPin[1]);
    if (i == 2)
      myPacket.data3 = sensorCommand(sensorType[2], sensorPin[2]);
  }
}

float readBatteryVoltage() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return ((float)(result)/1000.0); // Vcc in millivolts
}

void setup() {
  Serial.begin(115200);
  Serial.println(versionStr);

  SPI.begin();
  radio.begin();
  network.begin(89, this_node);
  getSensorData();

}
unsigned long lastSent = 0;
void loop() {

  network.update();

  while (network.available()) {
    RF24NetworkHeader header;        // If so, grab it and print it out
    network.peek(header);
    if (header.type == 'b') {
      BasePacket bMessage;
      network.read(header, &bMessage, sizeof(bMessage));
      onReceiveMessage(bMessage.message);
    }
  }

  if ((millis() - lastSent) > (data_delay * 1000))
  {
    Serial.println("sending");
    RF24NetworkHeader header;
    header.to_node = 00;
    myPacket.voltageReading = readBatteryVoltage();
    Serial.println(myPacket.voltageReading);
    getSensorData();
    header.type = 's';
    
    messagesSent++;
    if (network.write(header, &myPacket, sizeof(myPacket))) {
      Serial.println("message sent");
      digitalWrite(greenLed, HIGH);
      digitalWrite(redLed, LOW);
      success++;
    } else {
      Serial.println("message failed");
      digitalWrite(greenLed, LOW);
      digitalWrite(redLed, HIGH);
      fail++;
    }
    lastSent = millis();
  }
}

Python Server Program

Python
This is the program to run on the RPI as a server for all the network messages
No preview (download only).

Credits

Jeremy McGinnis

Jeremy McGinnis

5 projects • 17 followers
I am a 24 year old Electrical Engineering student with two 3d printers, a cnc router and a lot of free time on my hands.

Comments