Pages

Showing posts with label greenhouse. Show all posts
Showing posts with label greenhouse. Show all posts

Sunday, January 3, 2016

Greenhouse Phase 5: XBee On Sensor Board

The first several articles in the Greenhouse Series were about
In Data To The House we talked about XBee sensor meshes and configured a couple of XBee radios to get data from the greenhouse into the house. In this article, we will 
  • wire an XBee Series 2 radio to the greenhouse sensor board
  • modify the Teensy sketch to send the data from the sensors to the XBee on the sensor board
  • write a small Python script to read the sensor data from the Coordinator radio

The Greenhouse Sensor Board


The Greenhouse sensor board needs to have the XBee radio added and the Teensy sketch modified to send data through the sensor board radio to the Coordinator radio.

Wiring in the XBee Radio


Wiring the XBee radio to the sensor board is very easy. The XBee radio uses standard serial communication to talk to a host processor. The RX and TX pins on the radio will be attached to TX1 and RX1 respectively on the Teensy. RX1 and TX1 are the first hardware-based serial pins on the Teensy 3.1. The serial connection and power are all that are needed. The XBee is in the lower right of the image below.




The current version of the board then looks like this.



This is a more up close shot of the board.




Originally I thought I was going to have to use an XBee Series 2 Pro radio to get data all the way across the yard from the greenhouse to the house, going through outer walls of both buildings and th Coordinator possibly in the basement of the house. The Pro is 63 mW of power and has a distance of about 1 mile, while the standard radios are 2 mW and good for about 400 feet. A quick test with a 2 mW radio got signals from inside the closed greenhouse into the basement of the main house, so I stayed with the 2 mW radio. This was a relief as the 2 mW radio uses up only 40 mA of power, while the Pro uses 295 mA and I would have had to build an external power board as the power regulator on the Teensy couldn't have powered the Pro.

Modifying the Teensy Sketch


The code running on the Teensy must now be modified to use the radio. The code can be found in the github repository here.

First make sure you have Andrew Rapp's XBee library for Arduino installed on your machine. At some point I will probably write a new Arduino XBee library as Andrew's code is GPL. Though I fully support people using the Gnu license, I much prefer the Apache 2 license and so will most likely write a library that will be licensed as such.

First you will need to include the XBee.h file in your sketch.

#include <XBee.h>

The sensor data will be sent as an XBee TX packet. TX packets allow arbitrary data to be sent between radios and are received as an RX frame on the destination radio. Adding the following lines will create an XBee radio object and some other data structures for the XBee TX packet.

XBee xbee;

XBeeAddress64 addr64 = XBeeAddress64(0x00000000, 0x00000000);

ZBTxRequest zbTx = ZBTxRequest(addr64, (uint8_t *)&sensorData, sizeof(sensorData));

The variable addr64 contains the 64 bit address of the radio that will receive the sensor data packets. Typically I use the Coordinator radio to be the main destination for sensor data, so here set the destination address to the special address 0000000000000000 for the Coordinator. You could also use the exact address for the Coordinator by looking at the underside of the radio, but by using the special address you can switch out your Coordinator radio for another radio and the Teensy code would not have to change.

The variable zbTx creates the TX request packet to be sent by the XBee. It uses our address for the Coordinator Radio. The last argument sizeof(sensorData) will give the number of bytes of sensor data to be transmitted. This way we can add or subtract data from the packet and not have to worry about counting bytes on the Teensy side.

The second argument (uint8_t *)&sensorData needs some explanation. The ZBTxRequest constructor requires a pointer to an array of bytes containing the data to be transmitted to the destination radio, an expression of type uint_8 *. sensorData contains our data, but it is a bunch of float data. How to we get those float values into an array of bytes? A lot of people create a byte array for the TX packet and use various tricks to get the bytes from the floats into the byte array, but that is way too much work. Can we do better?

Let's remind ourselves of the sensor data data structure.

typedef struct _SensorData {
  float temperatureInside;
  float humidityInside;
  float temperatureOutside;
  float humidityOutside;
  float altitude;
  float barometricPressure;
} SensorData;

SensorData sensorData;

When sensorData is laid out in the memory of the Teensy, the first 4 bytes will contain the value of temperatureInside, the next 4 bytes will contain the value for humidityInside, the next 4 temperatureOutside, and so on. Whatever order the fields are found in the struct will be the order of the values in the Teensy memory.

Though it will not matter in this article, the float values are laid out in memory lowest order byte first. This is called little-endian. Knowing the byte order will matter when we start processing the data in Java, but won't matter for the Python script.

