Open Arduino

Enter Your Project for a chance to win a grand prize for the most innovative use of Arduino or a $200 shopping cart! The Birthday Special: Arduino Projects for Arduino Day!

Back to The Project14 homepage

Project14 Home
Monthly Themes
Monthly Theme Poll

 

This project was initially part of the Forget Me Not Design Challenge.

Eric did some excellent work using RFM69 transceivers.

He also shared the work and I was able to make use of it for part of my own Application.

 

You can follow his work here.

Wireless Sensor Node and Gateway Design Details

 

 

 

At a later date I was asked about doing an article for TheShed magazine.

The publisher wanted a remote display of the glasshouse temperature, and it seemed that I could adapt this to show the concept.

 

Many of the readers wouldn't have the required OpenHAB display we were using in the Design Challenge, and therefore a receiver and some form of display was necessary.

 

 

 

Transmitter

Eric had demonstrated the RFM69xx transceivers but they needed a microcontroller board to host them.

 

When I was looking at where to buy the RFM69's I came across the Montenio board that was made by LowPowerLab

https://lowpowerlab.com/guide/moteino/#whatisit

Close

          image from LowPowerLab showing the raw unpopulated boards.

 

The price of the competed item was below what I could get the raw parts for.

The documentation and support also seemed to be there (unlike many other sites just selling the product), so it made sense to support someone else.

 

 

Close

The big advantage of these is the ability to solder the Transceiver module directly to the board, rather than wires and some mounting method.

 

The DS18B20 was attached to D7, and a 4k7 resistor soldered between the pin and Vcc.

These pictures shows another board configured with the sensor and resistor.

 

In order to stop it getting damaged, I placed it inside a small container that I picked up somewhere.

The yellow wire is the antenna.

The 5v adaptor gets connected to the Vin and Gnd pins of the Moteino, and the 3v3 regulator happily runs the transceiver and the DS18B20 sensor.

 

 

 

 

 

 

 

 

 

 

Transmitter Code

I wanted to reduce the amount of data being sent.

This wasn't for battery consumption, more to reduce eNoise or unwanted RF noise radiating for no reason.

It seemed sensible to update the display whenever the temperature changed by 0.5 degs, or every 5 minutes.

 

You may wish to have a finer resolution, or increase the increase the time between transmissions.

