Showing posts with label TCP. Show all posts
Showing posts with label TCP. Show all posts

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.


Saturday, February 1, 2014

Building a Wireless Temperature Sensor Network Part 6

Welcome back for part 6, the final post in the building a wireless temperature sensor network saga. In part 5 we added internet monitoring and data logging capability to our temperature sensor network using the Arduino Yun. In this post we will discuss how we can access our wireless sensor network from behind a router firewall, giving us the capability to access it from across the street to across the globe. We will also add the ability to monitor our wireless temperature sensor network from an iOS device such as an iPhone or iPad, which gives us even further flexibility to access our temperature sensor network.

By using the ubiquitous TCP / IP protocol to communicate out the temperature data we have the ability to monitor it from almost anywhere. The roadblock we run into is the firewall in the WiFi router that the Arduino Yun is connected to restricts us to only connecting to the sensor network from within the router's local WiFi / Ethernet network. A basic home WiFi router's default configuration assumes all the devices connected to its local network are clients and will be the ones to initiate communication with a device outside the local network. Because of that any device outside of router's local network that tries to initiate communication with a device inside the router's local network must have malicious intensions. The problem is the Arduino Yun is configured as a server, but the router does not know that. To change this you would need to go into your routers settings and poke a hole in the firewall. The router's settings can typically be accessed through its web page. Refer to your router's user manual for the address to access the web page. Once you get access to your router's settings you have two options for poking a hole in the firewall to access the Yun, Port Forwarding and DMZ. Port Forwarding, which is the safer of the two but is a little more complex to setup, requires you know the TCP / IP communication port that you plan to communicate through (we used 6666 in our Arduino code) and the IP address of the Yun. DMZ just requires you to know the IP address of the Yun. With DMZ set for a specific device or IP address on its local network the router then sends any incoming communication requests from outside the firewall to the specified IP address, regardless of the port. Before moving on there are two other things you should be aware of when trying to connect to your temperature sensor network over the internet:
  1. When outside of your router's firewall you need to use the IP address of the router to connect to the Yun, not the local IP address of the Yun that the router assigns to it. To get the IP address of your router just search "My IP Address" on Google and it will show up in the results. 
  2. Your router will not assign a permanent IP address to the Yun, it will change once and awhile with no warning. To avoid this hassle go into your router settings and assign it a static IP address (an address that will not change).
I know that was a lot of information to swallow if you are not familiar with the Ethernet networks, but if you get stuck just search on the internet. There is plenty of information out there on the topics we just covered. 

To connect to and fetch temperature data from our wireless sensor network from an iOS device we will make some slight changes to our Arduino Yun code presented in the last post (part 5). Instead of posting the whole sketch again, you will just find the two sections of the code where changes were.

//The global strings for storing latest temperature reading are initialized with the string "Empty"
String sen1Temp = "Empty"; //stores temperature value for XBee with sensor 1
String sen2Temp = "Empty"; //stores temperature value for XBee with sensor 2
String sen3Temp = "Empty"; //stores temperature value from Yun with sensor 3


/*The portion of the code where we accept the client connection and send the data has been simplified since now we are only sending one string of data, instead of three separate strings. We will let the iOS code parse the string into three separate values*/
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
    String temp = sen1Temp + "," + sen2Temp + "," + sen3Temp + "\n";
    
    client.write((uint8_t*)&temp[0], temp.length()); //send sensor 1 temp or low battery warning
    client.stop(); //disconnect from client
  }

/*Lastly this function was changed to simplify the three global strings that hold the data that we will send out*/
void setAndLogSensorValue(int sen, float val, int temp) {
  String dataString = getTimeStamp() + " "; //get time info and append space to the end
  
  if (sen == 1) { //sensor 1
    if (temp == 1) {
      sen1Temp = String(val); //store latest temp value in global string
      dataString += "Sensor 1 temperature: " + sen1Temp + "\n"; //build datalog string
     writeToFile(dataString); //write temp value to file 
    }
    else { sen1Temp = "Low Battery"; }
  }
  else if (sen == 2) { //sensor 2
    if (temp == 1) {
      sen2Temp = String(val); //store latest temp value in global string
      dataString += "Sensor 2 temperature: " + sen2Temp + "\n"; //build datalog string
      writeToFile(dataString); //write temp value to file
    }
    else { sen2Temp = "Low Battery";}
  }
  else {
      sen3Temp = String(val); //store latest temp value in global string
      dataString += "Sensor 3 temperature: " + sen3Temp+ "\n"; //build datalog string
      writeToFile(dataString); //write temp value to file
  }
}

