Sunday, February 2, 2014

Building a Wireless Temperature Sensor Network Part 5


Welcome back for part 5 of building a wireless temperature sensor network. In part 3 and 4 we integrated a portable power source for each sensor design. In this post we will add internet monitoring and data logging capabilities to our temperature sensor network. To do this we will replace the Aruduino Uno in our sensor network controller with an Arduino Yun.

Out of all the Arduino's I have used the Arduino Yun is my favorite, because it can do so much! If you are not familiar with the Arduino Yun I encourage you to read up on it and its capabilities by clicking here. For this project we will be using the WiFi capability of the Yun to monitor our sensors over the internet and the microSD card drive to log our temperature data. If you are using a brand new Yun be sure to configure its WiFi before using it for this project, for instructions on configuring the Yun's WiFi click here. The Yun does not come with a microSD card so you will need to purchase one and plug it into the drive on the Yun for this project.

Unlike the Uno, the Yun does not have an onboard voltage regulator so we will need to add one to convert the output voltage of our DC power supply to 5 V for powering the Yun. For the voltage regulator I decided to use an LM7805C since it is low cost, outputs 5 V, and they sell it at the local Radio Shack. You could also use the LM317 regulator we used for the sensor 1 design, you would just need to tweek the output control resistors to get 5 V instead of 3.3 V at the output. Below you will find the updated schematic of the network controller with the Yun and LM7805C added in place of the Uno. Please note that you will need to use a heat sink with the voltage regulator to dissipate heat since the Yun has a microcontroller and a processor it can easily consume current levels > 300 mA.
Network Controller with Arduino Yun
The Yun does not have dedicated serial communication capabilities like the Uno so we will have to add serial communication capability in our sketch code to be able to communicate with the XBee module. The benefit of not having dedicated serial communication pins is we can now use any digital pins on the Arduino as our communication pins, instead of always having to use D0 and D1. Notice from the figure that we are using D10 as the receive pin and D11 as the transmit pin.

To add internet monitoring and data logging to our project as well as re-add serial communication to our project we will take advantage of the awesome libraries that are available for the Yun. Below is the sketch code for our project. Notice at the top of the sketch we are using five different libraries! The first is the Bridge library, this library is needed for the microcontroller to communicate with the processor on the Yun. The Bridge is needed to access a lot of the Yun's capabilities including communication over the internet. The YunServer and YunClient libraries are what we will use to communicate temperature data over the internet. These libraries use the TCP / IP protocol which uses a client / server model to communicate data. In this project the controller is the server and what ever device we use to connect and get the temperature data is the client. The SoftwareSerial library is for serial communication between the Yun and the XBee module (this capability was built-in to the Uno). The FileIO library is what we will use to log time stamped temperature data to our microSD card.

/*This sketch was written for the Arduino Yun. The Yun has an XBee Series 2 RF Module connected to it as a coordinator. The Yun uses the XBee coordinator to communicate with two XBee routers. Each XBee router has an analog pin set to measure a temperature sensor and a second analog pin set to measure the voltage level of the battery powering the XBee. This program receives the temperature readings from the two router XBees and allows the readings to be read over the internet and logs the temperature data to a microSD card. This sketch is part of a tutorial on building a wireless sensor network, the tutorial can be found at http://forcetronic.blogspot.com/*/

#include <Bridge.h> //needed for comm between microcontroller and proccesor on Yun
#include <YunServer.h> //needed for LAN or WiFi device to connect (Yun acts as server)
#include <YunClient.h> //use to manage connected clients
#include <SoftwareSerial.h> //Need to create serial comm with XBee (Yun does not have dedicated serial port like Uno)
#include <FileIO.h> //Used for logging data to a file stored on the microSD card