Going back to our second argument, the expression &sensorData will give us the memory address of the lowest byte of the sensor data. Just to reinforce, this byte will be the low order byte (remember little-endian) of temperatureInside. However, the type of this pointer will be SensorData * and we need a pointer type of uint_8 *, so we use the type coercion (uint_8 *).

The program then places data in the structure as before. It doesn't matter what order we make the assignments in, the only thing that matters as far as the radios are concerned is the order of the fields in the struct.

Initializing the xbee object is pretty easy. First, the serial object needs to be initialized. Since the TX1/RX1 pins are being used on the Teensy for the XBee, we need to use the Serial1 object.

Serial1.begin(9600);
xbee = XBee();
xbee.begin(Serial1);

The final new line sends the actual packet to the destination radio.

xbee.send(zbTx);

Let's look at the output coming across the USB serial connection for the Teensy. Once the Python script is written, we will want to confirm that the values being output here are the same being output by the Python script.




So far so good!

Reading the sensors after 1 second pauses is probably too frequent. When the board is finally deployed, I will probably sample every 10 minutes, which means that the SLEEP_DELAY constant at the beginning of the file should be set to 60000. But waiting 10 minutes while debugging the system is painful, so for now the value is 1000 for those 1 second pauses.

Checking Radio Communication


It is always best to test in as simple an environment as you can. Now that the sensor board has its radio, let's use XCTU to see if the board is communicating with the Coordinator radio before writing the Python script to read the data.

If you remember the last article, we talked some about Networking mode in XCTU. This mode lets us look at all of the radios in the mesh and see who can talk to whom. Let's use that now.

First  I placed the greenhouse sensor board in the greenhouse and plugged it into power. Then I went back into the house and plugged the Coordinator radio into a Sparkfun USB Explorer and plugged the USB cable into my laptop and set up XCTU to look at the radio. A quick check in Networking mode (remember to hit the Scan button) showed the radios talking to each other.

I then placed another XBee Router next to the window in the main house and carried my laptop into the basement, expecting to see the greenhouse radio transmitting through the Router radio by the window which would then communicate the sensor packets to the Coordinator radio. But if you look at the picture below, you can see that the greenhouse radio is talking directly to the Coordinator radio despite the distance from the house to the greenhouse, 2 outer walls, and the distance into the basement.

Not bad!




If you remember in the last article we talked about XCTU's Consoles mode which allows us to look at the packets coming into a radio. I switched over to the Consoles window and clicked the Open button. You can see the XBee API RX packets coming into the radio from the greenhouse radio. They are the red lines in the center section of the picture below. They are labeled Receive Packet and have a length of 36 bytes.





There is a window along the right side of the Consoles window that shows the actual contents of the packet. If you scroll this window to the bottom and hit the Hex tab, you can see the sensor data coming through.




Receiving the Data from the Coordinator Radio


Now that we know data is coming into the Coordinator radio from the sensor radio, it is time to write a Python script to show us the data. The complete script can be found here on github.

The first thing to do is install the Python XBee library on the computer you want to run the script on.

$ sudo pip install xbee

First we need the Python object that interfaces to a Series 2 radio in API mode. This is done by importing the ZigBee package from the xbee Python library.

from xbee import ZigBee

Next we will create a serial connection to the Sparkfun USB Explorer and hand this serial connection to the ZigBee object.

serial_port = serial.Serial(serial_port_name, SERIAL_BAUD_RATE)
 
xbee = ZigBee(serial_port, escaped=True)

The ZigBee object can now be polled to see if a new API frame has been read. The wait_read_frame() call blocks until a new frame is read.

Once a frame is received, we will first check to see if it is from the greenhouse radio. The reason for this check is that eventually all radios will be sending TX packets to the Coordinator and we need some way to tell which radio has sent a particular packet. This can be handled in several ways. We could preface each packet with an identifier saying what set of sensors the data is from. Or we could just know the address of each radio, assuming each radio only sends one type of data packet. I chose in this case to go with the latter.

The frame that the ZigBee class creates is a dictionary with multiple fields in it, including the address of the source radio of the TX packet, as well as the data. To get the 8 bytes of source address, we use the expression frame['source_addr_long']. We take these 8 bytes and create a hexadecimal string for easy comparison with the address supplied as a command line argument when we start running the script.

If we find that we have a packet from the greenhouse radio, we then need to decode the binary data in the packet to the series of floats that were sent. We use the expression frame['rf_data'] to get this data.

To read the data we need to know what order the floats are in the packet. Looking back at the struct in the Teensy code we know the order is


  1. The inside temperature
  2. The inside humidity
  3. The outside temperature
  4. The outside humidity
  5. The altitude
  6. The barometric pressure


We can read these values from the TX data by specifying the start location and the number of bytes to use for a data value. In our case, every value being sent is a float and a float is 4 bytes.