Depending on the application the rate might be finer at very low or very high temperatures.

 

 /* 
  based on Eric Tsai 
  License: CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/ 
  
  Date: 11-07-2016 
  File: ShedRFTempSender.ino 
  This sketch is for a wired Moteino w/ RFM69 wireless transceiver 
  Sends sensor data (temp) back to gateway.
   
   
  
  **** connections *****
  Moteino Pin assignments
     Pin D0  Rx
     Pin D1  Tx
     Pin D2  RFM69 D100
     Pin D3  
     Pin D4  
     Pin D5  
     Pin D6  
     Pin D7  Temp sensor DS18B20 data pin with 4k7 external pullup resistor to 3v3
     Pin D8  
     Pin D9  
     Pin D10  RFM69 SS
     Pin D11  RFM69 MOSI
     Pin D12  RFM69 MISO
     Pin D13  RFM69 SCK
     
     Program as an UNO.
   
  */  
   
   /* To do list -----------------
  
  1. 
    
 
 */
 
    
    
  //RFM69  --------------------------------------------------------------------------------------------------  
  #include 
  #include 
  #include 
  
  
  #define NODEID      31    //unique for each node on same network  
  #define NETWORKID   100  //the same on all nodes that talk to each other  
  #define GATEWAYID   1 
  //Match frequency to the hardware version of the radio on your Moteino (uncomment one):  
  #define FREQUENCY   RF69_433MHZ  
  //#define FREQUENCY   RF69_868MHZ  
  //#define FREQUENCY     RF69_915MHZ  
  #define ENCRYPTKEY    "TheShed123456789" //exactly the same 16 characters/bytes on all nodes!  
  #define IS_RFM69HW    //uncomment only for RFM69HW! Leave out if you have RFM69W!  
  #define ACK_TIME      30 // max # of ms to wait for an ack  
  #define LED           9  // Moteinos have LEDs on D9  
  #define SERIAL_BAUD   9600  //must be 9600 for GPS, use whatever if no GPS  
    
  boolean debug = 0;       // set this to 1 if you want serial output for debugging. 
    
  //struct for wireless data transmission 
  
  typedef struct {    
    int             nodeID;          // node ID (1xx, 2xx, 3xx);  eg 1xx = basement, 2xx = main floor, 3xx = outside  
    boolean         Sign;            // true if negative temperature
    int             deviceID;        // sensor ID (2, 3, 4, 5)  
    float           Temp;            // Temperature data  
  } Payload; 
  Payload theData;
   
    
  char buff[20];  
  byte sendSize=0;  
  boolean requestACK = false;  
  RFM69 radio;  
    
  //end RFM69 ------------------------------------------  
  
  //inputs
  const int TempSensor = 7;          // Tempsensor name connected to pin7. (Don't forget the 4k7 pullup to 5v).
    
  // DS18B20 Temperature chip i/o
  OneWire ds(7);                     // TempSensor on pin 7
  byte i;
  byte present = 0;
  byte data[12];
  byte addr[8];
  word TReading;
  byte HighByte, LowByte;
  int SignBit;  
  long ReadStartTime;
  long ConvTime = 1000;              //Time for a conversion to take place at 9 bit resolution
  boolean ConvIssued = false;        //Signals a reading has been started 
  boolean Sign;
    
  //temperature / humidity  =====================================  
  float t = 0.00;  
  float tempValue_previous = 0.00;
    
  // timings since they are greater than 32,768 they need to be a 'long' or 'unsigned long' rather than an 'int'. 
  
  unsigned long temperature_interval = 4000;                // read the temp every 4 seconds but conversion adds 1 more second
  unsigned long last_temperature_time;
  unsigned long last_send_time;
  unsigned long temperature_send_interval = 300000;         // 5 *60 *1000
  
   
  
  
  
  void setup()  
  {  
    Serial.begin(9600);          //  setup serial 
    if (debug)
    {    
      Serial.println("Serial Up");
    }
    
    pinMode(TempSensor, INPUT);\
   
    Temp_init();                // Find the One Wire sensor
    Read_Temp();
    delay(500);
    // set the various temperatures
    tempValue_previous = t;
    
    //RFM69-------------------------------------------  
    radio.initialize(FREQUENCY,NODEID,NETWORKID);  
    #ifdef IS_RFM69HW  
      radio.setHighPower(); //uncomment only for RFM69HW!  
    #endif  
    radio.encrypt(ENCRYPTKEY);
    
    if (debug)
    {     
      char buff[50];  
      sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);  
      Serial.println(buff);
    }
  
    theData.nodeID = NODEID;  //this node id should be the same for all devices in this node  
    //end RFM--------------------------------------------  
    
    //initialize times  
    last_temperature_time = millis();
    if (debug)
    {       
      Serial.println("Starting now");
    }
  }  
    
  void loop()  
  {  
    
    if (millis() - last_temperature_time > temperature_interval) // reads 4 seconds
    {
      Read_Temp();
    }  
  
    /* The idea is to minimise transmissions
       Send every five minutes or if the temp changes by 0.5 deg. 
    */
  
    if (t > (tempValue_previous + 0.5)|| (t < (tempValue_previous - 0.5)))       
      {
        // Only start if the change is 0.5 or more. 
        Temp_Send();
        last_send_time = millis();
        tempValue_previous = t;
      }
  
    if (millis() - last_send_time > temperature_send_interval) // 5 mins
      {
        Temp_Send();
        last_send_time = millis();
        tempValue_previous = t; 
      }      
  
  
  } 
  
  
  void Read_Temp()
  {
   //reset and setup the temp sensor for communicating
      if (ConvIssued != true)
      {
        ds.reset();
        ds.select(addr);
        ds.write(0x44,1);             // start conversion
        ConvIssued = true;            // We have asked for data
        ReadStartTime = millis();     // note the time we asked for temperature (so we know when to come back)
      }
      if (ConvIssued && (millis() - ReadStartTime) >= ConvTime)
      {  
    
        /* This is the delay....The program should do the reset, and come back when the time is up.
  
         */
        present = ds.reset();
        ds.select(addr);    
        ds.write(0xBE);         // Read Scratchpad
      }
      else
      {
        return;      //either there was no conversion issued, or time is too soon.
      }
      
      for ( i = 0; i < 9; i++)                   // we need 9 bytes
      {
        data[i] = ds.read();
      }
      LowByte = data[0];
      HighByte = data[1];
      TReading = (HighByte << 8) + LowByte;
      SignBit = TReading & 0x8000;               // test most sig bit
      
      ConvIssued = false;                        // means we got a reading back
         
      if (SignBit)                               // negative
      {
        Sign = 1;
        TReading = (TReading ^ 0xffff) + 1;      // 2's comp
      }
      
      t = TReading *.0625;
      
      if (SignBit)                               //negative
      {
        t = (0 - t);
      }
        if (debug)
        {
          Serial.print("prev temp = ");
          Serial.println(tempValue_previous);
          Serial.print("cur temp  = ");
          Serial.println(t);
          Serial.print("time : ");
          Serial.println(millis());
        }
  
          last_temperature_time = millis(); 
  
          // Check if any reads failed and exit early (to try again).  
          if (isnan(t))
          {  
            if (debug) Serial.println("Failed to read from temp sensor!");
            tempValue_previous =0;  
            return;  
          } 
  }
  
  
  void Temp_Send()
  {
              theData.deviceID = 2; 
              theData.Sign = Sign; 
              theData.Temp = t;  
              radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData));         
              if (debug)
              {
                 Serial.print("Sign : ");
                 Serial.println(Sign);
                 Serial.print("actual Temp : ");
                 Serial.println(t);
                 Serial.println("SENDING temp");
                 Serial.print("temp = ");
                 Serial.println(theData.Temp);
                 Serial.print("time : ");
                 Serial.println(millis());
              } 
    
  }
  
  void Temp_init()      // Find the One Wire device
  {
    if ( !ds.search(addr)) 
    {
      if (debug)
      {
        Serial.print("No more addresses.\n");
      }
      ds.reset_search();
      delay(250);
      return;
    }
    if (debug) 
    {
      Serial.print("R=");
      for( i = 0; i < 8; i++) 
      {
        Serial.print(addr[i], HEX);
        Serial.print(" ");
      }
    
      if ( OneWire::crc8( addr, 7) != addr[7]) 
      {
          Serial.print("CRC is not valid!\n");
          return;
      }
    
      if ( addr[0] != 0x28) 
      {
          Serial.print("Device is not a DS18B20 family device.\n");
          return;
      }
    }
  }

 