#define PORT 6666 //port number used to communicate temperature over the internet using TCP/IP
YunServer server(PORT); //create server object and set comm port
SoftwareSerial mySerial(10, 11); // Declare serial object and set serial comm pins (RX, TX)
int addr1; //variables to hold end point XBee address
int addr2; //Each address variable is two bytes 
int addr3;//XBee address is 64 bits long but first 32 bits are common to all so just need last 32
int addr4;
String sen1Temp; //stores temperature value for XBee with sensor 1
String sen2Temp; //stores temperature value for XBee with sensor 2
String sen3Temp; //stores temperature value from Yun with sensor 3
int sen3Counter = 0; //This counter variable is used print sensor 3 every 5 seconds
float batDead = 6.2; //battery pack voltage level where it needs to be replaced

void setup() {
  // put your setup code here, to run once:
  mySerial.begin(9600); //Set the baud rate for serial communication
  Bridge.begin(); //initiate the SPI based communication between the microcontroller and processor on the Yun
  FileSystem.begin(); //Initializes the SD card and FileIO class
  server.noListenOnLocalhost(); //Tells the server to begin listening for incoming connections
  server.begin(); //Start the server so it is ready to take connections from clients
  pinMode(13, OUTPUT); //set LED pin to output
  digitalWrite(13, HIGH); //turn LED on so we know all setup is complete and is YUN is connected to WiFi
}

void loop() {
  
  if (mySerial.available() >= 23) { // Wait for a full XBee frame to be ready
    if (mySerial.read() == 0x7E) { // Look for 7E because it is the start byte
     
      for (int i = 1; i<19; i++) { // Skip through the frame to get to the unique 32 bit address
       
        //get each byte of the XBee address
        if(i == 8) { addr1 = mySerial.read(); }
        else if (i==9) { addr2 = mySerial.read(); }
        else if (i==10) { addr3 = mySerial.read(); }
        else if (i==11) { addr4 = mySerial.read(); }
        else { byte discardByte = mySerial.read(); } //else throwout byte we don't need it
      }

      int aMSBBat = mySerial.read(); // Read the first analog byte of battery voltage level data
      int aLSBBat = mySerial.read(); // Read the second byte
      int aMSBTemp = mySerial.read(); // Read the first analog byte of temperature data
      int aLSBTemp = mySerial.read(); // Read the second byte
      float voltTemp = calculateXBeeVolt(aMSBTemp, aLSBTemp); //Get XBee analog values and convert to voltage values
      float voltBat = calculateBatVolt(aMSBBat, aLSBBat); //Get Xbee analog value and convert it to battery voltage level
      int id = indentifySensor(addr1,addr2,addr3,addr4); //save identity of sensor
      
      if(voltBat > batDead) { //This if else statement checks the battery voltage, if it is too low alert the user
        setAndLogSensorValue(id,calculateTempF(voltTemp),1); //set sensor string and log temperature only if battery is still good
      }
      else {
        setAndLogSensorValue(id,voltBat,0); //set sensor string for low battery, temperature reading will not be logged
      }
    }
  }
  
  delay(10); //delay to allow operations to complete
  //This if else statement is used to print the reading from sensor 3 once every ~5 second to match the XBee routers
  //It uses the delay() function above to calculate 5 seconds. May need to tweek count in if statement to get 5 seconds
  if (sen3Counter < 300) { sen3Counter++; }
  else {
    setAndLogSensorValue(3,calculateTempF(calculateArduinoVolt(analogRead(A0))),1);
    sen3Counter = 0; //reset counter back to zero
  }
  
  YunClient client = server.accept(); //accept any client trying to connect

  if(client.connected()){ //If we are connected to a client send identity and temperature data
    
    if (sen1Temp.length()==0) { sen1Temp = "Empty value\n"; } //if string is empty, let client know
      
    client.write((uint8_t*)&sen1Temp[0], sen1Temp.length()); //send sensor 1 temp or low battery warning
    
    if (sen2Temp.length() == 0) { sen2Temp = "Empty value\n"; }  //if string is empty, let client know
    
    client.write((uint8_t*)&sen2Temp[0], sen2Temp.length());  //Send sensor 2 temp or low battery warning
    
    if (sen3Temp.length() == 0) { sen3Temp = "Empty value\n"; }  //if string is empty, let client know
    
    client.write((uint8_t*)&sen3Temp[0], sen3Temp.length());  //Send sensor 3 temp
    client.stop(); //disconnect from client
  } 
}