inside_temperature = struct.unpack('f',rf_data[0:4])[0]
inside_humidity = struct.unpack('f',rf_data[4:8])[0]
outside_temperature = struct.unpack('f',rf_data[8:12])[0]
outside_humidity = struct.unpack('f',rf_data[12:16])[0]
altitude = struct.unpack('f',rf_data[16:20])[0]
barometric_pressure = struct.unpack('f',rf_data[20:24])[0]

Notice the inside temperature is first in the packet with its first byte at position 0 in rf_data, so we look at rf_data[0:4]. The inside humidity is second in the packet with its first byte at position 4, so we need to look at rf_data[4:8]. The rest of the values follows the same pattern.

The script can be run with something like the following command. For me the Sparkfun USB Explorer had ended up at /dev/ttyUSB0 and the greenhouse sensor radio has the 64 bit address 0013a200407bd2e6.

$ ./GreenhouseSensors.py /dev/ttyUSB0 0013a200407bd2e6

The screenshot below shows the data being output by the script.





Conclusion


At long last there is finally data making it from the greenhouse into the main house. You have seen how easy it is to create and process XBee packets both on the Teensy and in a Python script. Ultimately I will write an Interactive Spaces activity to process the data, but for now the Python script is enough. I will also soon modify the Python script to send the data up into the time series database in the cloud as discussed in the article Sensor Data To The Cloud: Part 1.

Sunday, December 13, 2015

Greenhouse Phase 4: Data To The House

The first several articles in the Greenhouse Series were about


Now it would be nice to get the data from the sensors in the Greenhouse into the main house for capture, processing, etc.

To do this, I decided to use Series 2 XBee radios. These powerful little radios are very easy to interface to and have a very nice feature, they support mesh networking. A lot of people look for cheaper radios, XBees are not the cheapest in the world, but these little radios have a whole lot of features and, if you need these features, spending some extra money rather than implementing those features yourself can be the right decision to make.

XBees


Let's start off by talking a little bit about Series 2 XBee radios.

What Is Mesh Networking?


So what is mesh networking? Mesh networking is useful in situations where you have a bunch of radios that need to talk to each other, some are too far away from each other to communicate directly, or perhaps there is a wall in the way that doesn't let the signal thorough. Also, sometimes noise prevents a signal from getting through, or something breaks. So how can we have a reliable network that makes sure that all data is received?

Suppose you have radios A and B, and A wants to talk to B, but B is too far away from A, or there is a wall in the way or something that means that the connection won't happen. One way of handing this is to have a 3rd radio C that is within range of both A and B. A could send its message for B to C and C could then forward the message to B, A -> C -> B.

One issue here is if C stops working for some reason, suddenly A can no longer talk to B.

So suppose we have a radio D that is also reachable by A and by B. It doesn't matter if C can talk to D or not, just that A and B can talk to D. But say C is working. Now there are 2 different routes from A to B, either A -> C -> B, or A -> D -> B. If radio A finds that C isn't working, it can route its message for B through D. Of course, if both C and D are not working for some reason, once again there is no way for A to talk to B.

It is possible for these chains to be longer. Each additional radio in the chain is called a hop. Say radio A can't reach B, but can reach C. C can't reach B, but can reach E. And suppose E can reach B. Now A can send a packet to B via the path A  -> C -> E -> B. This path has 3 hops.

So radios don't necessarily have to be able to talk directly to each other to transmit information to each other, they can use intermediate radios. Not only that, but they needn't use the same path each time. Some radios in a path that used to work may not be available at any given time, but if there is a path of radios who can talk to each other, the packet can eventually get from A to B. This is the basis of mesh networking and is similar to how the Internet works.

Other XBee Concepts


There are a few more concepts useful for understanding XBee radios. Every mesh has to have 1 radio that is the Coordinator. A mesh can have only one Coordinator. This radio controls the network. It determines the ultimate local addresses that each radio in the mesh gets. Radios have 2 addresses, one short one local to the mesh the radio is part of, and a much longer one that is unique to the radio no matter what mesh it is in.

Another radio type is a Router. Router radios can act as intermediaries and radios who cannot talk directly to each other must go through one or more Routers.

Finally there can be End Devices. These End Devices can be battery powered and lower powered. They can go to sleep and turn themselves back on. They can talk to Routers or Coordinators, but cannot talk to each other. This limited functionality means they can be lower cost or can work on battery power for very long periods of time because they can sleep between transmissions.

An example mesh network might look like below.