In this use there is only one node and it sends the temperature, but you could also decide to send battery voltage, humidity or the sunlight.

The data is three pieces NodeID, Sign and the Temperature (Lines 258-260).

 

 

Receiver

The original had the receiver buried inside what looked like a book.

Close Close

The serial output was fed into the Raspberry Pi and presented in OpenHAB.

https://www.element14.com/community/servlet/JiveServlet/showImage/38-17489-207583/HotWater_1.png

eLDERmon Graphs

 

 

For this application I needed an LCD.

It's not the best photo but the glare from the overhead lights was killing other angles ...

 

 

Receiver Code

 

/*
Author:  Eric Tsai
License:  CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/
Date:  11-07-2016
File: ShedRFTempReceiver.ino
This sketch receives RFM wireless data and forwards it to the Serial port and LCD display

To Do:
1)  
2)
3)  
*/

/*
**** connections *****
Moteino Pin assignments
   Pin D0  Rx
   Pin D1  Tx
   Pin D2  RFM69 D100
   Pin D3  
   Pin D4  
   Pin D5  
   Pin D6  
   Pin D7  
   Pin D8  
   Pin D9  LED on moteino
   Pin D10  RFM69 SS
   Pin D11  RFM69 MOSI
   Pin D12  RFM69 MISO
   Pin D13  RFM69 SCK
   Pin A4   I2C Data
   Pin A5   I2C Clock
   
   Program as an UNO.
*/


//general --------------------------------
#include 
#include <liquidcrystal_i2c.h><liquidcrystal_i2c.h>
//#include  


//RFM69  ----------------------------------
#include 
#include 


#define NODEID        1    //unique for each node on same network
#define NETWORKID     100  //the same on all nodes that talk to each other
#define FREQUENCY   RF69_433MHZ 
//#define FREQUENCY     RF69_915MHZ
#define ENCRYPTKEY    "TheShed123456789" //exactly the same 16 characters/bytes on all nodes!
#define LED         9
//#define IS_RFM69HW    //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define ACK_TIME      30 // max # of ms to wait for an ack
#define SERIAL_BAUD   9600

RFM69 radio;
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network
boolean debug = 1;       // set this to 1 if you want serial output for debugging.
  