//This function takes in the XBee address and returns the identity of the Xbee that sent the temperature data
int indentifySensor(int a1, int a2, int a3, int a4) {
 //These arrays are the unique 32 bit address of the two XBees in the network
 int rout1[] = {0x40, 0xB0, 0xA3, 0xA6};
 int rout2[] = {0x40, 0xB0, 0x87, 0x85}; 

  if(a1==rout1[0] && a2==rout1[1] && a3==rout1[2] && a4==rout1[3]) {
    return 1; //temp data is from XBee or sensor one
  }
  else if(a1==rout2[0] && a2==rout2[1] && a3==rout2[2] && a4==rout2[3]) {
    return 2; } //temp data is from XBee or sensor two
  else { return -1; }  //Data is from an unknown XBee
}

float calculateTempF(float v1) { //calculate temp in F from temp sensor
 float temp = 0;
 //calculate temp in C, .75 volts is 25 C. 10mV per degree
 if (v1 < .75) { temp = 25 - ((.75-v1)/.01); } //if below 25 C
 else if (v1 == .75) {temp = 25; }
 else { temp = 25 + ((v1 -.75)/.01); } //if above 25
 //convert to F
 temp =((temp*9)/5) + 32;

 return temp;
}

//This function takes an XBee analog pin reading and converts it to a voltage value
float calculateXBeeVolt(int analogMSB, int analogLSB) {
  int analogReading = analogLSB + (analogMSB * 256); //Turn the two bytes into an integer value
  float volt = (float)analogReading*(1.2/1023); //Convert the analog value to a voltage value
  return volt;
}

//This function takes an Arduino analog pin reading and converts it to a voltage value
float calculateArduinoVolt(int val) {
 float volt = (float)val * (5.0 / 1023.0); //convert ADC value to voltage
 return volt;
}

//This function calculates the measured voltage of the battery powering the sensor
float calculateBatVolt(int aMSB, int aLSB) {
  float mult = 10.0; //multiplier for calculating battery voltage
  return (calculateXBeeVolt(aMSB, aLSB)*mult); //xbee voltage x voltage divider multiplier equals battery voltage
}

//This function builds the temperature strings that are communicated over the internet and logs time stamped temperature data to file on
//microSD card
void setAndLogSensorValue(int sen, float val, int temp) {
  String dataString = getTimeStamp() + " "; //get time info and append space to the end
  
  if (sen == 1) {
    if (temp == 1) {
      sen1Temp = "Sensor 1 temperature: " + String(val) + "\n";
      dataString += sen1Temp;
     writeToFile(dataString); //write temp value to file 
    }
    else { sen1Temp = "Sensor 1 low bat volt: " + String(val) + "\n"; }
  }
  else if (sen == 2) {
    if (temp == 1) {
      sen2Temp = "Sensor 2 temperature: " + String(val) + "\n";
      dataString += sen2Temp;
      writeToFile(dataString); //write temp value to file
    }
    else { sen2Temp = "Sensor 2 low bat volt: " + String(val) + "\n";}
  }
  else {
    sen3Temp = "Sensor 3 temperature: " + String(val) + "\n";
    dataString += sen3Temp;
    writeToFile(dataString); //write temp value to file
  }
}

// This function return a string with the time stamp
String getTimeStamp() {
  String result;
  Process time;
  // date is a command line utility to get the date and the time 
  // in different formats depending on the additional parameter 
  time.begin("date");
  time.addParameter("+%D-%T");  // parameters: D for the complete date mm/dd/yy
                                //             T for the time hh:mm:ss    
  time.run();  // run the command

  // read the output of the command
  while(time.available()>0) {
    char c = time.read();
    if(c != '\n')
      result += c;
  } 
  return result;
}