One more important concept is that of the PAN (Personal Area Network) ID. All XBees must have a PAN ID, and all radios that are going to communicate with each other must have the same PAN ID. The PAN ID is determined by the Coordinator, and can be manually set or automatically created by the Coordinator. Finally, you have have multiple independent XBee networks in the same physical location, each with its own PAN ID. These networks will not be able to communicate with each other, only radios with the same PAN ID can communicate with each other.

In this post we will set the PAN ID manually.

The XBee Hardware


You can use the XBee hardware in several ways.

You can talk to it over a serial connection and control it from a host computer or microcontroller. This style of usage gives you the most flexibility, but as it requires some programming, it is also the most complex usage. The radios have standard RX and TX pins for communication.

It is also possible to wire digital and analog sensors directly to an XBee, eliminating the need for a host computer or microcontroller. In this style of usage, circuitry and programming are minimized. It is also possible then to make the radio sleep for the intervals between sensor samples, which means that a battery powering the XBee could last a very long time The number of sensor pins is limited, but if this is all you need, it is a great option for the simplicity and long battery life.

Configuring the XBees


XBee radios are configured with software from Digi, the manufacturer of XBee radios, called XCTU. When I first started using XBees, XCTU only ran on Windows machines, which meant I had to use virtual machines or Wine on my Linux box. But Digi have since rewritten the program in Java and it is now possible to run it on all major OSes.

The program is really nice now. Not only does it work on all OSes, but it also has a much improved user interface. It also has new functionality that is very useful for debugging, you can visualize your entire XBee network and see which radios can talk to which other radios, transmit data to any radio by typing the data into the UI. All in all, XCTU has become a very nice tool.

So jump into your favorite search engine and search for "digi xctu download" and pick the version appropriate for your computer and follow the directions for installation.

My Initial Network


The wireless network I build will eventually cover both the greenhouse and the house itself, but this post is about getting data from the greenhouse into the house. To do this I will use 3 XBees. Two will be XBee Pros. These radios have a lot more output power and I figured they would be good for getting through the outside walls of the greenhouse and the house and cover the distance between the 2 buildings. They might also be overkill, but I thought I would give them a try. The Coordinator Radio will be a standard XBee in the house and doesn't have to be as high powered.

Hardware for Configuring


It is necessary to have a serial connection to the radios to program them. To help with this, I bought several Sparkfun XBee Explorers. These little boards are awesome. You plug the radio into the board, hook up a USB cable and you are working with them in no time. I often hook the Coordinator Radio up to a host computer using an Explorer in production.

The Radios are blue and the Explorers are red in the picture below. You may also notice tape on the antenna for the radios. I usually use tape to mark the PAN ID and whether the radio is a Coordinator, Router, or End Device when building a network so I know which is which.






Running XCTU


Running XCTU is easy. If you follow the directions on the Digi site on Linux, it will install the software in /opt/Digi/XCTU-NG. I have not yet puzzled through how to set permissions so that I can use XCTU without running as root, for now I use the command

sudo /opt/Digi/XCTU-NG/app &

This will run the program as root and run it in the background so that I can do other things in the same terminal window.

The initial window you should see is the following.




Now we need to scan for the radios. First I want to program the Coordinator radio, so I labeled the Coordinator Radio with tape that had C written on it, plugged it carefully into the Sparkfun USB Explorer, and then plugged the Explorer into my computer.

If you look at the initial XCTU window, you will see an image kinda shaped like an XBee with a magnifying glass next to it. It is the second large icon from the left just under the menu bar. Click this and you will see the following dialog box.



On my machine the Explorer got assigned /dev/ttyUSB0, so I click the checkbox and hit Next >.

The next dialog box will ask me how I want to talk to the XBee, and will let me pick serial properties like baud rate, number of stop bits, etc.


The default baud rate for XBee radios is 9600 baud, in fact all the values seen are the defaults, so just clock Finish.

Another dialog box will pop up and give messages about its search for radios on the port given. If everything is wired property and you left the connection values  alone you should eventually see the following dialog box.


Select the checkbox next to the radio and click Add selected devices.

You should then see a window like below. To be honest, this window is from after I made the radio a Coordinator, so your picture will be slightly different. But that won't matter at all.



Now double click on the radio entry in the left hand pane. This will bring up data in the right hand pane about the radio.



Configuring the Coordinator


The radio plugged in now is to become our Coordinator. If you look over the above window, you will see that the radio is currently a Router. I have found that most brand new radios I buy have the Router AP firmware in them. That's fine, XCTU makes it pretty easy to change. There are a series of 5 large icons in the right pane labeled things like Read, Write, Update and others, and we will use Update.