// MaxMin Temperature  -----------------------------------
  float TempReading = 99.99;                   //Temp reading as + or - figure
  float HighTemp;
  float LowTemp;
  
  //LCD Scrolling message
  byte i;
  char LCDMessage[8] ;               // 7 char (0-6) scrolling message plus a null.
  byte MessageStartPosition =0;
  byte MessageStringCount;
  byte LCDPosition =0;
  char MessageString[] = "Current Temperature ";      // message to be scrolled
  int ScrollDelay = 300;             // Delay for the char scrolling. (use 750 for White chars)
  long LastScroll =0;                // timer used in the delay 

  // General Purpose
  boolean FirstReading = true;       // Values assummed until reset by first reading
  int SignBit;                       // Used to signify a negative number

  // RFM69 data structure
  typedef struct {    
    int             nodeID;             //node ID (1xx, 2xx, 3xx);  eg 1xx = basement, 2xx = main floor, 3xx = outside  
    boolean         Sign;               // true if negative temperature
    int             sensorID;           // sensor ID (2, 3, 4, 5)  
    float           Temp;               // Temperature data
  } Payload; 
  Payload theData;

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup() 
{
  lcd.init();                        // initialize the lcd   
  Wire.begin();
  lcd.clear();
  lcd.noCursor();
  lcd.backlight();                 // Turn on the backlight
  // Print a message to the LCD.
  lcd.setCursor(0,0);                 // First position (0) and first line (0) 
  lcd.print (" Initialising");
  lcd.setCursor(0,1);                 // First position (0) and second line (1)
  lcd.print (" The Shed 2016");  
  LastScroll = millis();                  //check the time for the LCD message to be seen 
  delay(5000-((millis())- (LastScroll)));    // this should end up as 1.5 secs
  lcd.clear();
  LastScroll=0;
  LCDScrollingMessage();        // Load the 7 chars for the scrolling message
  
  Serial.begin(9600); 
    if (debug)
  {
    Serial.println("start");
  }
  

  //RFM69 ---------------------------
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  #ifdef IS_RFM69HW
    radio.setHighPower(); //uncomment only for RFM69HW!
  #endif
  radio.encrypt(ENCRYPTKEY);
  radio.promiscuous(promiscuousMode);
  if (debug)
  {
    char buff[50];
    sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
    Serial.println(buff);
  }

}  // end of setup

byte ackCount=0;


void loop() 
{
  //This is basically a number of sub routines, each with a purpose
  
    UpdateMaxMin();        // Update the Min and Max Temperature variables
    HighLowTemp();         // Display the max amd Min temps
    DisplayTemp();         // Display the current Temp
    DisplayMessage();      // Display the 7 chars of the scrolling message
    LCDScrollingMessage(); // Scroll the message and load the next 7 chars.

  // check for incoming message
  if (radio.receiveDone())
  {
      if (debug)
      {
        Serial.print("[NodeID:");Serial.print(radio.SENDERID, DEC);Serial.print("] ");
        Serial.print("[RX_RSSI:");Serial.print(radio.readRSSI());Serial.print("]");
      } 
  
      if (promiscuousMode)    
      {
        Serial.print("to [");Serial.print(radio.TARGETID, DEC);Serial.print("] ");
      }    

    if (radio.DATALEN != sizeof(Payload))
    {
      Serial.println("Invalid payload received, not matching Payload struct!");
    }
    else    // ie the data size is correct
    {
      theData = *(Payload*)radio.DATA;
      SignBit = theData.Sign;
      TempReading = theData.Temp;
      

      
      if (SignBit)     // the Temp Reading number is negative
      {
        SignBit = true;       // means it is a negative number
        TempReading = 0.00 - TempReading;
      }
      
      // Send data out the Serial port for Future applications
      Serial.print(" SensorID=");
      Serial.print(theData.sensorID); 
     
      Serial.print(":Temp=");
      if (SignBit)
      {
        Serial.print("-");
      }
      Serial.println(TempReading);
      if (debug)
      {
        Serial.print("RawData=");
        Serial.println(theData.Temp); 
        Serial.print("HighTemp: ");
        Serial.println(HighTemp);
        Serial.print("LowTemp: ");
        Serial.println(LowTemp);
      }
    }

    // The far end will send multiple times if an ACK is not received.
    if (radio.ACK_REQUESTED)
    {
      byte theNodeID = radio.SENDERID;
      radio.sendACK();
      if (debug)
      {
        Serial.print(" - ACK sent.");
        Serial.println();
      }

     }

    // show that we did something 
    Blink(LED,30);

  }
}

void UpdateMaxMin()
{
      if (TempReading != 99.99)   // not the default value so must have received data
      {
        if (FirstReading)
        {
          HighTemp = TempReading; // set to the first reading
          LowTemp = TempReading;  // set to the first reading
        }
        
        FirstReading = false;      // allow real values to overwrite the assummed ones
        
         if (TempReading > HighTemp)
         {
            HighTemp = TempReading;        
         }
         if (TempReading < LowTemp)
         {
           // temp is lower than we had
            LowTemp = TempReading;        
         }

       }
}