//This function writes data to a file called TempData on the microSD card
void writeToFile(String data) {
   // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  // The FileSystem card is mounted at the following "/mnt/FileSystema1"
  File dataFile = FileSystem.open("/mnt/sd/TempData.txt", FILE_APPEND);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(data);
    dataFile.close();
  }  
  // if the file isn't open then you could signal an error here
  else { } 
}

The code for the Arduino Yun sketch is well commented, but let's highlight certain areas and add some explanation:

  1. A server object is created in the setup function and in each iteration of the main loop it checks if there is a client device that wants to connect. If there is a client trying to connect the server accepts it, sends the latest temperature data from each sensor, and closes the connection. In the TCP / IP protocol you have to choose a port number to communicate over. We will use Port 6666 for our temperature network communication.
  2. Each temperature sensor reading is time stamped and stored in the TempData.txt file. If the TempData.txt file does not exist on the microSD card, it will be created. If the file does already exist it will append the new data to any existing data in the file. If sensor 1 or 2's battery gets too low that sensor's temperature reading is no longer stored in the TempData.txt file. 
  3. Since the Yun is doing complex operations like writing to a file, reading data from the XBee, and sending readings over the internet it is not as easy as it was earlier in the project to sync sensor 3 to the same timing intervals as sensors 1 and 2. You may need to adjust the sensor 3 delay loop a bit to get the timing just right. 
  4. When you "Verify" this sketch you will notice that the code takes up approximately 84% of program memory space in the microcontroller so be sure to carefully optimize if you need to add extra capabilities, more sensors, or more extensive error checking to this code.
The easiest way to test the internet monitoring capability of our project without having to write a program is to use a Terminal and the "netcat" command. If you have a Linux based computer or a Mac you have a Terminal. For Mac's the Terminal is located in the Mac HD --> Applications --> Utility directory. The netcat command allows you to communicate with another internet connected device, such as the Yun, using the TCP / IP protocol. To connect to the Yun using the nc command (short for netcat) we need to know the Yun's IP address and the port we want to communicate over. We know the port from our sketch (6666). If the Yun is powered on and connected to your router its IP address can be obtained from the Arduino IDE, just go to Tools --> Port. You can also obtained the IP address of the Yun by going to the Device Table of the internet router you are using (see router's instruction manual to access Device Table). To connect to the Yun with your PC Terminal use the following command sequence: nc <Yun IP Addres> 6666
Please note for this to work your computer has to be connected to the same local network or router that your Yun is connected to. For instance if you are in your home, the Yun and your computer both should be connected to your home internet router. If you are using a Windows computer and following along to this project, there is an open source netcat.exe program out there on the web. You can download it at http://nmap.org/ncat/ . I have not had the chance to try it out. 

Refer to the image below of a Terminal showing the our updated project in action. You can see a connection is made, temperature data is sent to the Terminal, and then the connection is closed. From the Terminal image you can see a connection was made and temperature data was fetched three times. For this example a variable power supply was used to simulate sensor 2's battery getting low, this is detected and the user is notified through the Terminal.

Next let's look at an example data log file. This file came from the same example as the above terminal image so we would expect to see sensor 2 temperature readings to stopped being logged when its battery gets too low. From the below TempData.txt file you can see the date and time of the sensor reading is captured. If you scroll to the bottom you will notice we no longer see readings from sensor 2. This is due to its battery voltage becoming too low so the Yun no longer logs temperature data from it. 

That is all for part 5 of building a wireless temperature sensor network. In part 6, the final post of this project, we will look at how to access our temperature sensor network data outside of the router or local network it is connected to. We will also look at how to access our sensor network from an iOS device, such as an iPhone or iPad. If you have any questions or comments on what was covered in this post use the Comments section below or feel free to email me at forcetronics@gmail.com.