Before we continue, let's discuss what is going on. XBee Series 2 radios are basically small computers and each radio can either be a Coordinator, a Router, or an End Device. What determines the type of radio is the firmware uploaded to it. Right now the radio seen in that last window shown has the Router AT firmware and so is a Router, and we want it to contain the Coordinator API firmware so that it will be a Coordinator. We won't worry about the different between AT and API in this post, we need API. So click the Update button to see the following dialog.




Make sure the product family is selected as XB24-ZB. If this isn't chosen, you have the wrong kind of radio and should go make sure you order a Series 2 radio. Scroll through the middle list until you find ZigBee Coordinator API. As for the Firmware version, I always make sure I chose the latest firmware. I have never tried to find out if my Coordinator, Router, and End Device radios are running the latest firmware, but it probably is safer to make sure they are always running the latest at the same time.

Then click Update and wait.

Once the update is done, XCTU will read the values from the radio again and you should see the following.



Notice the function set for the radio is now labeled as Coordinator, though if your window is like the picture I have, you will see a ... and some of the letters of Coordinator. Rolling the mouse over this area will give a tooltip giving the function set name without the ... in the middle of the text. If it is not ZigBee Coordinator API, trying doing the update again, you didn't select the correct firmware.

Now we need to set the PAN ID. Remember every radio that needs to talk to each other needs to have the same Pan ID. For this post, we will make the Pan ID have the value 2000, though you can pick any value you want as long as all the radios get the same value. Scroll through the radio parameters until you see PAN ID. You probably won't need to scroll at all, it is the top value. Type 2000 into the text box.



Next, the software libraries that will be talking with the XBee are using Escaped Mode. Without going into too much detail, this mode makes it easier to detect XBee radio frames, so many software libraries use it. To use Escaped Mode, we need to tell the radio to set its configuration parameter AP to the value 2.



Now we need to write these values into the permanent memory in the radio so that they are kept when the radio is turned off and turned back on. If you look at those large icons again in the right hand pane, you will see Write. Click that button.

If you want to confirm that your values are set correctly, click the Read button and scroll through the window to make sure PAN ID and AP have the proper values.

Cool, now we have a Coordinator Radio to rule our network!

Configuring the Greenhouse Radio


Now it is time to configure the XBee that will go in the greenhouse. I decided to make this radio a Router for no particularly good reason. My general rule of thumb is that if the radio is not going to go to sleep between sensor measurements and will not be battery powered, I usually make it a Router to have the most flexibility.

Since my greenhouse is across the lawn, I chose an XBee Series 2 Pro, which has a higher power output than a standard XBee Series 2.

I labeled the greenhouse XBee with a piece of tape with the letter R to label it as a Router, carefully seated it on a second Sparkfun USB Explorer, and plugged it into my computer. I then clicked the Search Radio icon on the left side of XCTU and got the following dialog.




Notice the additional serial port /dev/ttyUSB1. Click Next >. You will again see the Port Values dialog where you can chose baud rate, stop bits, etc. Just click Finish. After a little while you will see the following dialog.



Make sure both radios are checked and click the Add selected devices button. You will now see two radios in the left panel of the XCTU window.




If you are looking at these images carefully, the above picture is not exact. This is actually from after the radios were configured, so the radio already has the Router API firmware. But ignore this for now, and imagine it claims the radio is Router AT, which is how the radios are usually set up when bought brand new.

Now double click on the new radio, which is below the Coordinator radio in the above picture, and get to its main screen.




First off is to update its firmware to be XBee Router API by clicking the Update button.




After the firmware update finishes, set its PAN ID to 2000.



Also set its API Mode to Escaped Mode, which is done the same way as the Coordinator Radio, setting the AP configuration parameter to 2. Then click Write.

Confirm the values by clicking Read and scrolling to check PAN ID and AP.

Are The Radios Communicating?


One of the features in XCTU that I really like that the previous version didn't have is the ability to look at the network of radios and see who talks to whom and exactly what packets they are sending and the contents of those packets. You can also construct packets and have the radio send them. All of this really helps with debugging.

To get into this mode, look at the toolbar immediately under the menu bar and click the rightmost icon. As you mouse over it the tooltip will read Switch To Networking Mode. This will show the following window.






Now click on the leftmost icon in the righthand pane, it is labeled Scan. If the radios are properly configured and powered up, you will see a window like the following.




In this window, the Coordinator radio is on the right and the Router is on the left. You can see that they talk directly to each other by the line between them.

If your network were more complicated, you would see more radios and who is talking to whom by the line between the various pairs of radios.

The mode you are in now shows the network as a visual graph. You can also see a table of radios by clicking the Mode button next to the Scan button and selecting Table. You will then see the following.




By clicking on the Connections dropdown on the right hand side of each radio row you can see a list of the radios that radio is connected to.