To create apps for the iOS you need XCode, which is Apple's software development environment and it only works on Macs. XCode uses the Objective C programming language which is based off of C but its syntax is vastly different (in my opinion). XCode is free to download and you can test your iOS code on an iPhone or iPad software simulator that runs on your Mac. To actually put your code on a physical iPhone or iPad or to post an app on the App Store you need to pay for a $99 per year subscription. For this project a simple iOS app called "SensorMonitor" was put together with a basic user interface for example purposes. Before we discuss the code, below is a screen grab of the SensorMonitor iOS app that connects to the wireless sensor network and fetches the temperature data. The IP address of the wireless network is typed into the text box at the top of the app. When the "Get Temperature Data" button is pushed the app connects to the sensor network, receives the temperature data, and prints the temperature next to the corresponding sensor label. Below on the left is a screen shot of the SensorMonitor app before the button was pushed to retrieve the temperature data. On the right is a screen shot of the app after the button was pushed and the temperature data is displayed next to the corresponding sensor label.  


Even for a simple iOS app like SensorMonitor there is too much code and different files to display and cover in this post. What is shown below is the "IBAction" function from the app's code. The IBAction function is called when the "Get Temperature Data" button is pressed, the function name is "goConnect." The goConnect function is commented to explain what is happening at each line. 

//This function is called when the "Get Temperature Data" button on the user interface is pressed
-(IBAction)goConnect { //connect button action method
    NSError *hError; //declare variable to store error info
    NSString *tempVal; //string to hold temperature data from Yun
    
    if (![io openWithAddress:self.ipTextField.text port:6666 error:&hError]) { //connect to Arduino Yun on port 6666
        //Connection failed print the error to the message label
        self.message.text = [[NSString alloc] initWithString:[hError localizedDescription]]; //display error if connection failed
        [io closeWithError:&hError]; //if already connected and button was pressed, disconnect from Yun
    }
    else { //the connection was a success
        //get sensor 1 temperature data
        if(![io scan:&tempVal trimNewLine:YES error:&hError]) { //read the first sensor value
            self.message.text = [[NSString alloc] initWithString:[hError localizedDescription]]; //if error occurred display error details
            self.sen1.text = @"Read Failed"; //temp data read failed so tell user
        }
        else {
            self.sen1.text = tempVal;
            NSArray *tempData = [tempVal componentsSeparatedByString:@","];
            //self.sen2.text = [tempData objectAtIndex:1];
            self.sen1.text = [@"Sensor 1: " stringByAppendingString:[tempData objectAtIndex:0]]; //Add the sensor 1 temperature data to the Sensor 1 label
            self.sen2.text = [@"Sensor 2: " stringByAppendingString:[tempData objectAtIndex:1]]; //set sensor 2 label to the temperature value just read
            self.sen3.text = [@"Sensor 3: " stringByAppendingString:[tempData objectAtIndex:2]]; //set sensor 3 label to the temperature value just read
    
            self.message.text = @" "; //There were no errors so leave message blank
        }
        [io closeWithError:&hError]; //if already connected and button was pressed, disconnect from Yun
    }
}

If you are interested in accessing the SensorConnect app's code in full just email me at forcetronics@gmail.com and I will send it to you. The value of the SensorConnect app to someone who wants to monitor a sensor network with an iOS device is the code that does the communication with the Yun using TCP / IP protocol. Myself and a colleague of mine created an Objective C API made up of three classes to communicate over Ethernet. In the above function, the "io" object is a handle to the API for connection and communication with the Arduino Yun.

In this 6 part project we went over how to build your own wireless temperature sensor network that included features such as:
  • Temperature data communicated using Zigbee protocol
  • Battery powered sensors
  • Time stamped temperature data logging
  • Monitoring over the internet using an iOS device
This project was meant to serve as a foundation or starting point for you to build your own custom temperature sensor or any type of sensor network. If you found any of the information in this project useful please click on one of the ads in this blog to help fund future projects. If you have any comments or questions related to this post or this project use the Comment section below or send me an email at forcetronics@gmail.com. See you back soon for the next project!