void HighLowTemp()
{
  
  /* This displays the highest and lowest temp
    
    Displays over two lines
   
   |         H -xx.xx| 
   |-xx.xx   L -xx.xx|
   
   */

      // Top line
      lcd.setCursor(9,0);             
      lcd.print("H");                 // first position (10) and first line (0) displays   | Temp    L -xx.x| 
      lcd.setCursor(11,0);            // 13th position (12) and first line (0)
      if (HighTemp < 10.0 && SignBit == false)
      {      
        lcd.print(" ");
      }
      lcd.print(HighTemp);
      
      // Bottom line
      lcd.setCursor(9,1);            // 11th position (10) and second line (1)
      lcd.print("L ");
      lcd.setCursor(11,1);            // 13th position (12) and second line (1)
      if (LowTemp < 10.0 && SignBit == false)
      {      
        lcd.print(" ");
      }
      lcd.print(LowTemp);
    
  }    
    
 void DisplayTemp() 
 // This displays the current temp
  {
      if (TempReading == 99.99)
      {
        lcd.setCursor(2,1);
        lcd.print("-.-");
        return;
      }
      
      if(TempReading >= 0.0 && TempReading < 10.0)
      {
        lcd.setCursor(1,1);
        lcd.print("  ");        // removes the - sign if it changes to a positive number
        lcd.print(TempReading); 
        return;
      }
      
      if(SignBit && TempReading <= 0.0 && TempReading >= -9.99)
      {
        lcd.setCursor(2,1);
        lcd.print(TempReading); 
        return;        
      }
      
      if(SignBit && TempReading <= -10.0)
      {
        lcd.setCursor(1,1);
        lcd.print(TempReading); 
        return;        
      }   

      // Do this one because we're still here
        lcd.setCursor(1,1);
        lcd.print(" ");
        lcd.print(TempReading); 
  }

 void DisplayMessage()
   // This displays the 7 chars of the message on the first line 
  {
      lcd.setCursor(0,0);
      lcd.print(LCDMessage);
  }
void LCDScrollingMessage()
 {
   /*
   This demonstrates how to scroll a portion of the LCD
   The basic principle is to construct a message, find the length of the message.
   You then grab xx chars from the message to display.
   Each time you loop, you shift along the message one place and grab xx chars, and repeat.
   When you reach the end of the message, start grabbing from the beginning.
   A space at the end of the message, makes the message look correct on the LCD as it loops.
   Don't let the loop run too fast.
   
   */
   
   MessageStringCount = (sizeof(MessageString))-1;        // Last character in a string is a null, which is counted.
   
   if ((millis()-LastScroll) >=ScrollDelay)
   {
    
     for (i = 0; i <7; i++)                             //there is room for 7 chars (i = 0 to 6)
       {
        LCDPosition = MessageStartPosition +i;
        
        if (LCDPosition >= MessageStringCount)
         {
          LCDPosition = LCDPosition - MessageStringCount;
         }
         LCDMessage[i] = MessageString[LCDPosition];
       }                                                        
       LCDMessage[7] = 0;                                //terminate the string with a null
        
       if (MessageStartPosition >= MessageStringCount)
        {
         MessageStartPosition =0;
        }
        else
        {
          MessageStartPosition +=1;
        }
        LastScroll = millis();
   }
 }  

void Blink(byte PIN, int DELAY_MS)
{
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN,HIGH);
  delay(DELAY_MS);
  digitalWrite(PIN,LOW);
} 

 

I decided to use some of the techniques that I explained here Arduino, Temperature, LCD and more

The display scrolls 8 characters of a message ... in this case "Current Temperature"

 

The Arduino's I use for developing projects have the option of 5v or 3v3, which eliminates the need for level converters.

The RFM69 is a 3v3 device and while they survive 5v on the inputs, they don't work correctly, so either you'll need level converters, or a board that works on 3v3.

 

The biggest issue is most of the LCD's want 5v and hence the contrast becomes a problem.

I2C LCD's can happily be powered from 5v and 3v3 on the SDA and SCL pins, so they'll work correctly.

 

I do have a 3v3 I2C LCD somewhere that arrived with some other 5v versions.

There was some head scratching about why it wouldn't work until I noticed it had an extra chip on the board.

It seemed to have slipped into the suppliers shipment as they don't offer them.

 

 

 

Video

This is a simple demonstration showing the concept with the receiver connected to an Arduino Uno rather than the Moteino.

I've done this because as luck would have it, I didn't have a spare one available to photograph/video.

 

 

 

Hopefully this has refreshed some memory cells and provided some useful information to others interested in sending data from one place to another.

While you can use WiFi and other methods, having an intelligent receiver means you can add value to the data, or raise alarms when you're not getting it within the required time.

 

Mark