Consoles Mode


As stated earlier, you can also connect to a radio and tell it packets to send. Though we won't go into much detail here, I will at least show the window. The Consoles Mode is entered by clicking the button just to the left of the Networking Mode button. If you mouse over the button it will show Switch to Consoles working mode. Clicking on this will show the following.




Some day perhaps I will talk about how to use this window.


Conclusion


So now we have a couple of XBee radios configured to get data from the greenhouse into the house network for storage and processing. We have discussed some of the basics of XBee Series 2 radios and mesh networking. You have seen how to use Digi's XCTU tool for configuring radios and for looking at who is talking to whom. I also set up another Pro radio to sit between the greenhouse and the Coordinator, but have not shown that radio as it is configured the same way as the greenhouse Router.

In the next installment of this series we will wire the Greenhouse XBee Router to the sensor platform built before and write the software to capture the sensor data, get it into the house, and then upload it to a time series database in the cloud.

Be seeing you!



Saturday, July 4, 2015

Greenhouse: Phase 3, Sensors Working

It has been a while since I posted, but a lot has been going on.

As far as this project is concerned, the barometer ended up being a real pain. I was using the Adafruit MPL3115A2 breakout board and, as you can see from the previous post, I tied it in on the same I2C bus as one of the AM2315 Temperature and Humidity Sensors on the DUE. I modified the Adafruit library for the MPL3115A2 in the same way I modified the AM2315 to work on any I2C bus I want it to and fired everything up.

Nothing. Not a peep from the sensor.

I tried it on the second I2C bus.

Nothing.

Now that's annoying. I checked the wiring a bunch of times, checked my code, and yet... nothing.

I then dug out an Arduino UNO to try it out on and everything worked perfectly.

I wrote a post on the forums at Adafruit to see if anyone had any ideas. Someone else tried the MPL3115A2 on a DUE and also never got it to work. So I went over to Sparkfun and bought their same board. I know, kinda silly, but I was hoping it was something about the Adafruit board and that the Sparkfun board would just work. But no, it was not to be and the Sparkfun board remained silent on the DUE. Ultimately the folks at Adafruit decided that something was odd about the DUE I2C libraries just for the barometer board, perhaps a timing issue or something, even if it was working with the temperature/humidity sensors.

Poking around, I found the Teensy 3.1 has 2 I2C buses so gave it a try. Everything worked the first time, though the Teensy libraries do not use the I2C interfaces from twi.h, and I found they had their own I2C library called i2c_t3. So much for uniform library naming.

My final modifications for the Adafruit libraries for the AM2315 and the MPL3115A are found here and here, respectively.

Here is a picture of the final breadboard. I am leaving it as a breadboard for the next year in case I want to add in other sensors. I left it with the Sparkfun barometer board, which is on the right. The left side is the Teensy 3.1.




Below is the Fritzing diagram of the circuit. I couldn't find a part that shows the pins on the underside of the Teensy so just drew the wires with a note to label them. You can see the wires coming out from under the Teensy on its right side in the above picture.




My Arduino sketch is still not sending the binary data to the host processor, but here is the current code.



#include <i2c_t3.h>
#include <Adafruit_AM2315.h>
#include <Adafruit_MPL3115A2.h>

// The inside AM2315 temperature and humidity sensor.
Adafruit_AM2315 am2315Inside(&Wire1);

Adafruit_MPL3115A2 barometric(&Wire1);


// The outside AM2315 temperature and humidity sensor.
Adafruit_AM2315 am2315Outside(&Wire);

// The sensor data packet containing all data to be transmitted
// to the host.
typedef struct _SenseData {
  float temperatureInside;
  float humidityInside;
  float temperatureOutside;
  float humidityOutside;
  float altitude;
  float barometricPressure;
  float barometicTemp;
} SenseData;;

SenseData senseData;

void setup() {
  Serial.begin(9600);
}

void loop() {
 if (am2315Outside.begin()) {
    am2315Outside.readTemperatureAndHumidity(senseData.temperatureOutside, senseData.humidityOutside);
    Serial.print("Outside Hum: "); Serial.println(senseData.humidityOutside);
    Serial.print("Outside Temp: "); Serial.println(senseData.temperatureOutside);
  } else {
    Serial.println("Cannot see outside AM2315");
  }

  if (am2315Inside.begin()) {
    am2315Inside.readTemperatureAndHumidity(senseData.temperatureInside, senseData.humidityInside);
    Serial.print("Inside Hum: "); Serial.println(senseData.humidityInside);
    Serial.print("Inside Temp: "); Serial.println(senseData.temperatureInside);
  } else {
    Serial.println("Cannot see inside AM2315");
  }
 
  if (barometric.begin()) {
    senseData.barometricPressure = barometric.getPressure();
    Serial.print(senseData.barometricPressure/3377); Serial.println(" Inches (Hg)");

    senseData.altitude = barometric.getAltitude();
    Serial.print(senseData.altitude); Serial.println(" meters");

    senseData.barometicTemp = barometric.getTemperature();
    Serial.print(senseData.barometicTemp); Serial.println("*C");
  } else {
    Serial.println("Couldnt find barametric sensor");
  }

  delay(1000);
}

You may notice the conditionals inside the loop that are constantly calling the begin() method for the sensor. I did this so that if a physical sensor is removed, the other sensors can continue to report their data. The else section will probably set those fields to minus infinity or NaN if the sensor is not read.

When I am finally ready to send this to the host computer, I will use a line like

Serial.write(&senseData, sizeof(senseData);

This line will send out the packet with the data in the order that the fields are defined in the structure SenseData above.

In the next installment of the Greenhouse Saga, I will show the Java end that will read the data from the sensor and place it in an Interactive Spaces activity.


Tuesday, May 5, 2015

Greenhouse Phase 2: Temperature and Humidity Sensors

The next phase of the greenhouse is to add some sensors to the mix. The initial round will be for measuring temperature and humidity both inside and outside of the greenhouse and measure windspeed outside the greenhouse. Windspeed measurements in the greenhouse would hopefully be pretty boring data, except perhaps if someone left the door open.

The greenhouse was initially meant to be mostly underground, what is called a walipini, but as they were digging they hit bedrock rather soon. If we had done that we would have had a reasonably temperature constant greenhouse as the temperature stays reasonably constant when underground. Instead we ended up with a climate battery. The concept here is to pump heat into the ground during the day when it is warm, or at least not too cold, and heat up the ground, and then release that heat back into the greenhouse at night. This is accomplished by having a fan that blows air into a series of tubes buried in the ground of the greenhouse.

I am very curious to see how well the climate battery works, so want to capture temperature and humidity data for a few weeks with the climate battery off and then turn it on and see how much the temperature and humidity profiles smooth out.

After some searching, I decided to use the AM2315 temperature and humidity sensor from Adafruit. The sensor comes in a case that protects the inside sensors. The description on Adafruit says that it isn't really rated for being outdoors, but I have read about a lot of people using them that way so I will give it a try.

One immediate complication is that the AM2315 is an I2C device. For those unfamiliar with I2C, this is a two wire protocol for communicating with a series of devices on a serial bus, one wire for a clock and a second wire for data. Each I2C device has an address and you can read or write to the devices by giving the address of the device you want to read or write from. Unfortunate the AM2315 has a fixed I2C address (0x5C), so if you want to use 2 of them, you need 2 independent I2C buses.

My initial design used 2 Arduino Unos, once for each AM2315. I am using Arduinos for now as I want to experiment over time with which sensors I am going to use and want to be able to easily throw in other sensors on a breadboard. Then, at some point in the future, I can built something a little more permanent. 2 Arduinos means 2 USB ports to get the data out to a host for processing, so I decided after a while to go with the Arduino DUE, which is the only Arduino with 2 I2C buses on it.

I got the Adafruit AM2315 from its git repository at https://github.com/adafruit/Adafruit_AM2315 and wired the AM2315 to the I2C bus at pins 20 and 21 on the DUE. A quick check and everything was working just fine.

That second AM2315 ended up being more of an issue. The Adafruit library only supports the I2C bus on pins 20 and 21 and I wanted to use the code on both buses. I took a look at the source of the library and found that the Wire instance variable was hardcoded directly into the AM2315 code, so modified it to allow the user to specify which bus to use.

The initial class definition in Adafruit_AM2315.h had

class Adafruit_AM2315 {
 public:
  Adafruit_AM2315();

and I changed it to

class Adafruit_AM2315 {
 public:
  Adafruit_AM2315(TwoWire* w=&Wire);

This allows the usage Adafruit_AM2315 foo() to still go with Wire, but I can now also specify if I want Wire or Wire1, though with a slightly more complicated syntax.

I also needed to add a private instance variable of TwoWire* wire to the class definition.

Adafruit_AM2315 foo(&Wire1);

This required some changes to the Adafruit_AM2315.cpp code, I did a replacement of Wire. to wire-> throughout the source and modified the Adafruit AM2315 constructor from


Adafruit_AM2315::Adafruit_AM2315() {
}

to

Adafruit_AM2315::Adafruit_AM2315(TwoWire *w): wire{w} {
}

You can find the modified library at my github repo https://github.com/kmhughes/Adafruit_AM2315.

The circuit for both AM2315s, the anemometer, and a barometric pressure sensor (which should be ignored for now) is as follows.


Note the 10k pullup resistors on the I2C lines, the Arduino will not see the AM2315s if these resistors are missing.

The Arduino code to read the sensors is


#include <Wire.h>
#include <Adafruit_AM2315.h>
#include <Adafruit_MPL3115A2.h>

// The inside AM2315 temperature and humidity sensor.
Adafruit_AM2315 am2315Inside(&Wire);

// The outside AM2315 temperature and humidity sensor.
Adafruit_AM2315 am2315Outside(&Wire1);

// The sensor data packet containing all data to be transmitted
// to the host.
typedef struct {
  float temperatureInside;
  float humidityInside;
  float temperatureOutside;
  float humidityOutside;
  int windSpeed;
} SenseData;

SenseData senseData;

void setup() {
  Serial.begin(9600);

  if (! am2315Outside.begin()) {
     Serial.println("Outside AM2315 sensor not found, check wiring & pullups!");
     while (1);
  }

  if (! am2315Inside.begin()) {
     Serial.println("Inside AM2315 sensor not found, check wiring & pullups!");
     while (1);
  }
  
  // TODO(keith): initialize barametric sensors
}

void loop() {
  am2315Outside.readTemperatureAndHumidity(senseData.temperatureOutside, senseData.humidityOutside);
  am2315Inside.readTemperatureAndHumidity(senseData.temperatureInside, senseData.humidityInside);

  senseData.windSpeed = analogRead(0);
  
  // TODO(keith): Remove when sending data to host
  Serial.print("Outside Hum: "); Serial.println(senseData.humidityOutside);
  Serial.print("Outside Temp: "); Serial.println(senseData.temperatureOutside);
  Serial.print("Inside Hum: "); Serial.println(senseData.humidityInside);
  Serial.print("Inside Temp: "); Serial.println(senseData.temperatureInside);

  Serial.print("Wind Speed: "); Serial.println(senseData.windSpeed);
  
  // TODO(keith): Write data to host computer
  
  // TODO(keith): change sample rate to every 5 minutes or so
  delay(1000);
}

Here you can see that the inside AM2315 is on the first I2C bus on pins 20 and 21 and the outside AM2315 is on the additional bus. The anemometer is connected to analog 0 on the Arduino.

You may wonder why am I using a struct to store the sensor data? As you will see, once the code is complete, this will make it very easy to write all of my sensor data in a single line to the host computer. But for now I am debugging, so will leave the Serial prints in and have the code look annoyingly complicated.

This code can be found at https://github.com/kmhughes/robotbrains-smartspaces/blob/master/arduino/GreenhouseSensors/GreenhouseSensors.ino

One thing I discovered is that even if the AM2315s are sitting physically next to each other, they give different temperature values. So I will have to calibrate them and subtract an offset from both values calculated from a known accurate sensor.

At the moment I cannot get the barometer, which is also an I2C device, working on the DUE though I did make it work on an UNO. Once I sort that out I will modify the code and post the host side that will be capturing the data and storing it for later graphing and analysis and show the wiring inside and outside the greenhouse.

Saturday, April 25, 2015

Greenhouse Phase 1: Solar Power

We had a greenhouse built for us in the early months of this year. The plan is to ultimately build an aquaponics system in it that will be as automated as I can make it, hence why I am discussing it in this blog. My next post will be about the sensors I am building for measuring temperature and humidity both inside and outside the greenhouse. The greenhouse has a climate battery in it and I am very curious to see exactly how much it modifies the temperature profile throughout the day. Once the sensor array is in I will measure the temperatures for a couple of weeks without the climate battery, and then 2 weeks with the climate battery.

Step 1 of the project was to put in a small solar array to power the greenhouse.

For now the panels are just leaning on the outside of the greenhouse. Eventually, when I decide how many total I want, I will mount them on more permanent structures. Obviously they shouldn't stay on the windows, they will block the light.

Each panel outputs a maximum of 100 watts at 12 volts. They are tied together in parallel to give 12 volts at 200 watts.





For now I am working with just two batteries. Each is 12 volts and they are wired in parallel. The box on the left is a power inverter that takes the 12 volt DC and converts it to 110 volts AC.




Also do excuse the wire mess. It will not stay that way, but until the greenhouse layout is complete with the aquaponics system I don't want to be shortening wire lengths until I know where the wires will go.

The entire system is from Renogy and was bought on Amazon. For the most part I like the system, but I must admit, the charge controller feels like a piece of cheap crap and the wires do not feel that securely attached to it despite me tightening them down as much as I could.




OK, now on to attaching the I2C sensors to the Arduino Due...